@skrr-ai/cli 0.1.40 → 0.1.42

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 (120) hide show
  1. package/dist/base-command.d.ts +9 -0
  2. package/dist/base-command.js +18 -3
  3. package/dist/commands/agent-shares/list-for.js +4 -11
  4. package/dist/commands/agent-shares/received.js +4 -11
  5. package/dist/commands/agent-shares/sent.js +4 -11
  6. package/dist/commands/agent-shares/with.js +3 -10
  7. package/dist/commands/agents/activity.js +1 -1
  8. package/dist/commands/agents/api-actions/add.js +6 -0
  9. package/dist/commands/agents/chat.js +6 -1
  10. package/dist/commands/agents/create.js +6 -1
  11. package/dist/commands/agents/manager/ensure.d.ts +18 -0
  12. package/dist/commands/agents/manager/ensure.js +56 -0
  13. package/dist/commands/agents/show.js +4 -0
  14. package/dist/commands/agents/tools/auth.d.ts +6 -0
  15. package/dist/commands/agents/tools/auth.js +27 -1
  16. package/dist/commands/agents/tools/index.js +11 -1
  17. package/dist/commands/agents/update.js +6 -1
  18. package/dist/commands/code/install.d.ts +4 -2
  19. package/dist/commands/code/install.js +15 -14
  20. package/dist/commands/convos/list.js +1 -3
  21. package/dist/commands/convos/show.js +4 -11
  22. package/dist/commands/daemon/index.js +3 -3
  23. package/dist/commands/daemon/install.js +4 -4
  24. package/dist/commands/daemon/login.js +1 -1
  25. package/dist/commands/daemon/logs.d.ts +3 -1
  26. package/dist/commands/daemon/logs.js +16 -1
  27. package/dist/commands/daemon/start.js +3 -3
  28. package/dist/commands/daemon/status.d.ts +9 -0
  29. package/dist/commands/daemon/status.js +22 -0
  30. package/dist/commands/dm/messages.js +1 -11
  31. package/dist/commands/followups/cancel.js +1 -1
  32. package/dist/commands/followups/remind.js +4 -0
  33. package/dist/commands/followups/resolve.js +1 -1
  34. package/dist/commands/followups/show.js +16 -0
  35. package/dist/commands/harnesses/forget.js +20 -3
  36. package/dist/commands/harnesses/install.d.ts +18 -0
  37. package/dist/commands/harnesses/install.js +146 -25
  38. package/dist/commands/harnesses/installers.d.ts +1 -1
  39. package/dist/commands/harnesses/installers.js +36 -12
  40. package/dist/commands/harnesses/list.d.ts +74 -2
  41. package/dist/commands/harnesses/list.js +231 -15
  42. package/dist/commands/harnesses/ping.d.ts +2 -0
  43. package/dist/commands/harnesses/ping.js +21 -0
  44. package/dist/commands/harnesses/show.d.ts +22 -0
  45. package/dist/commands/harnesses/show.js +116 -17
  46. package/dist/commands/harnesses/update.d.ts +8 -0
  47. package/dist/commands/harnesses/update.js +25 -1
  48. package/dist/commands/harnesses/usage.d.ts +2 -0
  49. package/dist/commands/harnesses/usage.js +7 -2
  50. package/dist/commands/instructions/status.js +13 -0
  51. package/dist/commands/instructions/update-target.d.ts +1 -0
  52. package/dist/commands/instructions/update-target.js +11 -3
  53. package/dist/commands/login.js +5 -5
  54. package/dist/commands/machines/list.js +8 -9
  55. package/dist/commands/machines/show.js +46 -17
  56. package/dist/commands/messages/show.js +3 -10
  57. package/dist/commands/shares/get.js +2 -9
  58. package/dist/commands/shares/list.js +1 -9
  59. package/dist/commands/shortcuts/show.js +4 -11
  60. package/dist/commands/skills/import-as-actions.js +6 -15
  61. package/dist/commands/skills/local.js +6 -0
  62. package/dist/commands/spaces/actions/run.js +3 -4
  63. package/dist/commands/spaces/briefs/get.js +2 -9
  64. package/dist/commands/spaces/briefs/list.js +1 -9
  65. package/dist/commands/spaces/conversations.js +1 -9
  66. package/dist/commands/spaces/heartbeats/list.js +1 -9
  67. package/dist/commands/spaces/heartbeats/status.js +2 -9
  68. package/dist/commands/tasks/activity.js +1 -10
  69. package/dist/commands/tasks/approvals/list.js +1 -9
  70. package/dist/commands/tasks/attachments/list.js +1 -9
  71. package/dist/commands/tasks/comments/list.js +1 -9
  72. package/dist/commands/tasks/complete.js +13 -0
  73. package/dist/commands/tasks/dri-status.js +2 -9
  74. package/dist/commands/tasks/events/append.js +9 -7
  75. package/dist/commands/tasks/execution-timeline.js +1 -9
  76. package/dist/commands/tasks/monitor.js +1 -9
  77. package/dist/commands/tasks/result/submit.js +14 -0
  78. package/dist/commands/tasks/runs.js +1 -9
  79. package/dist/commands/tasks/show.js +5 -12
  80. package/dist/commands/token/list.d.ts +8 -0
  81. package/dist/commands/token/list.js +51 -6
  82. package/dist/help.js +8 -12
  83. package/dist/lib/agent-home-workspace.d.ts +19 -0
  84. package/dist/lib/agent-home-workspace.js +39 -0
  85. package/dist/lib/agent-resolver.js +2 -2
  86. package/dist/lib/agentic-stream.d.ts +22 -1
  87. package/dist/lib/agentic-stream.js +48 -7
  88. package/dist/lib/cli-installers.d.ts +124 -2
  89. package/dist/lib/cli-installers.js +196 -13
  90. package/dist/lib/command-miss.d.ts +22 -0
  91. package/dist/lib/command-miss.js +78 -0
  92. package/dist/lib/daemon-setup.js +2 -2
  93. package/dist/lib/daemon-target.d.ts +14 -0
  94. package/dist/lib/daemon-target.js +32 -0
  95. package/dist/lib/daemonBroker.js +1 -1
  96. package/dist/lib/daemonHandoff.js +1 -1
  97. package/dist/lib/exec-runtime-binary.js +2 -2
  98. package/dist/lib/followups.d.ts +12 -0
  99. package/dist/lib/followups.js +14 -1
  100. package/dist/lib/format.d.ts +11 -0
  101. package/dist/lib/format.js +21 -0
  102. package/dist/lib/harness-labels.d.ts +39 -0
  103. package/dist/lib/harness-labels.js +60 -0
  104. package/dist/lib/harnesses.d.ts +6 -0
  105. package/dist/lib/instruction-input.d.ts +13 -0
  106. package/dist/lib/instruction-input.js +20 -1
  107. package/dist/lib/local-skills.d.ts +44 -1
  108. package/dist/lib/local-skills.js +58 -2
  109. package/dist/lib/login.js +5 -5
  110. package/dist/lib/machines.d.ts +6 -0
  111. package/dist/lib/machines.js +68 -2
  112. package/dist/lib/runtime-tool-support.d.ts +34 -0
  113. package/dist/lib/runtime-tool-support.js +77 -0
  114. package/dist/lib/share-rows.d.ts +17 -0
  115. package/dist/lib/share-rows.js +35 -0
  116. package/dist/lib/task-closure.d.ts +13 -0
  117. package/dist/lib/task-closure.js +17 -0
  118. package/dist/node_modules/@skrr-ai/data-provider/index.js +5066 -5033
  119. package/oclif.manifest.json +33472 -33360
  120. package/package.json +6 -3
