@skrr-ai/cli 0.1.40 → 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 (114) 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 +12 -10
  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/resolve.js +1 -1
  33. package/dist/commands/followups/show.js +16 -0
  34. package/dist/commands/harnesses/forget.js +20 -3
  35. package/dist/commands/harnesses/install.js +18 -6
  36. package/dist/commands/harnesses/installers.d.ts +1 -1
  37. package/dist/commands/harnesses/installers.js +36 -12
  38. package/dist/commands/harnesses/list.d.ts +74 -2
  39. package/dist/commands/harnesses/list.js +231 -15
  40. package/dist/commands/harnesses/ping.d.ts +2 -0
  41. package/dist/commands/harnesses/ping.js +21 -0
  42. package/dist/commands/harnesses/show.d.ts +22 -0
  43. package/dist/commands/harnesses/show.js +116 -17
  44. package/dist/commands/harnesses/update.d.ts +8 -0
  45. package/dist/commands/harnesses/update.js +25 -1
  46. package/dist/commands/harnesses/usage.d.ts +2 -0
  47. package/dist/commands/harnesses/usage.js +7 -2
  48. package/dist/commands/instructions/status.js +13 -0
  49. package/dist/commands/instructions/update-target.d.ts +1 -0
  50. package/dist/commands/instructions/update-target.js +11 -3
  51. package/dist/commands/login.js +5 -5
  52. package/dist/commands/machines/list.js +8 -9
  53. package/dist/commands/machines/show.js +46 -17
  54. package/dist/commands/messages/show.js +3 -10
  55. package/dist/commands/shares/get.js +2 -9
  56. package/dist/commands/shares/list.js +1 -9
  57. package/dist/commands/shortcuts/show.js +4 -11
  58. package/dist/commands/skills/local.js +6 -0
  59. package/dist/commands/spaces/actions/run.js +3 -4
  60. package/dist/commands/spaces/briefs/get.js +2 -9
  61. package/dist/commands/spaces/briefs/list.js +1 -9
  62. package/dist/commands/spaces/conversations.js +1 -9
  63. package/dist/commands/spaces/heartbeats/list.js +1 -9
  64. package/dist/commands/spaces/heartbeats/status.js +2 -9
  65. package/dist/commands/tasks/activity.js +1 -10
  66. package/dist/commands/tasks/approvals/list.js +1 -9
  67. package/dist/commands/tasks/attachments/list.js +1 -9
  68. package/dist/commands/tasks/comments/list.js +1 -9
  69. package/dist/commands/tasks/dri-status.js +2 -9
  70. package/dist/commands/tasks/events/append.js +9 -7
  71. package/dist/commands/tasks/execution-timeline.js +1 -9
  72. package/dist/commands/tasks/monitor.js +1 -9
  73. package/dist/commands/tasks/result/submit.js +9 -0
  74. package/dist/commands/tasks/runs.js +1 -9
  75. package/dist/commands/tasks/show.js +5 -12
  76. package/dist/commands/token/list.d.ts +8 -0
  77. package/dist/commands/token/list.js +51 -6
  78. package/dist/help.js +8 -12
  79. package/dist/lib/agent-home-workspace.d.ts +19 -0
  80. package/dist/lib/agent-home-workspace.js +39 -0
  81. package/dist/lib/agent-resolver.js +2 -2
  82. package/dist/lib/agentic-stream.d.ts +22 -1
  83. package/dist/lib/agentic-stream.js +48 -7
  84. package/dist/lib/cli-installers.d.ts +84 -2
  85. package/dist/lib/cli-installers.js +146 -13
  86. package/dist/lib/command-miss.d.ts +22 -0
  87. package/dist/lib/command-miss.js +78 -0
  88. package/dist/lib/daemon-setup.js +2 -2
  89. package/dist/lib/daemon-target.d.ts +14 -0
  90. package/dist/lib/daemon-target.js +32 -0
  91. package/dist/lib/daemonBroker.js +1 -1
  92. package/dist/lib/daemonHandoff.js +1 -1
  93. package/dist/lib/exec-runtime-binary.js +2 -2
  94. package/dist/lib/followups.d.ts +10 -0
  95. package/dist/lib/followups.js +14 -1
  96. package/dist/lib/format.d.ts +11 -0
  97. package/dist/lib/format.js +21 -0
  98. package/dist/lib/harness-labels.d.ts +39 -0
  99. package/dist/lib/harness-labels.js +60 -0
  100. package/dist/lib/harnesses.d.ts +6 -0
  101. package/dist/lib/instruction-input.d.ts +13 -0
  102. package/dist/lib/instruction-input.js +20 -1
  103. package/dist/lib/local-skills.d.ts +17 -1
  104. package/dist/lib/local-skills.js +20 -2
  105. package/dist/lib/login.js +5 -5
  106. package/dist/lib/machines.d.ts +6 -0
  107. package/dist/lib/machines.js +68 -2
  108. package/dist/lib/runtime-tool-support.d.ts +34 -0
  109. package/dist/lib/runtime-tool-support.js +77 -0
  110. package/dist/lib/share-rows.d.ts +17 -0
  111. package/dist/lib/share-rows.js +35 -0
  112. package/dist/node_modules/@skrr-ai/data-provider/index.js +5029 -5007
  113. package/oclif.manifest.json +2689 -2584
  114. package/package.json +6 -3
