@skrr-ai/cli 0.1.39 → 0.1.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +1 -1
  2. package/dist/base-command.d.ts +9 -0
  3. package/dist/base-command.js +26 -3
  4. package/dist/commands/agent-shares/list-for.js +4 -11
  5. package/dist/commands/agent-shares/received.js +4 -11
  6. package/dist/commands/agent-shares/sent.js +4 -11
  7. package/dist/commands/agent-shares/with.js +3 -10
  8. package/dist/commands/agents/activity.js +1 -1
  9. package/dist/commands/agents/api-actions/add.js +6 -0
  10. package/dist/commands/agents/chat.js +6 -1
  11. package/dist/commands/agents/create.d.ts +7 -0
  12. package/dist/commands/agents/create.js +19 -2
  13. package/dist/commands/agents/manager/ensure.d.ts +18 -0
  14. package/dist/commands/agents/manager/ensure.js +56 -0
  15. package/dist/commands/agents/show.js +4 -0
  16. package/dist/commands/agents/tools/auth.d.ts +6 -0
  17. package/dist/commands/agents/tools/auth.js +27 -1
  18. package/dist/commands/agents/tools/index.js +11 -1
  19. package/dist/commands/agents/triggers/policy.d.ts +1 -0
  20. package/dist/commands/agents/triggers/policy.js +32 -0
  21. package/dist/commands/agents/update.js +10 -4
  22. package/dist/commands/code/install.d.ts +4 -2
  23. package/dist/commands/code/install.js +12 -10
  24. package/dist/commands/convos/list.js +1 -3
  25. package/dist/commands/convos/show.js +4 -11
  26. package/dist/commands/daemon/index.js +3 -3
  27. package/dist/commands/daemon/install.js +4 -4
  28. package/dist/commands/daemon/login.js +1 -1
  29. package/dist/commands/daemon/logs.d.ts +3 -1
  30. package/dist/commands/daemon/logs.js +16 -1
  31. package/dist/commands/daemon/start.js +3 -3
  32. package/dist/commands/daemon/status.d.ts +9 -0
  33. package/dist/commands/daemon/status.js +22 -0
  34. package/dist/commands/dm/messages.js +1 -11
  35. package/dist/commands/followups/cancel.js +1 -1
  36. package/dist/commands/followups/observability.js +6 -2
  37. package/dist/commands/followups/remind.js +5 -2
  38. package/dist/commands/followups/reschedule.js +2 -1
  39. package/dist/commands/followups/resolve.js +1 -1
  40. package/dist/commands/followups/show.js +20 -0
  41. package/dist/commands/followups/watch.d.ts +1 -0
  42. package/dist/commands/followups/watch.js +9 -3
  43. package/dist/commands/harnesses/forget.js +20 -3
  44. package/dist/commands/harnesses/install.js +18 -6
  45. package/dist/commands/harnesses/installers.d.ts +1 -1
  46. package/dist/commands/harnesses/installers.js +36 -12
  47. package/dist/commands/harnesses/list.d.ts +74 -2
  48. package/dist/commands/harnesses/list.js +231 -15
  49. package/dist/commands/harnesses/ping.d.ts +2 -0
  50. package/dist/commands/harnesses/ping.js +21 -0
  51. package/dist/commands/harnesses/show.d.ts +22 -0
  52. package/dist/commands/harnesses/show.js +116 -17
  53. package/dist/commands/harnesses/update.d.ts +8 -0
  54. package/dist/commands/harnesses/update.js +25 -1
  55. package/dist/commands/harnesses/usage.d.ts +2 -0
  56. package/dist/commands/harnesses/usage.js +7 -2
  57. package/dist/commands/instructions/status.js +13 -0
  58. package/dist/commands/instructions/update-target.d.ts +1 -0
  59. package/dist/commands/instructions/update-target.js +11 -3
  60. package/dist/commands/login.js +5 -5
  61. package/dist/commands/machines/list.js +8 -9
  62. package/dist/commands/machines/show.js +46 -17
  63. package/dist/commands/messages/show.js +3 -10
  64. package/dist/commands/shares/get.js +2 -9
  65. package/dist/commands/shares/list.js +1 -9
  66. package/dist/commands/shortcuts/show.js +4 -11
  67. package/dist/commands/skills/local.js +6 -0
  68. package/dist/commands/spaces/actions/run.js +3 -4
  69. package/dist/commands/spaces/briefs/get.js +2 -9
  70. package/dist/commands/spaces/briefs/list.js +1 -9
  71. package/dist/commands/spaces/conversations.js +1 -9
  72. package/dist/commands/spaces/heartbeats/list.js +1 -9
  73. package/dist/commands/spaces/heartbeats/status.js +2 -9
  74. package/dist/commands/tasks/activity.js +1 -10
  75. package/dist/commands/tasks/approvals/list.js +1 -9
  76. package/dist/commands/tasks/attachments/list.js +1 -9
  77. package/dist/commands/tasks/comments/list.js +1 -9
  78. package/dist/commands/tasks/dri-status.js +2 -9
  79. package/dist/commands/tasks/events/append.js +9 -7
  80. package/dist/commands/tasks/execution-timeline.js +1 -9
  81. package/dist/commands/tasks/lifecycle/detach.js +26 -5
  82. package/dist/commands/tasks/monitor.js +1 -9
  83. package/dist/commands/tasks/result/submit.js +9 -0
  84. package/dist/commands/tasks/runs.js +1 -9
  85. package/dist/commands/tasks/show.js +5 -12
  86. package/dist/commands/token/list.d.ts +8 -0
  87. package/dist/commands/token/list.js +51 -6
  88. package/dist/help.js +8 -12
  89. package/dist/lib/agent-home-workspace.d.ts +19 -0
  90. package/dist/lib/agent-home-workspace.js +39 -0
  91. package/dist/lib/agent-resolver.js +2 -2
  92. package/dist/lib/agentic-stream.d.ts +22 -1
  93. package/dist/lib/agentic-stream.js +48 -7
  94. package/dist/lib/cli-installers.d.ts +84 -2
  95. package/dist/lib/cli-installers.js +146 -13
  96. package/dist/lib/command-miss.d.ts +22 -0
  97. package/dist/lib/command-miss.js +78 -0
  98. package/dist/lib/daemon-setup.js +2 -2
  99. package/dist/lib/daemon-target.d.ts +14 -0
  100. package/dist/lib/daemon-target.js +32 -0
  101. package/dist/lib/daemonBroker.js +1 -1
  102. package/dist/lib/daemonHandoff.js +1 -1
  103. package/dist/lib/dedicated-lease-command.js +4 -1
  104. package/dist/lib/dedicated-machines.d.ts +8 -1
  105. package/dist/lib/dedicated-machines.js +22 -6
  106. package/dist/lib/dedicated-wait.js +13 -0
  107. package/dist/lib/exec-runtime-binary.js +2 -2
  108. package/dist/lib/followups.d.ts +18 -0
  109. package/dist/lib/followups.js +14 -1
  110. package/dist/lib/format.d.ts +11 -0
  111. package/dist/lib/format.js +21 -0
  112. package/dist/lib/harness-labels.d.ts +39 -0
  113. package/dist/lib/harness-labels.js +60 -0
  114. package/dist/lib/harnesses.d.ts +6 -0
  115. package/dist/lib/instruction-input.d.ts +13 -0
  116. package/dist/lib/instruction-input.js +20 -1
  117. package/dist/lib/label-ref.d.ts +8 -1
  118. package/dist/lib/label-ref.js +12 -2
  119. package/dist/lib/local-skills.d.ts +17 -1
  120. package/dist/lib/local-skills.js +20 -2
  121. package/dist/lib/login.js +5 -5
  122. package/dist/lib/machines.d.ts +6 -0
  123. package/dist/lib/machines.js +68 -2
  124. package/dist/lib/option-hint.d.ts +15 -0
  125. package/dist/lib/option-hint.js +75 -0
  126. package/dist/lib/runtime-tool-support.d.ts +34 -0
  127. package/dist/lib/runtime-tool-support.js +77 -0
  128. package/dist/lib/share-rows.d.ts +17 -0
  129. package/dist/lib/share-rows.js +35 -0
  130. package/dist/lib/workspaces.d.ts +3 -0
  131. package/dist/lib/workspaces.js +38 -0
  132. package/dist/node_modules/@skrr-ai/data-provider/index.js +5029 -5007
  133. package/oclif.manifest.json +34255 -34142
  134. package/package.json +10 -5