@@ -4,6 +4,7 @@ exports.isPingSuccess = isPingSuccess;
4
4
  const core_1 = require("@oclif/core");
5
5
  const base_command_1 = require("../../base-command");
6
6
  const harnesses_1 = require("../../lib/harnesses");
7
+ const cli_installers_1 = require("../../lib/cli-installers");
7
8
  /**
8
9
  * Is this machine actually reachable right now?
9
10
  *
@@ -84,6 +85,9 @@ class HarnessesPing extends base_command_1.BaseCommand {
84
85
  this.log(result.latencyMs != null
85
86
  ? `Answered in ${result.latencyMs}ms.`
86
87
  : 'Answered (no latency reported).');
88
+ // Answering proves the daemon is reachable, not that this harness can
89
+ // run a turn — a signed-out Devin answers too (OSK-9263).
90
+ await this.logReadiness(args.harness);
87
91
  }
88
92
  else {
89
93
  this.log(`Ping ${result.status}${result.error ? `: ${result.error}` : ''}.`);
@@ -104,5 +108,22 @@ class HarnessesPing extends base_command_1.BaseCommand {
104
108
  await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
105
109
  }
106
110
  }
111
+ /** Best-effort: never turns a successful ping into a failure. */
112
+ async logReadiness(harnessId) {
113
+ try {
114
+ const response = await (0, harnesses_1.getHarness)(harnessId);
115
+ const harness = response?.harness ??
116
+ response;
117
+ if (!harness?.daemonId)
118
+ return;
119
+ const readiness = await (0, cli_installers_1.readinessByProvider)(harness.daemonId, this.resolvedCredential);
120
+ const verdict = readiness?.get((0, cli_installers_1.readinessKey)(harness.provider));
121
+ if (verdict)
122
+ this.log(`Readiness: ${verdict.label} — ${verdict.detail}`);
123
+ }
124
+ catch {
125
+ // A readiness read is extra information; the ping already answered.
126
+ }
127
+ }
107
128
  }
108
129
  exports.default = HarnessesPing;