@@ -37,10 +37,10 @@ class Daemon extends core_1.Command {
37
37
  this.log((0, exec_runtime_binary_1.notInstalledMessage)());
38
38
  this.exit(127);
39
39
  }
40
- this.log(`runtime binary: ${bin}`);
40
+ this.log(`background service binary: ${bin}`);
41
41
  this.log('');
42
- this.log('Local runtime:');
43
- this.log(' skrr daemon install — install the runtime as a background service');
42
+ this.log('Background service on this computer:');
43
+ this.log(' skrr daemon install — install it as a background service');
44
44
  this.log(' skrr daemon uninstall — remove that service (local state is left alone)');
45
45
  this.log(' skrr daemon start — start the daemon');
46
46
  this.log(' skrr daemon stop — stop the daemon');
@@ -35,7 +35,7 @@ class DaemonInstall extends core_1.Command {
35
35
  // whole topic, and without a per-command alias only the bare topic
36
36
  // resolved: every subcommand under it failed.
37
37
  static aliases = ['daemons:install'];
38
- static summary = 'Install the local skrr runtime as a background service';
38
+ static summary = 'Install the skrr background service on this computer';
39
39
  static description = (0, exec_runtime_binary_1.forwardedFlagsNote)('install', {
40
40
  binds: true,
41
41
  bootstrapsRuntime: true,
@@ -57,11 +57,11 @@ class DaemonInstall extends core_1.Command {
57
57
  // is a floor under the missing case, not a second update channel competing
58
58
  // with the daemon's own.
59
59
  if (!(0, exec_runtime_binary_1.isCurrentRuntimeBinary)((0, exec_runtime_binary_1.findRuntimeBinary)())) {
60
- this.log('No local skrr runtime found. Fetching the signed release...');
60
+ this.log('No skrr background service installed on this computer. Fetching the signed release...');
61
61
  const outcome = await (0, daemon_installer_1.installDaemon)();
62
62
  if (!outcome.ok) {
63
63
  this.log('');
64
- this.log(` Could not install the runtime: ${outcome.error}`);
64
+ this.log(` Could not install the background service: ${outcome.error}`);
65
65
  this.log('');
66
66
  this.log(' You can install it directly instead:');
67
67
  this.log(' macOS: brew install dush1023/oversky/oversky');
@@ -84,7 +84,7 @@ class DaemonInstall extends core_1.Command {
84
84
  // Reachable only if the runtime vanished between the bootstrap above
85
85
  // and this line. Saying so beats repeating install instructions for
86
86
  // something that was just installed.
87
- this.log('The skrr runtime disappeared between installing it and running it.');
87
+ this.log('The skrr background service disappeared between installing it and running it.');
88
88
  this.exit(127);
89
89
  }
90
90
  throw err;
@@ -28,7 +28,7 @@ const exec_runtime_binary_1 = require("../../lib/exec-runtime-binary");
28
28
  */
29
29
  class DaemonLogin extends core_1.Command {
30
30
  static aliases = ['daemons:login'];
31
- static summary = 'Authenticate the local skrr runtime (separate from `skrr login`)';
31
+ static summary = 'Authenticate the skrr background service on this computer (separate from `skrr login`)';
32
32
  static description = (0, exec_runtime_binary_1.forwardedFlagsNote)('login', { binds: true });
33
33
  static strict = false; // forward all args verbatim — see run()
34
34
  async run() {
@@ -17,6 +17,7 @@ export interface DaemonLogsFlags {
17
17
  lines?: number;
18
18
  stderr?: boolean;
19
19
  path?: boolean;
20
+ profile?: string;
20
21
  }
21
22
  /**
22
23
  * Older daemons already kept logs in this profile-scoped location, even
@@ -50,7 +51,7 @@ export declare function describeLogAge(mtimeMs: number, now?: number): {
50
51
  line: string;
51
52
  stale: boolean;
52
53
  };
53
- export declare function resolveLegacyDaemonLogPath(flags: Pick<DaemonLogsFlags, 'stderr'>, options?: {
54
+ export declare function resolveLegacyDaemonLogPath(flags: Pick<DaemonLogsFlags, 'stderr' | 'profile'>, options?: {
54
55
  home?: string;
55
56
  env?: NodeJS.ProcessEnv;
56
57
  }): string;
@@ -81,6 +82,7 @@ export default class DaemonLogs extends Command {
81
82
  lines: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
82
83
  stderr: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
83
84
  path: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
85
+ profile: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
84
86
  };
85
87
  static examples: string[];
86
88
  run(): Promise<void>;
@@ -64,8 +64,14 @@ function describeLogAge(mtimeMs, now = Date.now()) {
64
64
  function resolveLegacyDaemonLogPath(flags, options = {}) {
65
65
  const home = options.home ?? (0, node_os_1.homedir)();
66
66
  const env = options.env ?? process.env;
67
+ // An explicit --profile outranks the environment, as it does everywhere else.
68
+ const fromFlag = flags.profile?.trim();
67
69
  const fromEnv = env.OVERSKY_PROFILE?.trim();
68
- let profile = fromEnv && PROFILE_NAME.test(fromEnv) ? fromEnv : undefined;
70
+ let profile = fromFlag && PROFILE_NAME.test(fromFlag)
71
+ ? fromFlag
72
+ : fromEnv && PROFILE_NAME.test(fromEnv)
73
+ ? fromEnv
74
+ : undefined;
69
75
  if (!profile) {
70
76
  try {
71
77
  const pinned = (0, node_fs_1.readFileSync)(node_path_1.default.join(home, '.skrr', 'profile.lock'), 'utf8').trim();
@@ -110,6 +116,8 @@ function readDaemonLogTail(filePath, maxLines) {
110
116
  */
111
117
  function buildOverskyLogsArgs(flags) {
112
118
  return [
119
+ // The runtime's own global option, placed before the subcommand.
120
+ ...(flags.profile ? ['--profile', flags.profile] : []),
113
121
  'logs',
114
122
  ...(flags.follow ? ['--follow'] : []),
115
123
  ...(flags.lines !== undefined ? ['--lines', String(flags.lines)] : []),
@@ -149,6 +157,12 @@ class DaemonLogs extends core_1.Command {
149
157
  path: core_1.Flags.boolean({
150
158
  description: 'Print the resolved log file path and exit',
151
159
  }),
160
+ // Every other daemon verb reaches a non-default profile with --profile; this
161
+ // one could only be steered by OVERSKY_PROFILE (OSK-10001).
162
+ profile: core_1.Flags.string({
163
+ char: 'p',
164
+ description: 'Read the logs of this daemon profile instead of the active one',
165
+ }),
152
166
  };
153
167
  static examples = [
154
168
  '<%= config.bin %> daemon logs',
@@ -156,6 +170,7 @@ class DaemonLogs extends core_1.Command {
156
170
  '<%= config.bin %> daemon logs --lines 500',
157
171
  '<%= config.bin %> daemon logs --stderr',
158
172
  '<%= config.bin %> daemon logs --path',
173
+ '<%= config.bin %> daemon logs --profile dogfood-laptop --lines 50',
159
174
  ];
160
175
  async run() {
161
176
  const { flags } = await this.parse(DaemonLogs);
@@ -34,19 +34,19 @@ class DaemonStart extends core_1.Command {
34
34
  const status = await (0, exec_runtime_binary_1.execRuntimeBinaryCapture)((0, runtime_start_verification_1.statusArgsForStart)(argv));
35
35
  const verification = (0, runtime_start_verification_1.verifyRuntimeStart)(status.stdout, status.code, argv);
36
36
  if (verification.status === 'reauth_required') {
37
- this.logToStderr(`Local runtime did not start: profile '${verification.profile}' requires ` +
37
+ this.logToStderr(`The background service did not start: profile '${verification.profile}' requires ` +
38
38
  `re-authentication (reason: ${verification.reason ?? 'UNKNOWN'}). Run ` +
39
39
  `\`${verification.recoveryCommand}\`, then retry \`skrr daemon start\`.`);
40
40
  this.exit(runtime_start_verification_1.DAEMON_START_REAUTH_EXIT);
41
41
  }
42
42
  if (verification.status === 'not_running') {
43
- this.logToStderr(`Local runtime did not remain running after skrrd exited successfully ` +
43
+ this.logToStderr(`The background service did not keep running after skrrd exited successfully ` +
44
44
  `(profile '${verification.profile}', service status: ` +
45
45
  `${verification.runtimeStatus ?? 'unknown'}). Run \`skrr daemon logs\` for the reason.`);
46
46
  this.exit(runtime_start_verification_1.DAEMON_START_UNVERIFIED_EXIT);
47
47
  }
48
48
  if (verification.status === 'unreadable_status') {
49
- this.logToStderr(`skrrd start exited successfully, but skrr could not verify a running local runtime ` +
49
+ this.logToStderr(`skrrd start exited successfully, but skrr could not verify a running background service ` +
50
50
  `for profile '${verification.profile}'; refusing to report success. ` +
51
51
  'Run `skrr daemon status` and `skrr daemon logs` for the reason.');
52
52
  this.exit(runtime_start_verification_1.DAEMON_START_UNVERIFIED_EXIT);
@@ -1,4 +1,13 @@
1
1
  import { Command } from '@oclif/core';
2
+ /**
3
+ * The status report is the runtime's, and it describes the daemon: running,
4
+ * connected, which credential. Whether the coding-agent CLIs on the machine can
5
+ * actually run a turn — installed, signed in — is a different command, and
6
+ * nothing on this surface named it (OSK-10002). One line after a successful
7
+ * human report; never inside `--json`, whose contract is one document, and
8
+ * never after `--help` or a failure, where it would be noise beside the point.
9
+ */
10
+ export declare function readinessPointer(argv: readonly string[], exitCode: number): string | null;
2
11
  export default class DaemonStatus extends Command {
3
12
  static aliases: string[];
4
13
  static summary: string;
@@ -1,7 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readinessPointer = readinessPointer;
3
4
  const core_1 = require("@oclif/core");
4
5
  const exec_runtime_binary_1 = require("../../lib/exec-runtime-binary");
6
+ /**
7
+ * The status report is the runtime's, and it describes the daemon: running,
8
+ * connected, which credential. Whether the coding-agent CLIs on the machine can
9
+ * actually run a turn — installed, signed in — is a different command, and
10
+ * nothing on this surface named it (OSK-10002). One line after a successful
11
+ * human report; never inside `--json`, whose contract is one document, and
12
+ * never after `--help` or a failure, where it would be noise beside the point.
13
+ */
14
+ function readinessPointer(argv, exitCode) {
15
+ if (exitCode !== 0)
16
+ return null;
17
+ if (argv.some((arg) => arg === '--json' || arg === '--help' || arg === '-h'))
18
+ return null;
19
+ // Runnable as printed: `installers` picks the only connected daemon itself,
20
+ // and lists the ids to choose from when there are several (OSK-10002 verifier).
21
+ return (' Coding agents: which CLIs are installed on this machine, and signed in where the daemon can tell — ' +
22
+ '`skrr harnesses installers` (add `--daemon <Daemon ID above>` if several daemons are connected)');
23
+ }
5
24
  class DaemonStatus extends core_1.Command {
6
25
  // `skrr daemons <sub>` — the plural is advertised as an alias for the
7
26
  // whole topic, and without a per-command alias only the bare topic
@@ -23,6 +42,9 @@ class DaemonStatus extends core_1.Command {
23
42
  const argv = this.argv;
24
43
  try {
25
44
  const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(['status', ...argv]);
45
+ const pointer = readinessPointer(argv, code);
46
+ if (pointer)
47
+ this.log(pointer);
26
48
  this.exit(code);
27
49
  }
28
50
  catch (err) {
@@ -87,7 +87,7 @@ class DmMessages extends base_command_1.BaseCommand {
87
87
  return;
88
88
  }
89
89
  (0, format_1.renderTable)(selected.map((message) => ({
90
- when: formatTime(message.createdAt),
90
+ when: (0, format_1.localTime)(message.createdAt),
91
91
  role: String(message.sender ?? (message.isCreatedByUser ? 'user' : 'assistant')),
92
92
  text: textFromMessage(message).replace(/\s+/g, ' ').trim(),
93
93
  id: String(message.messageId ?? message._id ?? message.id ?? '-'),
@@ -100,16 +100,6 @@ class DmMessages extends base_command_1.BaseCommand {
100
100
  }
101
101
  }
102
102
  exports.default = DmMessages;
103
- function formatTime(value) {
104
- if (!value)
105
- return '-';
106
- try {
107
- return new Date(String(value)).toISOString().slice(0, 19);
108
- }
109
- catch {
110
- return String(value);
111
- }
112
- }
113
103
  function textFromMessage(message) {
114
104
  if (typeof message.text === 'string')
115
105
  return message.text;
@@ -44,7 +44,7 @@ class FollowupsCancel extends base_command_1.BaseCommand {
44
44
  this.log(JSON.stringify(result, null, 2));
45
45
  return;
46
46
  }
47
- this.log(`Follow-up ${args.id} cancelled.`);
47
+ this.log((0, followups_1.settledLine)(args.id, result, 'cancelled'));
48
48
  }
49
49
  }
50
50
  exports.default = FollowupsCancel;
@@ -48,7 +48,7 @@ class FollowupsResolve extends base_command_1.BaseCommand {
48
48
  this.log(JSON.stringify(result, null, 2));
49
49
  return;
50
50
  }
51
- this.log(`Follow-up ${args.id} settled: ${flags.outcome}.`);
51
+ this.log((0, followups_1.settledLine)(args.id, result, `settled: ${flags.outcome}`));
52
52
  }
53
53
  }
54
54
  exports.default = FollowupsResolve;
@@ -3,6 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
4
  const base_command_1 = require("../../base-command");
5
5
  const followups_1 = require("../../lib/followups");
6
+ /** What each gate posture means for whoever reads `show`. */
7
+ const GATE_LABEL = {
8
+ exempt_silent: 'fires without approval (silent)',
9
+ exempt_user_directed: 'fires without approval (you asked for it)',
10
+ exempt_system: 'fires without approval (system)',
11
+ user_directed_guarded: 'fires without approval (you asked for it); still held by quiet hours, approval-only action classes and the spend cap',
12
+ autonomy_ladder: "follows the agent's autonomy mode",
13
+ approval_ladder: 'needs approval unless the autonomy mode allows it',
14
+ };
6
15
  /** `skrr followups show <id>` — one follow-up, the same shape as the list rows. */
7
16
  class FollowupsShow extends base_command_1.BaseCommand {
8
17
  static description = 'Show one follow-up';
@@ -34,12 +43,19 @@ class FollowupsShow extends base_command_1.BaseCommand {
34
43
  ['why', row.why],
35
44
  ['intent', row.intentKey],
36
45
  ['producer', row.producer],
46
+ ['gate', row.gate ? (GATE_LABEL[row.gate] ?? row.gate) : undefined],
37
47
  ['terminal', row.terminal ? `${row.terminal} (${row.terminalReason ?? '—'})` : undefined],
38
48
  [
39
49
  'settled',
40
50
  row.terminal ? `${row.terminalAt ?? '—'} by ${row.terminalBy ?? 'unknown'}` : undefined,
41
51
  ],
42
52
  ['deadline', row.deadlineAt],
53
+ [
54
+ 'waiting',
55
+ row.parkedAt && !row.terminal
56
+ ? `on an offline machine since ${row.parkedAt} (escalates after 60 minutes)`
57
+ : undefined,
58
+ ],
43
59
  ['escalated to', row.escalatedInitiativeId],
44
60
  ]) {
45
61
  if (value)
@@ -54,12 +54,29 @@ class HarnessesForget extends base_command_1.BaseCommand {
54
54
  const current = await (0, harnesses_1.getHarness)(args.harness);
55
55
  label = current?.harness?.name || args.harness;
56
56
  }
57
- catch {
58
- /* fall back to the id — the confirmation still has to happen */
57
+ catch (err) {
58
+ // Existence before consent (OSK-9995): asking "Forget harness X?" about
59
+ // a row that does not exist, then reporting NOT_FOUND only after --yes,
60
+ // made the prompt the first thing to fail. Any other read failure still
61
+ // falls back to the id — the confirmation has to happen regardless.
62
+ if (err instanceof api_fetch_1.ApiFetchError && err.status === 404) {
63
+ // NOT_FOUND, the code `harnesses show` gives the same missing id, so a
64
+ // script branching on the code sees one answer (OSK-9995 verifier).
65
+ this.failWithCliError({
66
+ message: `No harness on this account has id ${args.harness}. Run \`skrr harnesses list --all\` to see them.`,
67
+ code: 'NOT_FOUND',
68
+ status: 404,
69
+ exit: 4,
70
+ retryable: false,
71
+ });
72
+ return;
73
+ }
59
74
  }
60
75
  const ok = await this.confirm(`Forget harness "${label}"? Agents pinned to it lose their pin.`);
61
76
  if (!ok) {
62
- this.log('Cancelled.');
77
+ // Non-zero because nothing was forgotten — the same rule as `tasks
78
+ // update`: a script can only read the exit code, and 0 read as success.
79
+ this.error('Cancelled — nothing was forgotten.', { exit: 1 });
63
80
  return;
64
81
  }
65
82
  }
@@ -47,7 +47,7 @@ class HarnessesInstall extends base_command_1.BaseCommand {
47
47
  static flags = {
48
48
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
49
49
  daemon: core_1.Flags.string({
50
- description: 'Which machine to install on. Defaults to your only active daemon; required when several are active.',
50
+ description: 'Which machine to install on. Defaults to your only connected daemon; required when several are connected.',
51
51
  }),
52
52
  };
53
53
  async run() {
@@ -63,7 +63,7 @@ class HarnessesInstall extends base_command_1.BaseCommand {
63
63
  const target = await (0, daemon_target_1.resolveDaemonTarget)({
64
64
  explicit: flags.daemon,
65
65
  local: (0, daemon_target_1.localMachine)(),
66
- listActiveDaemons: () => (0, daemon_target_1.listActiveDaemons)(this.resolvedCredential),
66
+ listActiveDaemons: () => (0, daemon_target_1.listConnectedDaemons)(this.resolvedCredential),
67
67
  });
68
68
  const daemonId = this.daemonIdFor(target);
69
69
  let start;
@@ -152,14 +152,26 @@ class HarnessesInstall extends base_command_1.BaseCommand {
152
152
  this.error('This machine is a Dedicated Runtime whose daemon has not published an id this process can read. Name a machine with --daemon.', { exit: 1 });
153
153
  }
154
154
  if (target.kind === 'none') {
155
- this.error(`No active daemon is registered for this account, so nothing can perform the install. Start the local runtime with \`${this.config.bin} daemon start\`.`, { exit: 1 });
155
+ this.failWithCliError({
156
+ message: `No daemon is connected for this account, so nothing can perform the install. Start the background service on this computer with \`${this.config.bin} daemon start\`.`,
157
+ code: 'NO_DAEMON',
158
+ exit: 1,
159
+ retryable: false,
160
+ });
156
161
  }
157
162
  const choices = target.daemons
158
163
  .slice(0, 10)
159
- .map((d) => ` ${(0, daemon_ref_1.formatDaemonRef)(d.id)} ${d.name || '—'}`)
164
+ // Full ids: they are what `--daemon` takes (OSK-9993).
165
+ .map((d) => ` ${d.id} ${d.name || '—'}`)
160
166
  .join('\n');
161
- this.error(`${target.daemons.length} active daemons — say which machine to install on:\n${choices}\n` +
162
- `Pass one with --daemon, or see them all with \`${this.config.bin} list-daemons\`.`, { exit: 1 });
167
+ // The same refusal, and the same code, as \`code install\` (OSK-9993 verifier).
168
+ this.failWithCliError({
169
+ message: `${target.daemons.length} connected daemons — say which machine to install on:\n${choices}\n` +
170
+ `Pass one with --daemon, or see each daemon id in the DAEMON ID column of \`${this.config.bin} harnesses list\`.`,
171
+ code: 'AMBIGUOUS_DAEMON',
172
+ exit: 1,
173
+ retryable: false,
174
+ });
163
175
  }
164
176
  }
165
177
  exports.default = HarnessesInstall;
@@ -11,7 +11,7 @@ import { BaseCommand } from '../../base-command';
11
11
  *
12
12
  * That gap is not cosmetic, because `skrr harnesses list` answers a DIFFERENT
13
13
  * question and looks like it answers this one. Its `status` is the daemon's
14
- * socket, so on this account `skrr-code` read `online` with no binary installed
14
+ * socket, so on this account the first-party harness read `online` with no binary installed
15
15
  * at all, and `harnesses ping` answered it in 78ms — the daemon was there, the
16
16
  * harness was not. A user could not discover that from inside the product; it
17
17
  * took `command -v` in a shell.
@@ -4,7 +4,6 @@ const core_1 = require("@oclif/core");
4
4
  const base_command_1 = require("../../base-command");
5
5
  const data_provider_1 = require("@skrr-ai/data-provider");
6
6
  const cli_installers_1 = require("../../lib/cli-installers");
7
- const daemon_ref_1 = require("../../lib/daemon-ref");
8
7
  const daemon_target_1 = require("../../lib/daemon-target");
9
8
  const format_1 = require("../../lib/format");
10
9
  /**
@@ -19,7 +18,7 @@ const format_1 = require("../../lib/format");
19
18
  *
20
19
  * That gap is not cosmetic, because `skrr harnesses list` answers a DIFFERENT
21
20
  * question and looks like it answers this one. Its `status` is the daemon's
22
- * socket, so on this account `skrr-code` read `online` with no binary installed
21
+ * socket, so on this account the first-party harness read `online` with no binary installed
23
22
  * at all, and `harnesses ping` answered it in 78ms — the daemon was there, the
24
23
  * harness was not. A user could not discover that from inside the product; it
25
24
  * took `command -v` in a shell.
@@ -44,7 +43,7 @@ class HarnessesInstallers extends base_command_1.BaseCommand {
44
43
  static flags = {
45
44
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
46
45
  daemon: core_1.Flags.string({
47
- description: 'Which machine to ask. Defaults to your only active daemon; required when several are active.',
46
+ description: 'Which machine to ask. Defaults to your only connected daemon; required when several are connected.',
48
47
  }),
49
48
  };
50
49
  async run() {
@@ -53,7 +52,7 @@ class HarnessesInstallers extends base_command_1.BaseCommand {
53
52
  const target = await (0, daemon_target_1.resolveDaemonTarget)({
54
53
  explicit: flags.daemon,
55
54
  local: (0, daemon_target_1.localMachine)(),
56
- listActiveDaemons: () => (0, daemon_target_1.listActiveDaemons)(this.resolvedCredential),
55
+ listActiveDaemons: () => (0, daemon_target_1.listConnectedDaemons)(this.resolvedCredential),
57
56
  });
58
57
  const daemonId = this.daemonIdFor(target);
59
58
  let res;
@@ -65,8 +64,21 @@ class HarnessesInstallers extends base_command_1.BaseCommand {
65
64
  return;
66
65
  }
67
66
  const providers = res.providers ?? [];
67
+ // Computed before the output mode splits: a JSON reader needs the same
68
+ // "this daemon is older than that provider" signal the table prints, and
69
+ // the daemon version the header shows (OSK-9537 verifier).
70
+ const notOffered = providers.length > 0 ? (0, cli_installers_1.providersNotOffered)(providers, data_provider_1.MANAGED_CLI_PROVIDER_IDS) : [];
68
71
  if (flags.json) {
69
- this.log(JSON.stringify({ daemonId, platform: res.platform, providers }, null, 2));
72
+ this.log(JSON.stringify({
73
+ daemonId,
74
+ platform: res.platform,
75
+ daemonVersion: res.daemonVersion ?? null,
76
+ providers,
77
+ notOffered: notOffered.map((provider) => ({
78
+ provider,
79
+ addedInDaemon: cli_installers_1.MANAGED_CLI_PROVIDER_SINCE[(0, cli_installers_1.readinessKey)(provider)] ?? null,
80
+ })),
81
+ }, null, 2));
70
82
  return;
71
83
  }
72
84
  if (providers.length === 0) {
@@ -116,12 +128,12 @@ class HarnessesInstallers extends base_command_1.BaseCommand {
116
128
  }
117
129
  // A provider the platform knows and this daemon did not mention. Without
118
130
  // this it simply is not on the list, which reads as dropped support rather
119
- // than as an older daemon — `devin` arrived in daemon 0.8.43, so 0.8.35
131
+ // than as an older daemon — `devin` arrived in the 0.8.45 daemon release, so 0.8.35
120
132
  // answers with six providers and says nothing about the seventh.
121
- const missing = (0, cli_installers_1.providersNotOffered)(providers, data_provider_1.MANAGED_CLI_PROVIDER_IDS);
133
+ const missing = notOffered;
122
134
  if (missing.length > 0) {
123
135
  this.log('');
124
- this.log(` Not offered by this daemon: ${missing.join(', ')}. ` +
136
+ this.log(` Not offered by this daemon: ${(0, cli_installers_1.describeProvidersNotOffered)(missing)}. ` +
125
137
  'That usually means the daemon is older than the provider — ' +
126
138
  'check `skrr daemon status` for its version and how to update it.');
127
139
  }
@@ -142,14 +154,26 @@ class HarnessesInstallers extends base_command_1.BaseCommand {
142
154
  this.error('This machine is a Dedicated Runtime whose daemon has not published an id this process can read. Name a machine with --daemon.', { exit: 1 });
143
155
  }
144
156
  if (target.kind === 'none') {
145
- this.error(`No active daemon is registered for this account, so nothing can report on it. Start the local runtime with \`${this.config.bin} daemon start\`.`, { exit: 1 });
157
+ this.failWithCliError({
158
+ message: `No daemon is connected for this account, so nothing can report on it. Start the background service on this computer with \`${this.config.bin} daemon start\`.`,
159
+ code: 'NO_DAEMON',
160
+ exit: 1,
161
+ retryable: false,
162
+ });
146
163
  }
147
164
  const choices = target.daemons
148
165
  .slice(0, 10)
149
- .map((d) => ` ${(0, daemon_ref_1.formatDaemonRef)(d.id)} ${d.name || '—'}`)
166
+ // Full ids: they are what `--daemon` takes (OSK-9993).
167
+ .map((d) => ` ${d.id} ${d.name || '—'}`)
150
168
  .join('\n');
151
- this.error(`${target.daemons.length} active daemons — say which machine to report on:\n${choices}\n` +
152
- `Pass one with --daemon, or see them all with \`${this.config.bin} list-daemons\`.`, { exit: 1 });
169
+ // The same refusal, and the same code, as \`code install\` (OSK-9993 verifier).
170
+ this.failWithCliError({
171
+ message: `${target.daemons.length} connected daemons — say which machine to report on:\n${choices}\n` +
172
+ `Pass one with --daemon, or see each daemon id in the DAEMON ID column of \`${this.config.bin} harnesses list\`.`,
173
+ code: 'AMBIGUOUS_DAEMON',
174
+ exit: 1,
175
+ retryable: false,
176
+ });
153
177
  }
154
178
  }
155
179
  exports.default = HarnessesInstallers;
@@ -1,4 +1,5 @@
1
1
  import { BaseCommand } from '../../base-command';
2
+ import { type ProviderReadiness } from '../../lib/cli-installers';
2
3
  /**
3
4
  * Minimal shape of a provider Harness row as returned by
4
5
  * `GET /api/harnesses` (`{ harnesses: WireHarness[] }`). Only the
@@ -71,6 +72,25 @@ export declare function shortMachineLabel(machineUuid?: string): string;
71
72
  export declare function emptyListMessage(input: {
72
73
  provider: string | null;
73
74
  local: boolean;
75
+ /** `--all` was passed, so suggesting it is noise (OSK-9285). */
76
+ all?: boolean;
77
+ /**
78
+ * Every provider this account has a row for, whatever its status, when the
79
+ * caller could read it. Absent or null: unknown, and nothing is claimed.
80
+ */
81
+ accountProviders?: readonly string[] | null;
82
+ }): string;
83
+ /**
84
+ * The NAME cell. `show` and `--json` said when YOUR alias covers the shared
85
+ * name; this table printed the alias as if it were the name everyone sees
86
+ * (OSK-10004). The marker leads because the column truncates from the right,
87
+ * and a trailing one is the first thing a long name would cut.
88
+ */
89
+ export declare const ALIAS_MARKER = "*";
90
+ export declare function harnessNameCell(row: {
91
+ name?: string;
92
+ sharedName?: string;
93
+ aliased?: boolean;
74
94
  }): string;
75
95
  /**
76
96
  * What the DEFAULT column says for one row.
@@ -89,9 +109,33 @@ export type HarnessSelection = {
89
109
  hiddenOffline: number;
90
110
  /** Rows dropped by `--limit` alone. */
91
111
  hiddenByLimit: number;
112
+ /**
113
+ * Superseded rows a status filter dropped — the ones `--status offline`
114
+ * leaves to `--status superseded`. Only superseded rows are counted: online
115
+ * rows the same filter drops were never offline, so they are not "hidden".
116
+ */
117
+ hiddenSuperseded: number;
92
118
  /** Rows that survived every filter, before the online-only default and `--limit`. */
93
119
  matched: number;
94
120
  };
121
+ /**
122
+ * What `--status` accepts: exactly what the STATUS column prints (OSK-10005).
123
+ *
124
+ * The column says `superseded` for a row a later registration on the same
125
+ * machine replaced; the filter accepted only online|offline, so the one value
126
+ * that says "never coming back" could be read and never selected.
127
+ */
128
+ export declare const HARNESS_STATUS_FILTERS: readonly ["online", "offline", "superseded"];
129
+ /**
130
+ * The status to ask the SERVER for. Only `online` narrows there.
131
+ *
132
+ * Supersession is judged among the rows in one response — a row is superseded
133
+ * when a newer row for the same provider on the same machine exists. Asked for
134
+ * `offline` only, the server cannot see the live row that replaced an offline
135
+ * one, so it labelled that row `offline` while `--all` called it `superseded`.
136
+ * Online rows are never superseded, so that one filter is safe to push down.
137
+ */
138
+ export declare function serverStatusParam(status: string | undefined): string | undefined;
95
139
  /**
96
140
  * Harness rows created before `harnessLocation` was introduced do not carry that
97
141
  * field. A daemon id is the durable discriminator for those legacy rows:
@@ -145,6 +189,8 @@ export declare function selectHarnessRows(rows: HarnessRow[], options: {
145
189
  local: boolean;
146
190
  daemon?: string;
147
191
  limit?: number;
192
+ /** Keep only rows whose STATUS column reads this (`harnessStatusLabel`). */
193
+ status?: string;
148
194
  /** Hide offline rows (the human-readable default). */
149
195
  onlineOnly: boolean;
150
196
  }): HarnessSelection;
@@ -177,7 +223,9 @@ export type HarnessDefaults = {
177
223
  workspaceDefaultHarnessId?: string | null;
178
224
  };
179
225
  export type HarnessesJson = {
180
- harnesses: HarnessRow[];
226
+ harnesses: Array<HarnessRow & {
227
+ readiness?: ProviderReadiness | null;
228
+ }>;
181
229
  filters: HarnessesFilters;
182
230
  defaults: HarnessDefaults;
183
231
  counts: {
@@ -189,6 +237,8 @@ export type HarnessesJson = {
189
237
  hiddenByLocal: number;
190
238
  /** Rows `--limit` dropped — raise `--limit` to get them back. */
191
239
  hiddenByLimit: number;
240
+ /** Superseded rows `--status` dropped — `--status superseded` lists them. */
241
+ hiddenSuperseded: number;
192
242
  /**
193
243
  * OSK-3385 — how many of the returned rows are `status: 'online'`.
194
244
  *
@@ -213,7 +263,22 @@ export type HarnessesJson = {
213
263
  * (`compareHarnessRows`), not in server order. Round 1 introduced that sort;
214
264
  * it is deliberate, so `.harnesses[0]` is the row you almost always want.
215
265
  */
216
- export declare function buildHarnessesJson(selection: HarnessSelection, filters: HarnessesFilters, defaults?: HarnessDefaults): HarnessesJson;
266
+ export declare function buildHarnessesJson(selection: HarnessSelection, filters: HarnessesFilters, defaults?: HarnessDefaults, readiness?: (row: HarnessRow) => ProviderReadiness | undefined): HarnessesJson;
267
+ /**
268
+ * What the READINESS column's non-answers mean, counted from the rows shown.
269
+ *
270
+ * Each footnote names a different next step, so they are not merged: a `-` on
271
+ * an offline row wants `--all`/a started daemon, a `-` on a provider the daemon
272
+ * does not manage (hermes, gemini) wants nothing, `unknown` is installed but
273
+ * unverifiable, and only a definite not-ready label (interactive-required,
274
+ * not-installed, …) has something to fix. Counting `unknown` as not ready
275
+ * would send the reader to fix eleven rows that are fine as far as anyone can
276
+ * tell.
277
+ */
278
+ export declare function readinessFootnotes(rows: readonly HarnessRow[], readiness: {
279
+ lookup: (row: HarnessRow) => ProviderReadiness | undefined;
280
+ daemonAnswered: (row: HarnessRow) => boolean;
281
+ }): string[];
217
282
  /**
218
283
  * Rows with the provider in its CANONICAL spelling.
219
284
  *
@@ -246,6 +311,13 @@ export default class HarnessesList extends BaseCommand {
246
311
  daemon: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
247
312
  limit: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
248
313
  local: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
314
+ readiness: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
249
315
  };
250
316
  run(): Promise<void>;
317
+ /**
318
+ * The canonical providers this account has any row for, or null when that
319
+ * cannot be read. Asked only when a --provider filter came back empty, so
320
+ * the ordinary path pays nothing for it.
321
+ */
322
+ private accountProviders;
251
323
  }