@@ -59,12 +59,19 @@ export declare function closestNames(typed: string, names: string[], limit?: num
59
59
  export interface LabelVocabularyContext {
60
60
  /** Narrow to labels attachable to this entity type. Omit for all of them. */
61
61
  applicableTo?: LabelApplicable;
62
- /** Narrows to the workspace scope; `--workspace` / OVERSKY_WORKSPACE_ID. */
62
+ /**
63
+ * Narrows to this workspace scope. When omitted the AMBIENT workspace is
64
+ * resolved (`--workspace` / OVERSKY_WORKSPACE_ID / the server's default
65
+ * workspace): a session that never selected a workspace still writes to
66
+ * one, and a vocabulary that cannot see it refuses names that exist.
67
+ */
63
68
  workspaceId?: string;
64
69
  /** Archived labels are excluded unless asked for. */
65
70
  includeArchived?: boolean;
66
71
  /** Injected in tests. */
67
72
  getLabels?: typeof dataService.getLabels;
73
+ /** Injected in tests; defaults to `workspaces.ambientWorkspaceId`. */
74
+ resolveWorkspaceId?: () => Promise<string | undefined>;
68
75
  }
69
76
  export interface LabelRefContext extends LabelVocabularyContext {
70
77
  /** Which entity the resolved labels will be attached to. */
@@ -39,6 +39,7 @@ exports.closestNames = closestNames;
39
39
  exports.collectLabelVocabulary = collectLabelVocabulary;
40
40
  exports.resolveLabelRefs = resolveLabelRefs;
41
41
  const data_provider_1 = require("@skrr-ai/data-provider");
42
+ const workspaces_1 = require("./workspaces");
42
43
  /** A canonical v4 uuid, which is what every label id is. */
43
44
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
44
45
  function looksLikeLabelId(ref) {
@@ -89,9 +90,18 @@ function closestNames(typed, names, limit = 3) {
89
90
  */
90
91
  async function collectLabelVocabulary(context) {
91
92
  const getLabels = context.getLabels ?? data_provider_1.dataService.getLabels;
93
+ let workspaceId = context.workspaceId;
94
+ if (!workspaceId) {
95
+ try {
96
+ workspaceId = await (context.resolveWorkspaceId ?? workspaces_1.ambientWorkspaceId)();
97
+ }
98
+ catch {
99
+ workspaceId = undefined;
100
+ }
101
+ }
92
102
  const requests = [{ scope: 'user' }];
93
- if (context.workspaceId)
94
- requests.push({ scope: 'workspace', scopeId: context.workspaceId });
103
+ if (workspaceId)
104
+ requests.push({ scope: 'workspace', scopeId: workspaceId });
95
105
  const settled = await Promise.allSettled(requests.map((request) => getLabels({
96
106
  ...request,
97
107
  ...(context.applicableTo ? { applicableTo: context.applicableTo } : {}),
@@ -38,7 +38,23 @@ export declare function isUserInvocable(skill: Pick<DaemonLocalSkill, 'triggers'
38
38
  * dropped, because the server's own rule is that a skill the user picked and
39
39
  * the turn did not load must fail the turn rather than run without it.
40
40
  */
41
- export declare function resolveLocalSkillRefs(inventory: ReadonlyArray<DaemonLocalSkill>, requested: ReadonlyArray<string>): {
41
+ /**
42
+ * The restart that refreshes THIS daemon's inventory. A daemon id is
43
+ * `daemon_<machine>_<source>_<profile>`; a bare `skrr daemon restart` restarts
44
+ * the default profile, so for any other profile the hint named a different
45
+ * daemon than the one whose list was stale (OSK-10080 verifier).
46
+ */
47
+ export declare function daemonRestartCommand(daemonId: string | undefined): string;
48
+ export declare function resolveLocalSkillRefs(inventory: ReadonlyArray<DaemonLocalSkill>, requested: ReadonlyArray<string>, options?: {
49
+ /**
50
+ * The inventory is what the daemon reported when it last registered, not a
51
+ * live read. A skill added since is missing from it — for up to an hour, until
52
+ * the daemon reconnects — so "not on this machine" would be false (OSK-10080).
53
+ */
54
+ snapshot?: boolean;
55
+ /** Whose snapshot, so the refresh hint restarts the right daemon. */
56
+ daemonId?: string;
57
+ }): {
42
58
  refs: Array<{
43
59
  name: string;
44
60
  pathHash: string;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SKILL_CONVENTION_DISCOVERY_SINCE = void 0;
4
4
  exports.chooseDaemon = chooseDaemon;
5
5
  exports.isUserInvocable = isUserInvocable;
6
+ exports.daemonRestartCommand = daemonRestartCommand;
6
7
  exports.resolveLocalSkillRefs = resolveLocalSkillRefs;
7
8
  exports.withSkillInvocations = withSkillInvocations;
8
9
  exports.looksLikeSkillPath = looksLikeSkillPath;
@@ -10,6 +11,7 @@ exports.findLocalSkillsNamed = findLocalSkillsNamed;
10
11
  exports.describeStoreSkillMiss = describeStoreSkillMiss;
11
12
  exports.daemonPredates = daemonPredates;
12
13
  const data_provider_1 = require("@skrr-ai/data-provider");
14
+ const instruction_input_1 = require("./instruction-input");
13
15
  /**
14
16
  * Which machine to ask, or why there is no single answer.
15
17
  *
@@ -44,7 +46,19 @@ function isUserInvocable(skill) {
44
46
  * dropped, because the server's own rule is that a skill the user picked and
45
47
  * the turn did not load must fail the turn rather than run without it.
46
48
  */
47
- function resolveLocalSkillRefs(inventory, requested) {
49
+ /**
50
+ * The restart that refreshes THIS daemon's inventory. A daemon id is
51
+ * `daemon_<machine>_<source>_<profile>`; a bare `skrr daemon restart` restarts
52
+ * the default profile, so for any other profile the hint named a different
53
+ * daemon than the one whose list was stale (OSK-10080 verifier).
54
+ */
55
+ function daemonRestartCommand(daemonId) {
56
+ const profile = daemonId ? (0, instruction_input_1.parseDaemonId)(daemonId)?.profile : undefined;
57
+ return profile && profile !== 'default'
58
+ ? `skrr daemon restart --profile ${profile}`
59
+ : 'skrr daemon restart';
60
+ }
61
+ function resolveLocalSkillRefs(inventory, requested, options = {}) {
48
62
  const refs = [];
49
63
  const problems = [];
50
64
  const seen = new Set();
@@ -58,7 +72,11 @@ function resolveLocalSkillRefs(inventory, requested) {
58
72
  if (matches.length === 0) {
59
73
  problems.push(named.length > 0
60
74
  ? `No skill "${name}" under ${root}. It is under: ${named.map((s) => s.source ?? '?').join(', ')}.`
61
- : `No skill named "${name}" on this machine. See \`skrr skills local\`.`);
75
+ : options.snapshot
76
+ ? `No skill named "${name}" in the list this machine's daemon reported when it last connected. ` +
77
+ `A skill added since then appears after the daemon reconnects — run \`${daemonRestartCommand(options.daemonId)}\` on that machine. ` +
78
+ 'See `skrr skills local`.'
79
+ : `No skill named "${name}" on this machine. See \`skrr skills local\`.`);
62
80
  continue;
63
81
  }
64
82
  if (matches.length > 1) {
package/dist/lib/login.js CHANGED
@@ -84,7 +84,7 @@ async function cliLogin(opts = {}) {
84
84
  });
85
85
  if (brokerResult.ok) {
86
86
  console.log('');
87
- console.log(' Authorized via the local skrr runtime (no browser needed).');
87
+ console.log(' Authorized via the skrr background service on this computer (no browser needed).');
88
88
  return {
89
89
  token: brokerResult.accessToken,
90
90
  refreshToken: brokerResult.refreshToken,
@@ -107,7 +107,7 @@ async function cliLogin(opts = {}) {
107
107
  // broker failures — missing bootstrap, network blip, server 5xx —
108
108
  // silently fall through to PKCE / device-code below.
109
109
  if (brokerResult.reason === 'ci_token_refused') {
110
- throw new Error('The local skrr runtime is bootstrapped from a CI token, which cannot broker an interactive CLI login. ' +
110
+ throw new Error('The skrr background service on this computer is bootstrapped from a CI token, which cannot broker an interactive CLI login. ' +
111
111
  'Use `skrr create-token --name <name>` to mint a long-lived CI credential directly, ' +
112
112
  'or run `skrr login` on a machine where the daemon was authenticated interactively.');
113
113
  }
@@ -126,19 +126,19 @@ async function cliLogin(opts = {}) {
126
126
  brokerRateLimited = true;
127
127
  if (!(0, ssh_detect_1.isNonInteractive)()) {
128
128
  console.log('');
129
- console.log(' The local skrr runtime has brokered several logins recently and the server is ' +
129
+ console.log(' The skrr background service on this computer has brokered several logins recently and the server is ' +
130
130
  'rate-limiting them. Falling back to the browser flow.');
131
131
  }
132
132
  }
133
133
  if (brokerResult.reason === 'base_url_mismatch' && brokerResult.daemonServerUrl) {
134
134
  console.log('');
135
- console.log(` The local skrr runtime is logged in to ${brokerResult.daemonServerUrl}, but skrr is configured for ${serverUrl}.`);
135
+ console.log(` The skrr background service on this computer is logged in to ${brokerResult.daemonServerUrl}, but skrr is configured for ${serverUrl}.`);
136
136
  console.log(` Skipping the daemon-broker handoff. Run \`skrr login --base-url ${brokerResult.daemonServerUrl}\` to use the daemon instead.`);
137
137
  }
138
138
  }
139
139
  if ((0, ssh_detect_1.isNonInteractive)()) {
140
140
  throw new Error(brokerRateLimited
141
- ? 'The local skrr runtime is being rate-limited while brokering logins, and this ' +
141
+ ? 'The skrr background service on this computer is being rate-limited while brokering logins, and this ' +
142
142
  'shell cannot run the browser flow instead. The limit is per 15-minute window — ' +
143
143
  'retry shortly. For CI, use `skrr create-token` and set OVERSKY_TOKEN=osk_ci_...'
144
144
  : 'skrr login requires an interactive terminal or an SSH session. ' +
@@ -112,6 +112,12 @@ export interface Machine {
112
112
  * could come back says so, and a destroyed one cannot.
113
113
  */
114
114
  export declare function machineStateLabel(machine: Machine): string;
115
+ /**
116
+ * What a list says it left out, by the label each hidden machine would have
117
+ * printed: `250 terminated, 5 offline, 1 paused`. "offline or terminated"
118
+ * called a paused machine offline after the table stopped doing so (OSK-9996).
119
+ */
120
+ export declare function hiddenMachinesSummary(hidden: readonly Machine[]): string;
115
121
  /** Is this a machine a user could still use, or bring back? */
116
122
  export declare function isReachableMachine(machine: Machine): boolean;
117
123
  /** The user-editable slice. Identities, capabilities and lifecycle are not. */
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.machineStateLabel = machineStateLabel;
4
+ exports.hiddenMachinesSummary = hiddenMachinesSummary;
4
5
  exports.isReachableMachine = isReachableMachine;
5
6
  exports.liveHarnessCount = liveHarnessCount;
6
7
  exports.listMachines = listMachines;
@@ -30,8 +31,36 @@ function machineStateLabel(machine) {
30
31
  if (controls && controls.canResume === false && controls.canTerminate === false) {
31
32
  return 'terminated';
32
33
  }
34
+ // A leased machine at rest says WHICH rest. `machines hosted list` printed
35
+ // `paused` and this list printed `offline` for the same machine, with the
36
+ // reason only in `machines show` (OSK-9996). `offline` stays for a machine
37
+ // that simply is not connected.
38
+ const leaseState = machine.lease?.state;
39
+ if (leaseState === 'paused' || leaseState === 'stopped')
40
+ return leaseState;
41
+ // A lease the provider has ended cannot come back either; it read `offline`
42
+ // while an unleased dead sandbox read `terminated`, so the hidden-machine
43
+ // footer counted 95 ended leases as offline (OSK-9996 verifier).
44
+ if (leaseState === 'terminated')
45
+ return 'terminated';
33
46
  return 'offline';
34
47
  }
48
+ /**
49
+ * What a list says it left out, by the label each hidden machine would have
50
+ * printed: `250 terminated, 5 offline, 1 paused`. "offline or terminated"
51
+ * called a paused machine offline after the table stopped doing so (OSK-9996).
52
+ */
53
+ function hiddenMachinesSummary(hidden) {
54
+ const counts = new Map();
55
+ for (const machine of hidden) {
56
+ const label = machineStateLabel(machine);
57
+ counts.set(label, (counts.get(label) ?? 0) + 1);
58
+ }
59
+ return [...counts.entries()]
60
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
61
+ .map(([label, count]) => `${count} ${label}`)
62
+ .join(', ');
63
+ }
35
64
  /** Is this a machine a user could still use, or bring back? */
36
65
  function isReachableMachine(machine) {
37
66
  return machineStateLabel(machine) !== 'terminated' && machine.status !== 'offline';
@@ -105,6 +134,22 @@ function hostedSandboxDaemonIds(ref) {
105
134
  return [];
106
135
  return [`hosted-machine-${ref}`, `hosted-job-machine-${ref}`];
107
136
  }
137
+ /**
138
+ * An id with its identity-kind prefix removed: `lease:rtl_…` → `rtl_…`,
139
+ * `dedicated-runtime:rtl_…` → `rtl_…`, `machine:0354…` → `0354…`.
140
+ *
141
+ * Other commands print the bare form. `machines dedicated list` and the lease
142
+ * payloads name `rtl_…`, and `harnesses list`'s MACHINE column prints the head
143
+ * of the identifier after the kind — so the id a user has to hand often lacks
144
+ * the prefix this list keys on, and `machines show rtl_…` answered "No machine
145
+ * matches" for a machine it was listing as `lease:rtl_…` (OSK-9269, OSK-9290).
146
+ */
147
+ function withoutIdentityKind(value) {
148
+ if (!value)
149
+ return undefined;
150
+ const separator = value.indexOf(':');
151
+ return separator === -1 ? undefined : value.slice(separator + 1);
152
+ }
108
153
  function resolveMachineRef(machines, ref) {
109
154
  const needle = ref.trim();
110
155
  const lower = needle.toLowerCase();
@@ -121,13 +166,26 @@ function resolveMachineRef(machines, ref) {
121
166
  // two unrelated-looking ids, no way on screen to tell they are one
122
167
  // machine. Exact composition, not a substring search, so this cannot
123
168
  // match a machine that merely contains the text.
124
- hostedSandboxDaemonIds(needle).some((candidate) => m.daemonId === candidate));
169
+ hostedSandboxDaemonIds(needle).some((candidate) => m.daemonId === candidate) ||
170
+ withoutIdentityKind(m.id) === needle ||
171
+ withoutIdentityKind(m.machineUuid) === needle ||
172
+ // A harness id names exactly one machine — the one it is registered on.
173
+ // Refusing it with "No machine matches" sent the user to a different
174
+ // command without saying which (OSK-9313).
175
+ (m.harnesses ?? []).some((h) => h.id === needle));
125
176
  if (exact.length === 1)
126
177
  return exact[0];
127
178
  const byName = machines.filter((m) => m.name?.toLowerCase() === lower);
128
179
  if (byName.length === 1)
129
180
  return byName[0];
130
- const prefix = machines.filter((m) => [m.id, m.machineUuid, m.stableDaemonId, m.daemonId].some((candidate) => candidate && candidate.toLowerCase().startsWith(lower)));
181
+ const prefix = machines.filter((m) => [
182
+ m.id,
183
+ m.machineUuid,
184
+ m.stableDaemonId,
185
+ m.daemonId,
186
+ withoutIdentityKind(m.id),
187
+ withoutIdentityKind(m.machineUuid),
188
+ ].some((candidate) => candidate && candidate.toLowerCase().startsWith(lower)));
131
189
  if (prefix.length === 1)
132
190
  return prefix[0];
133
191
  if (prefix.length > 1) {
@@ -140,5 +198,13 @@ function resolveMachineRef(machines, ref) {
140
198
  throw new Error(`"${ref}" matches ${prefix.length} machines: ${shown.join(', ')}` +
141
199
  `${rest > 0 ? `, and ${rest} more` : ''}. Use a longer prefix or the full id.`);
142
200
  }
201
+ // A harness id prefix or a harness name is the other `show`'s input. Say so
202
+ // rather than only "no machine", which left the reader to guess that a
203
+ // sibling command takes it (OSK-9313).
204
+ const harnessMatch = machines.some((m) => (m.harnesses ?? []).some((h) => (lower.length >= 4 && h.id.toLowerCase().startsWith(lower)) ||
205
+ (typeof h.name === 'string' && h.name.toLowerCase() === lower)));
206
+ if (harnessMatch) {
207
+ throw new Error(`No machine matches "${ref}", but a harness does. \`skrr harnesses show ${ref}\` takes it.`);
208
+ }
143
209
  throw new Error(`No machine matches "${ref}". Run \`skrr machines list\` to see them.`);
144
210
  }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * A "did you mean" for oclif's enum refusal (OSK-9735).
3
+ *
4
+ * Tasks, spaces, goals and key results each spell one lifecycle their own way —
5
+ * "under way" is `in_progress` for a space and `active` for a goal; "finished"
6
+ * is `done`, `completed` or `achieved` — and oclif's
7
+ * `Expected --status=in_progress to be one of: …` was the only place a user
8
+ * learned that. This names the value the command uses for the word that was
9
+ * typed, and otherwise the nearest spelling.
10
+ *
11
+ * It suggests and never substitutes. Some of the differences are deliberate (a
12
+ * key result's status is a health reading, not a lifecycle), so writing a mapped
13
+ * value would record a status nobody chose.
14
+ */
15
+ export declare function invalidOptionHint(message: string): string | null;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ /**
3
+ * A "did you mean" for oclif's enum refusal (OSK-9735).
4
+ *
5
+ * Tasks, spaces, goals and key results each spell one lifecycle their own way —
6
+ * "under way" is `in_progress` for a space and `active` for a goal; "finished"
7
+ * is `done`, `completed` or `achieved` — and oclif's
8
+ * `Expected --status=in_progress to be one of: …` was the only place a user
9
+ * learned that. This names the value the command uses for the word that was
10
+ * typed, and otherwise the nearest spelling.
11
+ *
12
+ * It suggests and never substitutes. Some of the differences are deliberate (a
13
+ * key result's status is a health reading, not a lifecycle), so writing a mapped
14
+ * value would record a status nobody chose.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.invalidOptionHint = invalidOptionHint;
18
+ /**
19
+ * Spellings of one concept across the CLI's vocabularies. A value is suggested
20
+ * only when it shares a group with what was typed.
21
+ */
22
+ const CONCEPT_GROUPS = [
23
+ new Set(['in_progress', 'inprogress', 'active', 'started', 'ongoing', 'running', 'doing']),
24
+ new Set(['done', 'completed', 'complete', 'achieved', 'finished', 'closed', 'resolved']),
25
+ new Set(['cancelled', 'canceled', 'abandoned']),
26
+ new Set(['in_review', 'inreview', 'review', 'reviewing']),
27
+ new Set(['planned', 'todo', 'to_do', 'not_started']),
28
+ ];
29
+ const REFUSAL = /Expected (--[\w-]+)=(.+?) to be one of: ([^\n]+)/;
30
+ const normalize = (value) => value
31
+ .trim()
32
+ .toLowerCase()
33
+ .replace(/[\s-]+/g, '_');
34
+ function editDistance(a, b) {
35
+ const row = Array.from({ length: b.length + 1 }, (_, i) => i);
36
+ for (let i = 1; i <= a.length; i += 1) {
37
+ let diagonal = row[0];
38
+ row[0] = i;
39
+ for (let j = 1; j <= b.length; j += 1) {
40
+ const above = row[j];
41
+ row[j] = Math.min(row[j] + 1, row[j - 1] + 1, diagonal + (a[i - 1] === b[j - 1] ? 0 : 1));
42
+ diagonal = above;
43
+ }
44
+ }
45
+ return row[b.length];
46
+ }
47
+ function invalidOptionHint(message) {
48
+ const match = REFUSAL.exec(message || '');
49
+ if (!match)
50
+ return null;
51
+ const [, flag, input, list] = match;
52
+ const options = list
53
+ .split(',')
54
+ .map((option) => option.trim())
55
+ .filter(Boolean);
56
+ const typed = normalize(input);
57
+ const exact = options.find((option) => normalize(option) === typed);
58
+ if (exact)
59
+ return ` Did you mean ${flag} ${exact}?`;
60
+ const group = CONCEPT_GROUPS.find((concept) => concept.has(typed));
61
+ const synonym = group && options.find((option) => group.has(normalize(option)));
62
+ if (synonym) {
63
+ return ` Did you mean ${flag} ${synonym}? Here "${input}" is spelled "${synonym}".`;
64
+ }
65
+ let nearest = null;
66
+ for (const option of options) {
67
+ const distance = editDistance(typed, normalize(option));
68
+ if (!nearest || distance < nearest.distance)
69
+ nearest = { option, distance };
70
+ }
71
+ if (nearest && nearest.distance <= Math.max(2, Math.floor(typed.length / 4))) {
72
+ return ` Did you mean ${flag} ${nearest.option}?`;
73
+ }
74
+ return null;
75
+ }
@@ -0,0 +1,34 @@
1
+ import type { TPlugin } from '@skrr-ai/data-provider';
2
+ /**
3
+ * What an agent turn can call, said where tools are configured.
4
+ *
5
+ * Plugin tools (`agents tools`, `--tools`) and OpenAPI actions
6
+ * (`agents api-actions add`) reach an agent turn through the runtime's
7
+ * `agent-tools` server (OSK-9750, OSK-9754). Two limits remain, and the CLI is
8
+ * where the tool is set up, so it says them there:
9
+ * - image and file plugins save their output through a web request an agent
10
+ * turn does not have, so they are stored but not loaded;
11
+ * - an OAuth action runs only after the user authorizes it once, through a
12
+ * link the agent hands over on its first call.
13
+ */
14
+ /** The catalog's AGENT column. `?` for a server older than the `agentRuntime` field. */
15
+ export declare function agentRuntimeLabel(tool: Pick<TPlugin, 'agentRuntime'>): string;
16
+ export declare const AGENT_COLUMN_FOOTNOTE: string;
17
+ /** Stored plugin keys the catalog says an agent turn does not load. */
18
+ export declare function pluginKeysNotLoaded(keys: string[], catalog: TPlugin[]): string[];
19
+ export declare function pluginsNotLoadedNotice(keys: string[]): string;
20
+ /** Said after adding an OAuth action, since nothing works until both steps happen. */
21
+ export declare function oauthActionNotice(redirectUrl: string): string;
22
+ /** Split an agent's stored `tools` into plugin keys and OpenAPI action tool names. */
23
+ export declare function splitStoredTools(tools: unknown): {
24
+ plugins: string[];
25
+ actions: string[];
26
+ };
27
+ /**
28
+ * After `--tools` is saved: warn about the plugin keys an agent turn will not load.
29
+ *
30
+ * Unknown keys are refused by the server (OSK-9545); this covers the known ones
31
+ * that are stored but skipped. Best effort: a catalog that cannot be read says
32
+ * nothing rather than failing a write that already succeeded.
33
+ */
34
+ export declare function warnPluginsNotLoaded(tools: unknown, warn: (message: string) => void, fetchCatalog?: () => Promise<TPlugin[]>): Promise<void>;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AGENT_COLUMN_FOOTNOTE = void 0;
4
+ exports.agentRuntimeLabel = agentRuntimeLabel;
5
+ exports.pluginKeysNotLoaded = pluginKeysNotLoaded;
6
+ exports.pluginsNotLoadedNotice = pluginsNotLoadedNotice;
7
+ exports.oauthActionNotice = oauthActionNotice;
8
+ exports.splitStoredTools = splitStoredTools;
9
+ exports.warnPluginsNotLoaded = warnPluginsNotLoaded;
10
+ const data_provider_1 = require("@skrr-ai/data-provider");
11
+ /**
12
+ * What an agent turn can call, said where tools are configured.
13
+ *
14
+ * Plugin tools (`agents tools`, `--tools`) and OpenAPI actions
15
+ * (`agents api-actions add`) reach an agent turn through the runtime's
16
+ * `agent-tools` server (OSK-9750, OSK-9754). Two limits remain, and the CLI is
17
+ * where the tool is set up, so it says them there:
18
+ * - image and file plugins save their output through a web request an agent
19
+ * turn does not have, so they are stored but not loaded;
20
+ * - an OAuth action runs only after the user authorizes it once, through a
21
+ * link the agent hands over on its first call.
22
+ */
23
+ /** The catalog's AGENT column. `?` for a server older than the `agentRuntime` field. */
24
+ function agentRuntimeLabel(tool) {
25
+ if (tool.agentRuntime === undefined)
26
+ return '?';
27
+ return tool.agentRuntime ? 'yes' : 'no';
28
+ }
29
+ exports.AGENT_COLUMN_FOOTNOTE = 'AGENT says whether an agent turn can call the tool. Image and file tools (AGENT no) ' +
30
+ 'save their output through a web request an agent turn does not have.';
31
+ /** Stored plugin keys the catalog says an agent turn does not load. */
32
+ function pluginKeysNotLoaded(keys, catalog) {
33
+ const byKey = new Map(catalog.map((tool) => [tool.pluginKey, tool]));
34
+ return keys.filter((key) => byKey.get(key)?.agentRuntime === false);
35
+ }
36
+ function pluginsNotLoadedNotice(keys) {
37
+ const [subject, verb] = keys.length === 1 ? ['it', 'saves'] : ['they', 'save'];
38
+ return (`Saved, but an agent turn cannot call ${keys.join(', ')}: ${subject} ${verb} ` +
39
+ 'output through a web request an agent turn does not have.');
40
+ }
41
+ /** Said after adding an OAuth action, since nothing works until both steps happen. */
42
+ function oauthActionNotice(redirectUrl) {
43
+ return (`Register ${redirectUrl} as the redirect URL with the OAuth provider. ` +
44
+ "The agent's first call to one of these operations returns a link for you to authorize; " +
45
+ 'after you finish, ask it to try again.');
46
+ }
47
+ /** Split an agent's stored `tools` into plugin keys and OpenAPI action tool names. */
48
+ function splitStoredTools(tools) {
49
+ const list = Array.isArray(tools) ? tools.map(String) : [];
50
+ return {
51
+ plugins: list.filter((name) => !name.includes(data_provider_1.actionDelimiter)),
52
+ actions: list.filter((name) => name.includes(data_provider_1.actionDelimiter)),
53
+ };
54
+ }
55
+ /**
56
+ * After `--tools` is saved: warn about the plugin keys an agent turn will not load.
57
+ *
58
+ * Unknown keys are refused by the server (OSK-9545); this covers the known ones
59
+ * that are stored but skipped. Best effort: a catalog that cannot be read says
60
+ * nothing rather than failing a write that already succeeded.
61
+ */
62
+ async function warnPluginsNotLoaded(tools, warn, fetchCatalog = () => data_provider_1.dataService.getAvailableTools()) {
63
+ const { plugins } = splitStoredTools(tools);
64
+ if (plugins.length === 0)
65
+ return;
66
+ let catalog;
67
+ try {
68
+ catalog = await fetchCatalog();
69
+ }
70
+ catch {
71
+ return;
72
+ }
73
+ const skipped = pluginKeysNotLoaded(plugins, catalog);
74
+ if (skipped.length > 0) {
75
+ warn(pluginsNotLoadedNotice(skipped));
76
+ }
77
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * How an agent-share row names its share and the other person.
3
+ *
4
+ * The share routes answer `{ shareId, owner: { userId, name }, recipient:
5
+ * { userId, name } }`. The four `agent-shares` listings read `id`/`_id` and
6
+ * stringified the person object, so every row printed SHARE ID `-` and FROM/TO
7
+ * `[object Object]` (OSK-10043): the one id `respond`/`revoke` need was
8
+ * unreadable, and so was who the share was with.
9
+ */
10
+ /** The share's id, whichever key the row carries it under. */
11
+ export declare function shareIdOf(share: Record<string, unknown>): string;
12
+ /**
13
+ * A person as `Name (userId)` — the name to recognise, the id that
14
+ * `agent-shares with <userId>` takes. Accepts a bare id string too, which older
15
+ * rows used.
16
+ */
17
+ export declare function sharePartyLabel(value: unknown): string;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /**
3
+ * How an agent-share row names its share and the other person.
4
+ *
5
+ * The share routes answer `{ shareId, owner: { userId, name }, recipient:
6
+ * { userId, name } }`. The four `agent-shares` listings read `id`/`_id` and
7
+ * stringified the person object, so every row printed SHARE ID `-` and FROM/TO
8
+ * `[object Object]` (OSK-10043): the one id `respond`/`revoke` need was
9
+ * unreadable, and so was who the share was with.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.shareIdOf = shareIdOf;
13
+ exports.sharePartyLabel = sharePartyLabel;
14
+ /** The share's id, whichever key the row carries it under. */
15
+ function shareIdOf(share) {
16
+ const id = share.shareId ?? share.id ?? share._id;
17
+ return typeof id === 'string' && id.length > 0 ? id : '-';
18
+ }
19
+ /**
20
+ * A person as `Name (userId)` — the name to recognise, the id that
21
+ * `agent-shares with <userId>` takes. Accepts a bare id string too, which older
22
+ * rows used.
23
+ */
24
+ function sharePartyLabel(value) {
25
+ if (typeof value === 'string')
26
+ return value.length > 0 ? value : '-';
27
+ if (!value || typeof value !== 'object')
28
+ return '-';
29
+ const party = value;
30
+ const id = [party.userId, party.id].find((v) => typeof v === 'string' && v.length > 0);
31
+ const name = [party.name, party.email].find((v) => typeof v === 'string' && v.length > 0);
32
+ if (name && id)
33
+ return `${name} (${id})`;
34
+ return name ?? id ?? '-';
35
+ }
@@ -56,6 +56,9 @@ export interface WorkspaceListing {
56
56
  */
57
57
  export declare function describeUnselectedWorkspace(listing: WorkspaceListing): string;
58
58
  export declare function listWorkspaces(): Promise<WorkspaceRow[]>;
59
+ export declare function ambientWorkspaceId(env?: NodeJS.ProcessEnv): Promise<string | undefined>;
60
+ /** Test seam — a memoized network result must not leak between specs. */
61
+ export declare function clearAmbientWorkspaceCache(): void;
59
62
  /** `listWorkspaces`, keeping the envelope's caller-level fields. */
60
63
  export declare function listWorkspaceListing(): Promise<WorkspaceListing>;
61
64
  export declare function getWorkspace(workspaceId: string): Promise<WorkspaceRow>;
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.describeUnselectedWorkspace = describeUnselectedWorkspace;
4
4
  exports.listWorkspaces = listWorkspaces;
5
+ exports.ambientWorkspaceId = ambientWorkspaceId;
6
+ exports.clearAmbientWorkspaceCache = clearAmbientWorkspaceCache;
5
7
  exports.listWorkspaceListing = listWorkspaceListing;
6
8
  exports.getWorkspace = getWorkspace;
7
9
  exports.matchWorkspace = matchWorkspace;
@@ -60,6 +62,42 @@ function describeUnselectedWorkspace(listing) {
60
62
  async function listWorkspaces() {
61
63
  return (await listWorkspaceListing()).rows;
62
64
  }
65
+ /**
66
+ * The workspace a bare session actually writes to, for callers merging the
67
+ * caller's vocabulary or defaults across scopes.
68
+ *
69
+ * `OVERSKY_WORKSPACE_ID` is only populated when a workspace was SELECTED —
70
+ * `workspaces use`, `--workspace`, or a daemon session handoff. An account
71
+ * that never selected one still has a server-side `defaultWorkspaceId`, and
72
+ * every unscoped write lands there; a caller that only read the variable
73
+ * would reason about a smaller world than the one the server writes into
74
+ * (observed: `labels list` reported a user-only vocabulary while three
75
+ * workspace-scoped labels existed and were attachable).
76
+ *
77
+ * The listing fetch is memoized and fail-open: this runs to give a better
78
+ * answer, and must never become the error — an unreachable server returns
79
+ * `undefined`, which is the same world the caller saw before this existed.
80
+ */
81
+ let memoizedDefaultWorkspaceId;
82
+ async function ambientWorkspaceId(env = process.env) {
83
+ const explicit = env.OVERSKY_WORKSPACE_ID?.trim();
84
+ if (explicit)
85
+ return explicit;
86
+ if (memoizedDefaultWorkspaceId !== undefined) {
87
+ return memoizedDefaultWorkspaceId ?? undefined;
88
+ }
89
+ try {
90
+ memoizedDefaultWorkspaceId = (await listWorkspaceListing()).defaultWorkspaceId ?? null;
91
+ }
92
+ catch {
93
+ memoizedDefaultWorkspaceId = null;
94
+ }
95
+ return memoizedDefaultWorkspaceId ?? undefined;
96
+ }
97
+ /** Test seam — a memoized network result must not leak between specs. */
98
+ function clearAmbientWorkspaceCache() {
99
+ memoizedDefaultWorkspaceId = undefined;
100
+ }
63
101
  /** `listWorkspaces`, keeping the envelope's caller-level fields. */
64
102
  async function listWorkspaceListing() {
65
103
  const rows = [];