@@ -25,3 +25,25 @@ export default class HarnessesShow extends BaseCommand {
25
25
  * still falls through to the general answer rather than being guessed at.
26
26
  */
27
27
  export declare function describeWrongHarnessRef(ref: string): string;
28
+ type HarnessRefResolution = {
29
+ kind: 'harness';
30
+ id: string;
31
+ } | {
32
+ kind: 'message';
33
+ message: string;
34
+ } | {
35
+ kind: 'none';
36
+ };
37
+ /**
38
+ * Resolve a reference that is not a full harness id against this account's
39
+ * rows: a unique id prefix or a unique name selects the harness; a machine uuid
40
+ * prefix or an ambiguous match explains itself. `none` falls through to the
41
+ * shape-based message.
42
+ */
43
+ export declare function resolveHarnessRef(ref: string): Promise<HarnessRefResolution>;
44
+ export declare function pickHarnessRef(rows: Array<{
45
+ _id: string;
46
+ name?: string;
47
+ machineUuid?: string;
48
+ }>, ref: string): HarnessRefResolution;
49
+ export {};
@@ -1,11 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.describeWrongHarnessRef = describeWrongHarnessRef;
4
+ exports.resolveHarnessRef = resolveHarnessRef;
5
+ exports.pickHarnessRef = pickHarnessRef;
4
6
  const core_1 = require("@oclif/core");
5
7
  const base_command_1 = require("../../base-command");
6
8
  const harness_provider_input_1 = require("../../lib/harness-provider-input");
7
9
  const harnesses_1 = require("../../lib/harnesses");
10
+ const harness_labels_1 = require("../../lib/harness-labels");
11
+ const cli_installers_1 = require("../../lib/cli-installers");
8
12
  const api_fetch_1 = require("../../lib/api-fetch");
13
+ const data_provider_1 = require("@skrr-ai/data-provider");
9
14
  class HarnessesShow extends base_command_1.BaseCommand {
10
15
  static description = 'Show one harness — one provider on one machine';
11
16
  static examples = [
@@ -29,20 +34,30 @@ class HarnessesShow extends base_command_1.BaseCommand {
29
34
  }
30
35
  catch (err) {
31
36
  // The server answers a malformed id with "Invalid harness ID", which says
32
- // nothing about what a valid one IS or where to get one. The sibling
33
- // `machines show` already sets the expectation by accepting what its own
34
- // list prints, so failing here with no pointer is the odd one out.
35
- // The readable text lives in `body`, not `message` — `message` is
36
- // "HTTP 400 Bad Request — GET /api/harnesses/<id>". Any 400 on this
37
- // lookup is the id being wrong, so branch on the status.
38
- if (err instanceof api_fetch_1.ApiFetchError && err.status === 400) {
39
- this.error(`Not a harness id: ${args.harness}\n${describeWrongHarnessRef(args.harness)}`, {
40
- exit: 1,
41
- });
37
+ // nothing about what a valid one IS or where to get one. The readable
38
+ // text lives in `body`, not `message`, and any 400 on this lookup is the
39
+ // id being wrong, so branch on the status.
40
+ if (!(err instanceof api_fetch_1.ApiFetchError && err.status === 400)) {
41
+ this.handleApiError(err);
42
+ return;
43
+ }
44
+ // Before refusing, try what other commands PRINT: the head of a harness
45
+ // id, a harness name, or a machine uuid. `machines show` accepts prefixes
46
+ // and names; this sibling answered the same input with a generic "24
47
+ // hexadecimal characters" (OSK-9313).
48
+ const resolved = await resolveHarnessRef(args.harness);
49
+ if (resolved.kind !== 'harness') {
50
+ const why = resolved.kind === 'message' ? resolved.message : describeWrongHarnessRef(args.harness);
51
+ this.error(`Not a harness id: ${args.harness}\n${why}`, { exit: 1 });
52
+ return;
53
+ }
54
+ try {
55
+ response = await (0, harnesses_1.getHarness)(resolved.id);
56
+ }
57
+ catch (retryErr) {
58
+ this.handleApiError(retryErr);
42
59
  return;
43
60
  }
44
- this.handleApiError(err);
45
- return;
46
61
  }
47
62
  const stored = response?.harness;
48
63
  // Canonical spelling in every output, whichever spelling the row was stored under.
@@ -53,13 +68,30 @@ class HarnessesShow extends base_command_1.BaseCommand {
53
68
  this.error(`No harness ${args.harness}.`, { exit: 1 });
54
69
  return;
55
70
  }
71
+ // `status online` is a heartbeat, not "this harness can run a turn": a
72
+ // Devin that is installed and not signed in read as fully usable (OSK-9263).
73
+ // Asked of the daemon, so only for a live local row; best-effort.
74
+ const readiness = harness.daemonId &&
75
+ harness.harnessLocation === 'local' &&
76
+ harness.status === 'online' &&
77
+ !harness.superseded
78
+ ? await (0, cli_installers_1.readinessByProvider)(harness.daemonId, this.resolvedCredential)
79
+ : null;
80
+ const verdict = readiness?.get((0, cli_installers_1.readinessKey)(harness.provider));
56
81
  if (flags.json) {
57
- this.log(JSON.stringify(harness, null, 2));
82
+ // `authStatus` is the DAEMON's sign-in to skrr, the same on every row of a
83
+ // daemon, so a script read a signed-out Devin as "ok" (OSK-9263/OSK-9265
84
+ // verifiers). `readiness` is the per-provider answer the text prints;
85
+ // null when it was not asked (not a live local row) or not reported.
86
+ this.log(JSON.stringify({ ...harness, readiness: verdict ?? null }, null, 2));
58
87
  return;
59
88
  }
60
- this.log(`${harness.name || harness._id}`);
89
+ // The same rule the list applies (OSK-9649): a no-engine placeholder's
90
+ // stored name is "oversky — <machine>", which headed this view with a
91
+ // provider name that is not one.
92
+ this.log(`${harness.name ? (0, data_provider_1.canonicalizeHarnessLabel)(harness.name) : harness._id}`);
61
93
  this.log(` id ${harness._id}`);
62
- this.log(` provider ${harness.provider}`);
94
+ this.log(` provider ${(0, harness_labels_1.harnessProviderDescription)(harness.provider)}`);
63
95
  // `harnessLocation` is the difference between "your laptop" and "a cloud row",
64
96
  // and it decides which verbs apply: ping and claudeEngine are local-only.
65
97
  // When YOUR alias is covering the shared name, say so and show both. The
@@ -67,11 +99,11 @@ class HarnessesShow extends base_command_1.BaseCommand {
67
99
  // value `harnesses update --name` just wrote appears on no surface at all,
68
100
  // and you cannot tell you are reading your own label rather than the one
69
101
  // everyone else sees.
70
- if (harness.aliased && harness.sharedName && harness.sharedName !== harness.name) {
102
+ if ((0, harness_labels_1.isAliasCoveringSharedName)(harness)) {
71
103
  this.log(` shared name ${harness.sharedName} (your alias is showing instead)`);
72
104
  }
73
105
  this.log(` mode ${harness.harnessLocation ?? 'unknown'}`);
74
- this.log(` status ${harness.status ?? 'unknown'}`);
106
+ this.log(` status ${(0, harness_labels_1.harnessStatusLabel)(harness)}`);
75
107
  // Only for the claude provider. The daemon stamps `claudeEngine` on every
76
108
  // harness it registers, so printing it whenever it is set labels a
77
109
  // first-party harness or `codex` row with how CLAUDE Code would run — a fact about a
@@ -81,6 +113,12 @@ class HarnessesShow extends base_command_1.BaseCommand {
81
113
  }
82
114
  if (harness.daemonId)
83
115
  this.log(` daemon ${harness.daemonId}`);
116
+ if (verdict) {
117
+ this.log(` readiness ${verdict.label} — ${verdict.detail}`);
118
+ }
119
+ else if (readiness) {
120
+ this.log(' readiness not reported — this daemon does not track credentials for this provider');
121
+ }
84
122
  if (harness.lastSeenAt)
85
123
  this.log(` last seen ${harness.lastSeenAt}`);
86
124
  }
@@ -105,6 +143,14 @@ function describeWrongHarnessRef(ref) {
105
143
  if (/^daemon_/.test(ref)) {
106
144
  return `That is a daemon id. \`skrr machines show ${ref}\` takes those. ${seeIds}`;
107
145
  }
146
+ if (/^(dedicated-runtime-|hosted-machine-|hosted-runtime-)/.test(ref)) {
147
+ // Printed in the DAEMON ID column for a rented machine, so it is the same
148
+ // redirect as `daemon_…`, not a malformed harness id (OSK-9313).
149
+ return `That is a daemon id. \`skrr machines show ${ref}\` takes those. ${seeIds}`;
150
+ }
151
+ if (/^sbs_/.test(ref)) {
152
+ return `That is a hosted machine id. \`skrr machines show ${ref}\` takes those. ${seeIds}`;
153
+ }
108
154
  if (/^machine:/.test(ref)) {
109
155
  return `That is a machine id. \`skrr machines show ${ref}\` takes those. ${seeIds}`;
110
156
  }
@@ -118,3 +164,56 @@ function describeWrongHarnessRef(ref) {
118
164
  }
119
165
  return `A harness id is 24 hexadecimal characters. ${seeIds}`;
120
166
  }
167
+ /**
168
+ * Resolve a reference that is not a full harness id against this account's
169
+ * rows: a unique id prefix or a unique name selects the harness; a machine uuid
170
+ * prefix or an ambiguous match explains itself. `none` falls through to the
171
+ * shape-based message.
172
+ */
173
+ async function resolveHarnessRef(ref) {
174
+ if (!ref.trim())
175
+ return { kind: 'none' };
176
+ let rows;
177
+ try {
178
+ const res = await (0, api_fetch_1.apiFetch)('/api/harnesses');
179
+ rows = res.harnesses ?? [];
180
+ }
181
+ catch {
182
+ return { kind: 'none' };
183
+ }
184
+ return pickHarnessRef(rows, ref);
185
+ }
186
+ function pickHarnessRef(rows, ref) {
187
+ const typed = ref.trim();
188
+ const needle = typed.toLowerCase();
189
+ const byId = rows.filter((r) => r._id.toLowerCase().startsWith(needle));
190
+ if (byId.length === 1)
191
+ return { kind: 'harness', id: byId[0]._id };
192
+ const byName = rows.filter((r) => typeof r.name === 'string' &&
193
+ (r.name.toLowerCase() === needle ||
194
+ (0, data_provider_1.canonicalizeHarnessLabel)(r.name).toLowerCase() === needle));
195
+ if (byName.length === 1)
196
+ return { kind: 'harness', id: byName[0]._id };
197
+ const ambiguous = byId.length > 1 ? byId : byName;
198
+ if (ambiguous.length > 1) {
199
+ const shown = ambiguous
200
+ .slice(0, 5)
201
+ .map((r) => `${r._id}${r.name ? ` (${(0, data_provider_1.canonicalizeHarnessLabel)(r.name)})` : ''}`);
202
+ return {
203
+ kind: 'message',
204
+ message: `"${typed}" matches ${ambiguous.length} harnesses: ${shown.join(', ')}${ambiguous.length > shown.length ? `, and ${ambiguous.length - shown.length} more` : ''}. Use a longer prefix or the full id.`,
205
+ };
206
+ }
207
+ const machine = rows.find((r) => {
208
+ const uuid = (r.machineUuid ?? '').toLowerCase();
209
+ const tail = uuid.includes(':') ? uuid.slice(uuid.indexOf(':') + 1) : uuid;
210
+ return uuid.startsWith(needle) || tail.startsWith(needle);
211
+ });
212
+ if (machine) {
213
+ return {
214
+ kind: 'message',
215
+ message: `That is a machine. \`skrr machines show ${typed}\` takes those; its harnesses are listed there.`,
216
+ };
217
+ }
218
+ return { kind: 'none' };
219
+ }
@@ -1,4 +1,5 @@
1
1
  import { BaseCommand } from '../../base-command';
2
+ import { getHarness } from '../../lib/harnesses';
2
3
  /**
3
4
  * Rename an harness, or change how Claude Code runs on it.
4
5
  *
@@ -23,3 +24,10 @@ export default class HarnessesUpdate extends BaseCommand {
23
24
  };
24
25
  run(): Promise<void>;
25
26
  }
27
+ /**
28
+ * `--name` sets the name everyone sees. When YOUR alias covers it, you will not
29
+ * see the new name anywhere you look, and "Updated <name>." read as though the
30
+ * rename had not happened (OSK-10004). Best-effort: a failed re-read keeps the
31
+ * plain receipt rather than failing a write that succeeded.
32
+ */
33
+ export declare function sharedRenameMessage(harnessId: string, name: string, read?: typeof getHarness): Promise<string>;
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sharedRenameMessage = sharedRenameMessage;
3
4
  const core_1 = require("@oclif/core");
4
5
  const base_command_1 = require("../../base-command");
5
6
  const harnesses_1 = require("../../lib/harnesses");
7
+ const harness_labels_1 = require("../../lib/harness-labels");
6
8
  /**
7
9
  * Rename an harness, or change how Claude Code runs on it.
8
10
  *
@@ -53,7 +55,9 @@ class HarnessesUpdate extends base_command_1.BaseCommand {
53
55
  this.log(JSON.stringify(response, null, 2));
54
56
  return;
55
57
  }
56
- this.log(`Updated ${response?.harness?.name || args.harness}.`);
58
+ this.log(flags.name === undefined
59
+ ? `Updated ${response?.harness?.name || args.harness}.`
60
+ : await sharedRenameMessage(args.harness, flags.name));
57
61
  }
58
62
  catch (err) {
59
63
  this.handleApiError(err);
@@ -61,3 +65,23 @@ class HarnessesUpdate extends base_command_1.BaseCommand {
61
65
  }
62
66
  }
63
67
  exports.default = HarnessesUpdate;
68
+ /**
69
+ * `--name` sets the name everyone sees. When YOUR alias covers it, you will not
70
+ * see the new name anywhere you look, and "Updated <name>." read as though the
71
+ * rename had not happened (OSK-10004). Best-effort: a failed re-read keeps the
72
+ * plain receipt rather than failing a write that succeeded.
73
+ */
74
+ async function sharedRenameMessage(harnessId, name, read = harnesses_1.getHarness) {
75
+ let row;
76
+ try {
77
+ row = (await read(harnessId))?.harness;
78
+ }
79
+ catch {
80
+ row = undefined;
81
+ }
82
+ if (row && (0, harness_labels_1.isAliasCoveringSharedName)(row)) {
83
+ return (`Updated the shared name to "${name}" — everyone this harness is shared with sees it.\n` +
84
+ `You still see your alias "${row.name}". \`skrr harnesses alias ${harnessId} --clear\` shows you the shared name too.`);
85
+ }
86
+ return `Updated ${name}.`;
87
+ }
@@ -17,6 +17,8 @@ export type UsageRow = {
17
17
  inputTokens?: number;
18
18
  outputTokens?: number;
19
19
  cachedInputTokens?: number;
20
+ /** Prompt-cache writes; absent from usage recorded before OSK-10006. */
21
+ cacheCreationInputTokens?: number;
20
22
  };
21
23
  /**
22
24
  * The rows to render, whichever spelling the server used.
@@ -42,7 +42,10 @@ function selectUsageRows(payload) {
42
42
  /** The HARNESS cell, under either spelling of the id. */
43
43
  function usageRowHarnessCell(row) {
44
44
  const id = row.harnessId ?? row.runtimeId;
45
- return id ? String(id).slice(0, 12) : '-';
45
+ // The FULL id. Twelve characters looked tidy and named nothing: a daemon's
46
+ // rows share a long prefix, so `6a985ed278b7` matched six harnesses and no
47
+ // command took it back (OSK-10007).
48
+ return id ? String(id) : '-';
46
49
  }
47
50
  const SHARED_COLUMNS = [
48
51
  { key: 'provider', header: 'PROVIDER' },
@@ -51,6 +54,7 @@ const SHARED_COLUMNS = [
51
54
  { key: 'in', header: 'INPUT' },
52
55
  { key: 'out', header: 'OUTPUT' },
53
56
  { key: 'cached', header: 'CACHED' },
57
+ { key: 'cacheWrite', header: 'CACHE WRITE' },
54
58
  ];
55
59
  /**
56
60
  * The rows differ as much as the two envelopes do.
@@ -73,6 +77,7 @@ function buildUsageTable(rows, perHarness) {
73
77
  in: count(r.inputTokens),
74
78
  out: count(r.outputTokens),
75
79
  cached: count(r.cachedInputTokens),
80
+ cacheWrite: count(r.cacheCreationInputTokens),
76
81
  });
77
82
  if (perHarness) {
78
83
  const buckets = [...rows].sort((a, b) => String(a.bucketHour ?? '') < String(b.bucketHour ?? '') ? 1 : -1);
@@ -143,7 +148,7 @@ class HarnessesUsage extends base_command_1.BaseCommand {
143
148
  this.log('');
144
149
  this.log(`Total: ${count(totals.sessions)} sessions · ${usd(totals.totalCostUsd)} · ` +
145
150
  `${count(totals.inputTokens)} in / ${count(totals.outputTokens)} out / ` +
146
- `${count(totals.cachedInputTokens)} cached`);
151
+ `${count(totals.cachedInputTokens)} cached / ${count(totals.cacheCreationInputTokens)} cache write`);
147
152
  // SAY WHICH PROVIDERS THAT TOTAL COVERS.
148
153
  //
149
154
  // It was presented as the account total and is not: the ingestion path is
@@ -93,6 +93,19 @@ class InstructionsStatus extends base_command_1.BaseCommand {
93
93
  // the sentence.
94
94
  { key: 'status', header: 'STATUS', maxWidth: 78 },
95
95
  ], (line) => this.log(line));
96
+ // The re-bind commands the STATUS column points at. Only for a target whose
97
+ // daemon is not live while another daemon on the SAME machine is: that is
98
+ // the case a re-install could never fix (OSK-9961).
99
+ const rebinds = response.targets
100
+ .filter((target) => target.desiredState === 'installed' && target.status === 'pending')
101
+ .map((target) => (0, instruction_input_1.rebindCommandFor)(target, liveDaemonIds))
102
+ .filter((command) => Boolean(command));
103
+ if (rebinds.length > 0) {
104
+ this.log('');
105
+ this.log('Re-bind these targets to the daemon now running on their machine:');
106
+ for (const command of rebinds)
107
+ this.log(` ${command}`);
108
+ }
96
109
  // Where the text went.
97
110
  //
98
111
  // The count in the STATUS column says skrr overwrote something you
@@ -9,6 +9,7 @@ export default class InstructionsUpdateTarget extends BaseCommand {
9
9
  tracking: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
10
10
  pin: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
11
  label: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
+ daemon: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
13
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
13
14
  };
14
15
  run(): Promise<void>;
@@ -4,7 +4,7 @@ const core_1 = require("@oclif/core");
4
4
  const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../base-command");
6
6
  class InstructionsUpdateTarget extends base_command_1.BaseCommand {
7
- static description = 'Update a target prompt or version policy without changing its placement';
7
+ static description = 'Update a target prompt, version policy, label, or the daemon on its machine that syncs it';
8
8
  static args = {
9
9
  target: core_1.Args.string({ description: 'Target ID', required: true, ignoreStdin: true }),
10
10
  };
@@ -13,13 +13,20 @@ class InstructionsUpdateTarget extends base_command_1.BaseCommand {
13
13
  tracking: core_1.Flags.string({ description: 'Version policy', options: ['latest', 'pinned'] }),
14
14
  pin: core_1.Flags.string({ description: 'Pinned version ID (implies pinned tracking)' }),
15
15
  label: core_1.Flags.string({ description: 'New target label' }),
16
+ daemon: core_1.Flags.string({
17
+ description: 'Re-bind the target to this daemon. It must run on the same machine: a target stays with its file.',
18
+ }),
16
19
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
17
20
  };
18
21
  async run() {
19
22
  this.requireAuth();
20
23
  const { args, flags } = await this.parse(InstructionsUpdateTarget);
21
- if (!flags.bundle && !flags.tracking && !flags.pin && flags.label === undefined) {
22
- this.error('Provide --bundle, --tracking, --pin, or --label', { exit: 2 });
24
+ if (!flags.bundle &&
25
+ !flags.tracking &&
26
+ !flags.pin &&
27
+ flags.label === undefined &&
28
+ !flags.daemon) {
29
+ this.error('Provide --bundle, --tracking, --pin, --label, or --daemon', { exit: 2 });
23
30
  }
24
31
  const trackingMode = (flags.pin ? 'pinned' : flags.tracking);
25
32
  let response;
@@ -29,6 +36,7 @@ class InstructionsUpdateTarget extends base_command_1.BaseCommand {
29
36
  trackingMode,
30
37
  pinnedVersionId: flags.pin,
31
38
  label: flags.label,
39
+ daemonId: flags.daemon,
32
40
  });
33
41
  }
34
42
  catch (error) {
@@ -40,8 +40,8 @@ const auth_storage_1 = require("../lib/auth-storage");
40
40
  */
41
41
  class Login extends base_command_1.BaseCommand {
42
42
  static description = 'Sign in to a skrr instance (PKCE localhost callback; device-code fallback). ' +
43
- 'On an interactive first login without a local runtime, skrr offers to install it as a ' +
44
- 'background service. Choose no, or set SKRR_SKIP_DAEMON_HANDOFF=1 before running this ' +
43
+ 'On an interactive first login on a computer without the skrr background service, skrr ' +
44
+ 'offers to install it. Choose no, or set SKRR_SKIP_DAEMON_HANDOFF=1 before running this ' +
45
45
  'command, to skip that optional setup.';
46
46
  static examples = [
47
47
  '<%= config.bin %> login',
@@ -56,7 +56,7 @@ class Login extends base_command_1.BaseCommand {
56
56
  static flags = {
57
57
  env: core_1.Flags.string({
58
58
  description: 'Deployment to sign in to. Shorthand for the matching --base-url, and the same ' +
59
- 'environment names the skrrd runtime CLI accepts.',
59
+ 'environment names `skrrd`, the background service, accepts.',
60
60
  options: Object.keys(config_1.ENVIRONMENT_BASE_URLS),
61
61
  }),
62
62
  'base-url': core_1.Flags.string({
@@ -64,7 +64,7 @@ class Login extends base_command_1.BaseCommand {
64
64
  description: 'API base URL to save and use (e.g. https://skrr.ai)',
65
65
  }),
66
66
  server: core_1.Flags.string({
67
- description: 'Alias for --base-url, matching the skrrd runtime CLI.',
67
+ description: 'Alias for --base-url, matching `skrrd`, the background service.',
68
68
  }),
69
69
  'device-code': core_1.Flags.boolean({
70
70
  description: 'Force the device-code flow (skip the PKCE localhost callback).',
@@ -206,7 +206,7 @@ class Login extends base_command_1.BaseCommand {
206
206
  handoff = await (0, daemonHandoff_1.handOffToLocalDaemon)(process.env, { loginFlow: result.flow });
207
207
  }
208
208
  else if (setup.status === 'failed') {
209
- this.log(`Could not set up the runtime: ${setup.detail}.`);
209
+ this.log(`Could not set up the background service: ${setup.detail}.`);
210
210
  }
211
211
  else {
212
212
  this.log(`This machine is not set up — ${setup.detail}.`);
@@ -23,7 +23,7 @@ class MachinesList extends base_command_1.BaseCommand {
23
23
  // with the one live machine at the top and a wall of dead ones after it
24
24
  // (OSK-7639). A list exists so someone can pick a row.
25
25
  all: core_1.Flags.boolean({
26
- description: 'Include machines that are offline or terminated. Off by default — dead sandboxes ' +
26
+ description: 'Include machines that are offline, paused, stopped or terminated. Off by default — dead sandboxes ' +
27
27
  'accumulate forever and bury the ones you can use.',
28
28
  }),
29
29
  };
@@ -48,7 +48,7 @@ class MachinesList extends base_command_1.BaseCommand {
48
48
  const all = machines;
49
49
  const shown = flags.all ? all : all.filter((m) => (0, machines_1.isReachableMachine)(m));
50
50
  if (shown.length === 0) {
51
- this.log(`No machine is reachable right now. ${all.length} offline/terminated — see them with --all.`);
51
+ this.log(`No machine is reachable right now (${(0, machines_1.hiddenMachinesSummary)(all)}) — see them with --all.`);
52
52
  return;
53
53
  }
54
54
  (0, format_1.renderTable)(shown.map((m) => ({
@@ -67,16 +67,15 @@ class MachinesList extends base_command_1.BaseCommand {
67
67
  // printed "7/23" beside a status of `online`.
68
68
  harnesses: `${m.onlineHarnessCount}/${(0, machines_1.liveHarnessCount)(m)}`,
69
69
  platform: m.platform ? `${m.platform}${m.arch ? `/${m.arch}` : ''}` : '-',
70
- seen: m.lastSeenAt
71
- ? new Date(m.lastSeenAt).toISOString().slice(0, 16).replace('T', ' ')
72
- : '-',
70
+ // Local time with the zone in the header, as `harnesses list` prints it (OSK-10005).
71
+ seen: (0, format_1.localStamp)(m.lastSeenAt),
73
72
  })), [
74
73
  { key: 'id', header: 'ID', verbatim: true },
75
74
  { key: 'name', header: 'NAME', maxWidth: 28 },
76
75
  { key: 'status', header: 'STATUS' },
77
76
  { key: 'harnesses', header: 'ONLINE' },
78
77
  { key: 'platform', header: 'PLATFORM' },
79
- { key: 'seen', header: 'LAST SEEN' },
78
+ { key: 'seen', header: `LAST SEEN (${(0, format_1.localZone)()})` },
80
79
  ], (line) => this.log(line));
81
80
  // Why a machine is not `online`, under the table. `degraded` on its own is
82
81
  // unactionable — the dogfood note on this output was "I still do not know
@@ -89,10 +88,10 @@ class MachinesList extends base_command_1.BaseCommand {
89
88
  this.log(` ${m.preferences?.name || m.name}: ${m.statusReason}`);
90
89
  }
91
90
  }
92
- const hidden = all.length - shown.length;
93
- if (hidden > 0) {
91
+ if (shown.length < all.length) {
92
+ const hidden = all.filter((m) => !shown.includes(m));
94
93
  this.log('');
95
- this.log(`${hidden} offline or terminated machine(s) hidden — pass --all to include them.`);
94
+ this.log(`${hidden.length} machine(s) hidden (${(0, machines_1.hiddenMachinesSummary)(hidden)}) — pass --all to include them.`);
96
95
  }
97
96
  }
98
97
  }
@@ -4,6 +4,8 @@ const core_1 = require("@oclif/core");
4
4
  const base_command_1 = require("../../base-command");
5
5
  const format_1 = require("../../lib/format");
6
6
  const machines_1 = require("../../lib/machines");
7
+ const cli_installers_1 = require("../../lib/cli-installers");
8
+ const harness_labels_1 = require("../../lib/harness-labels");
7
9
  /** One machine: its identity, its harnesses, and what you may do to it. */
8
10
  class MachinesShow extends base_command_1.BaseCommand {
9
11
  static description = 'Show one machine — its identity, harnesses, and available actions';
@@ -49,7 +51,10 @@ class MachinesShow extends base_command_1.BaseCommand {
49
51
  this.log(`${machine.preferences?.name || machine.name}`);
50
52
  this.log(` id ${machine.id}`);
51
53
  this.log(` identity ${machine.identityKind}`);
52
- this.log(` status ${machine.status}`);
54
+ // The label the lists print. A paused lease read `paused` in both tables and
55
+ // `offline` here, with the reason one line below (OSK-9996). `--json` keeps
56
+ // the server's `status`; the lease state rides beside it.
57
+ this.log(` status ${(0, machines_1.machineStateLabel)(machine)}`);
53
58
  // The status alone does not say what to do. Naming the rows that decided it
54
59
  // is the actionable half — bring that provider back, or forget the row.
55
60
  if (machine.statusReason)
@@ -61,7 +66,11 @@ class MachinesShow extends base_command_1.BaseCommand {
61
66
  if (machine.platform) {
62
67
  this.log(` platform ${machine.platform}${machine.arch ? `/${machine.arch}` : ''}`);
63
68
  }
64
- this.log(` last seen ${machine.lastSeenAt ?? '-'}`);
69
+ // Local time, zone named — the same clock `harnesses list` prints. This line
70
+ // printed a raw UTC ISO string while the harness table below printed UTC
71
+ // with no marker, so one row read 15:26 in `harnesses list` and 22:26 here
72
+ // (OSK-10005).
73
+ this.log(` last seen ${machine.lastSeenAt ? `${(0, format_1.localStamp)(machine.lastSeenAt)} ${(0, format_1.localZone)()}` : '-'}`);
65
74
  if (machine.preferences?.description) {
66
75
  this.log(` description ${machine.preferences.description}`);
67
76
  }
@@ -78,6 +87,28 @@ class MachinesShow extends base_command_1.BaseCommand {
78
87
  // header whose count read like a summary of rows it then printed anyway
79
88
  // (OSK-9389). `--all` shows them, and `--json` always carries every row.
80
89
  const shown = flags.all ? harnesses : harnesses.filter((h) => !h.superseded);
90
+ // The daemon's own standing with skrr is ONE fact about the machine, not a
91
+ // per-provider one: it is written across every row of a daemon at once. It
92
+ // sat in a per-row column and answered `ok` for a Devin its CLI called
93
+ // "Not logged in" (OSK-9265), so it is printed once, here.
94
+ const daemonSignIn = harnesses.find((h) => !h.superseded && h.authStatus)?.authStatus;
95
+ if (daemonSignIn)
96
+ this.log(` daemon auth ${daemonSignIn} (the daemon's sign-in to skrr)`);
97
+ // Per-provider readiness — installed, too old, signed in — asked of the
98
+ // daemon itself, so only while it is connected. Best-effort: a daemon that
99
+ // cannot answer leaves the column out rather than failing the command.
100
+ // Keyed by DAEMON as well as provider: one machine can run several daemons
101
+ // (profiles), and each answers only for itself.
102
+ // The same read `harnesses list` makes (readinessForRows): one per daemon,
103
+ // in parallel and bounded.
104
+ const daemonIds = [
105
+ ...new Set(shown
106
+ .filter((h) => h.status === 'online' && !h.superseded && h.daemonId)
107
+ .map((h) => h.daemonId)),
108
+ ];
109
+ const rowReadiness = await (0, cli_installers_1.readinessForRows)(shown, this.resolvedCredential);
110
+ const readinessFor = rowReadiness.lookup;
111
+ const readiness = rowReadiness.answered > 0 ? rowReadiness : null;
81
112
  this.log('');
82
113
  this.log(`Harnesses (${machine.onlineHarnessCount}/${live} online` +
83
114
  (flags.all && superseded > 0 ? `, ${superseded} superseded by newer registrations` : '') +
@@ -85,36 +116,34 @@ class MachinesShow extends base_command_1.BaseCommand {
85
116
  if (shown.length > 0) {
86
117
  (0, format_1.renderTable)(shown.map((h) => ({
87
118
  id: (0, format_1.formatCell)(h.id),
88
- provider: (0, format_1.formatCell)(h.provider),
119
+ // Canonical, and `none` for a no-engine placeholder — the stored
120
+ // `oversky` printed here after the harnesses list stopped (OSK-9649).
121
+ provider: (0, harness_labels_1.harnessProviderLabel)(h.provider),
89
122
  // Liveness only. Whether a newer registration replaced the row is a
90
123
  // different fact — a superseded row can have been online — so it
91
124
  // gets its own column rather than overwriting this one.
92
125
  status: (0, format_1.formatCell)(h.status),
93
126
  registration: h.superseded ? 'superseded' : 'current',
94
- // `authStatus` is the DAEMON's standing with skrr, not this
95
- // provider's standing with its vendor. It is written across every row
96
- // of a daemon at once (`HarnessService.markAuthOkAfterRegister` does
97
- // one `updateMany({owner, daemonId})`), so it is structurally the
98
- // same value for all of them and can never answer "is this harness
99
- // signed in". Under a bare `AUTH` header it read as the second
100
- // question and answered `ok` for a Devin whose own CLI said
101
- // "Not logged in." The header now names what the value is.
102
- auth: (0, format_1.formatCell)(h.authStatus),
103
- seen: h.lastSeenAt
104
- ? new Date(h.lastSeenAt).toISOString().slice(0, 16).replace('T', ' ')
105
- : '-',
127
+ readiness: readinessFor(h)?.label ?? '-',
128
+ seen: (0, format_1.localStamp)(h.lastSeenAt),
106
129
  })), [
107
130
  { key: 'id', header: 'HARNESS ID' },
108
131
  { key: 'provider', header: 'PROVIDER' },
109
132
  { key: 'status', header: 'STATUS' },
110
133
  ...(flags.all ? [{ key: 'registration', header: 'REGISTRATION' }] : []),
111
- { key: 'auth', header: 'DAEMON AUTH' },
112
- { key: 'seen', header: 'LAST SEEN' },
134
+ ...(readiness ? [{ key: 'readiness', header: 'READINESS' }] : []),
135
+ { key: 'seen', header: `LAST SEEN (${(0, format_1.localZone)()})` },
113
136
  ], (line) => this.log(` ${line}`));
114
137
  }
115
138
  if (!flags.all && superseded > 0) {
116
139
  this.log(` ${superseded} superseded registration(s) hidden — pass --all to include them.`);
117
140
  }
141
+ if (readiness && shown.some((h) => !readinessFor(h))) {
142
+ this.log(' READINESS "-": offline, or its daemon does not report credential state for that provider.');
143
+ }
144
+ if (!readiness && daemonIds.length > 0) {
145
+ this.log(` Readiness per provider: skrr harnesses installers --daemon ${daemonIds[0]}`);
146
+ }
118
147
  // Print what is PERMITTED, not the whole control map: a list of six
119
148
  // booleans mostly reading false is noise, and the useful question is
120
149
  // "what can I do to this machine right now".