@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
@@ -4,6 +4,7 @@ const core_1 = require("@oclif/core");
4
4
  const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const agent_visual_refs_1 = require("../../lib/agent-visual-refs");
6
6
  const create_1 = require("./create");
7
+ const runtime_tool_support_1 = require("../../lib/runtime-tool-support");
7
8
  const base_command_1 = require("../../base-command");
8
9
  const execution_target_1 = require("../../lib/execution-target");
9
10
  const prompt_1 = require("../../lib/prompt");
@@ -51,7 +52,10 @@ class AgentsUpdate extends base_command_1.BaseCommand {
51
52
  description: 'Default Claude permission mode for sessions',
52
53
  options: ['default', 'acceptEdits', 'bypassPermissions', 'plan'],
53
54
  }),
54
- tools: core_1.Flags.string({ description: 'Tools JSON array' }),
55
+ tools: core_1.Flags.string({
56
+ description: 'Tools JSON array of plugin keys, e.g. \'["calculator"]\'. `skrr agents tools` lists the keys, ' +
57
+ 'and which of them an agent turn can call.',
58
+ }),
55
59
  'model-parameters': core_1.Flags.string({ description: 'Model parameters JSON object' }),
56
60
  'agent-ids': core_1.Flags.string({ description: 'Delegated agent IDs JSON array' }),
57
61
  'network-mode': core_1.Flags.string({ description: 'Agent network mode' }),
@@ -186,14 +190,16 @@ class AgentsUpdate extends base_command_1.BaseCommand {
186
190
  this.handleApiError(err);
187
191
  }
188
192
  // The same check `agents create` makes. Without it, a key the server does
189
- // not model — or a typo of one that it does, `max_turns` for `maxTurns` —
190
- // was discarded with the same "Updated agent" line as a real change.
193
+ // not model, a typo of one that it does (`max_turns` for `maxTurns`), or a
194
+ // field only the server sets was discarded with the same "Updated agent"
195
+ // line as a real change.
191
196
  // `removeProjectIds` is an instruction, not a stored field, so it never
192
197
  // comes back and is not reported.
193
198
  const unapplied = (0, create_1.unappliedPayloadKeys)(payload, updated, ['removeProjectIds']);
194
199
  if (unapplied.length > 0) {
195
- this.warn(`The server did not keep ${unapplied.join(', ')} — ${unapplied.length === 1 ? 'that field is' : 'those fields are'} not part of the agent. Check \`skrr agents show <id> --json\` for the field names it stores.`);
200
+ this.warn((0, create_1.unappliedPayloadKeysWarning)(unapplied));
196
201
  }
202
+ await (0, runtime_tool_support_1.warnPluginsNotLoaded)(payload.tools, (message) => this.warn(message));
197
203
  if (flags.json) {
198
204
  this.log(JSON.stringify((0, web_url_1.withAgentUrl)(this.cliConfig.baseURL, updated), null, 2));
199
205
  return;
@@ -38,8 +38,10 @@ export default class CodeInstall extends BaseCommand {
38
38
  *
39
39
  * Refuses rather than guessing when several are active. Picking one would mean
40
40
  * installing a hundred megabytes onto a machine the user was not thinking
41
- * about, and the list this reads is the same one `skrr list-daemons` prints —
42
- * so the error can name the exact values that work.
41
+ * about. The candidates are CONNECTED daemons by the id the installer routes
42
+ * take (`listConnectedDaemons`), so the error names values that work — the
43
+ * Daemon resource list `skrr list-daemons` prints is a different id space
44
+ * (OSK-9993).
43
45
  *
44
46
  * A Dedicated Runtime's own machine is refused here, before any request. The
45
47
  * daemon and the API refuse it too; saying so locally is what keeps the answer
@@ -34,7 +34,7 @@ class CodeInstall extends base_command_1.BaseCommand {
34
34
  static description = `Install the ${auth_core_1.FIRST_PARTY_HARNESS.displayName} engine on this machine, through the daemon`;
35
35
  static examples = [
36
36
  '<%= config.bin %> code install',
37
- '<%= config.bin %> code install --daemon d_6a323ae4...',
37
+ '<%= config.bin %> code install --daemon <daemon-id>',
38
38
  '<%= config.bin %> code install --json',
39
39
  ];
40
40
  static flags = {
@@ -90,7 +90,7 @@ class CodeInstall extends base_command_1.BaseCommand {
90
90
  this.failWithCliError({
91
91
  message: `The daemon on ${(0, daemon_ref_1.formatDaemonRef)(daemonId)} accepted the request and returned no ` +
92
92
  'install job, and does not say why. Nothing has been shown to start. ' +
93
- `Check what it offers with \`${this.config.bin} list-daemons\` for the machine, and ` +
93
+ `Check what it offers with \`${this.config.bin} harnesses installers --daemon ${daemonId}\`, and ` +
94
94
  `\`${this.config.bin} code doctor\` here once you expect it to have landed.`,
95
95
  code: 'INSTALL_NOT_TRACKED',
96
96
  exit: 1,
@@ -132,7 +132,7 @@ class CodeInstall extends base_command_1.BaseCommand {
132
132
  return await (0, daemon_target_1.resolveDaemonTarget)({
133
133
  explicit,
134
134
  local: (0, daemon_target_1.localMachine)({ baseURL: this.cliConfig?.baseURL }),
135
- listActiveDaemons: () => (0, daemon_target_1.listActiveDaemons)(this.resolvedCredential),
135
+ listActiveDaemons: () => (0, daemon_target_1.listConnectedDaemons)(this.resolvedCredential),
136
136
  });
137
137
  }
138
138
  catch (err) {
@@ -144,8 +144,10 @@ class CodeInstall extends base_command_1.BaseCommand {
144
144
  *
145
145
  * Refuses rather than guessing when several are active. Picking one would mean
146
146
  * installing a hundred megabytes onto a machine the user was not thinking
147
- * about, and the list this reads is the same one `skrr list-daemons` prints —
148
- * so the error can name the exact values that work.
147
+ * about. The candidates are CONNECTED daemons by the id the installer routes
148
+ * take (`listConnectedDaemons`), so the error names values that work — the
149
+ * Daemon resource list `skrr list-daemons` prints is a different id space
150
+ * (OSK-9993).
149
151
  *
150
152
  * A Dedicated Runtime's own machine is refused here, before any request. The
151
153
  * daemon and the API refuse it too; saying so locally is what keeps the answer
@@ -169,8 +171,8 @@ class CodeInstall extends base_command_1.BaseCommand {
169
171
  }
170
172
  if (target.kind === 'none') {
171
173
  this.failWithCliError({
172
- message: 'No active daemon is registered for this account, so nothing can perform the install. ' +
173
- `Start the local runtime with \`${this.config.bin} daemon start\`, then run this again.`,
174
+ message: 'No daemon is connected for this account, so nothing can perform the install. ' +
175
+ `Start the background service on this computer with \`${this.config.bin} daemon start\`, then run this again.`,
174
176
  code: 'NO_DAEMON',
175
177
  exit: 1,
176
178
  retryable: false,
@@ -179,11 +181,11 @@ class CodeInstall extends base_command_1.BaseCommand {
179
181
  const { daemons } = target;
180
182
  const choices = daemons
181
183
  .slice(0, 10)
182
- .map((d) => ` ${(0, daemon_ref_1.formatDaemonRef)(d.id)} ${d.name || '—'}`)
184
+ .map((d) => ` ${d.id} ${d.name || '—'}`)
183
185
  .join('\n');
184
186
  this.failWithCliError({
185
- message: `${daemons.length} active daemons — say which machine should install the engine:\n${choices}\n` +
186
- `Pass one with --daemon, or see them all with \`${this.config.bin} list-daemons\`.`,
187
+ message: `${daemons.length} connected daemons — say which machine should install the engine:\n${choices}\n` +
188
+ `Pass one with --daemon, or see each daemon id in the DAEMON ID column of \`${this.config.bin} harnesses list\`.`,
187
189
  code: 'AMBIGUOUS_DAEMON',
188
190
  exit: 1,
189
191
  retryable: false,
@@ -110,9 +110,7 @@ class ConvosList extends base_command_1.BaseCommand {
110
110
  // The owning agent is the field callers most often had to reach for --json
111
111
  // to see (and the one `--agent` filters on), so surface it in the table too.
112
112
  agent: String(c.agent_id ?? c.agentId ?? '-'),
113
- updated: c.updatedAt
114
- ? new Date(String(c.updatedAt)).toISOString().slice(0, 19).replace('T', ' ')
115
- : '-',
113
+ updated: (0, format_1.localTime)(c.updatedAt),
116
114
  }));
117
115
  (0, format_1.renderTable)(rows, [
118
116
  { key: 'id', header: 'ID' },
@@ -5,6 +5,7 @@ const core_1 = require("@oclif/core");
5
5
  const data_provider_1 = require("@skrr-ai/data-provider");
6
6
  const base_command_1 = require("../../base-command");
7
7
  const web_url_1 = require("../../lib/web-url");
8
+ const format_1 = require("../../lib/format");
8
9
  class ConvosShow extends base_command_1.BaseCommand {
9
10
  static description = 'Show details for a single conversation';
10
11
  static examples = [
@@ -38,10 +39,10 @@ class ConvosShow extends base_command_1.BaseCommand {
38
39
  this.log(`Model: ${c.model ?? '-'}`);
39
40
  this.log(`User: ${c.user ?? '-'}`);
40
41
  if (c.createdAt) {
41
- this.log(`Created: ${formatTime(c.createdAt)}`);
42
+ this.log(`Created: ${(0, format_1.localTime)(c.createdAt)}`);
42
43
  }
43
44
  if (c.updatedAt) {
44
- this.log(`Updated: ${formatTime(c.updatedAt)}`);
45
+ this.log(`Updated: ${(0, format_1.localTime)(c.updatedAt)}`);
45
46
  }
46
47
  if (c.agent_id || c.agentId) {
47
48
  this.log(`Agent: ${typeof c.agentName === 'string' && c.agentName ? `${c.agentName} (${c.agent_id ?? c.agentId})` : (c.agent_id ?? c.agentId)}`);
@@ -102,17 +103,9 @@ function formatHandoverParty(entry, baseURL) {
102
103
  if (entry.by)
103
104
  lines.push(` By: ${entry.by}`);
104
105
  if (entry.at)
105
- lines.push(` At: ${formatTime(entry.at)}`);
106
+ lines.push(` At: ${(0, format_1.localTime)(entry.at)}`);
106
107
  // The reason last, because it is the sentence rather than a field.
107
108
  if (entry.reason)
108
109
  lines.push(` Reason: ${entry.reason}`);
109
110
  return lines;
110
111
  }
111
- function formatTime(value) {
112
- try {
113
- return new Date(String(value)).toISOString().slice(0, 19).replace('T', ' ');
114
- }
115
- catch {
116
- return String(value);
117
- }
118
- }
@@ -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;
@@ -24,7 +24,7 @@ const outcomes = (terminal) => Object.entries(terminal)
24
24
  * every agent only with `--all-agents`.
25
25
  */
26
26
  class FollowupsObservability extends base_command_1.BaseCommand {
27
- static description = 'Per-agent, per-kind rollup — created, outcomes, escalation, budget, check-in silence';
27
+ static description = 'Per-agent, per-kind rollup — created, outcomes, escalation, spend, budget, check-in silence';
28
28
  static examples = [
29
29
  '<%= config.bin %> followups observability',
30
30
  '<%= config.bin %> followups observability --agent agent_1',
@@ -93,6 +93,9 @@ class FollowupsObservability extends base_command_1.BaseCommand {
93
93
  outcomes: outcomes(kind.terminal),
94
94
  escalated: rate(kind.escalationRate),
95
95
  fires: String(kind.fires),
96
+ spend: kind.spend
97
+ ? `$${kind.spend.today.toFixed(2)} / $${kind.spend.total.toFixed(2)}`
98
+ : '—',
96
99
  budget: kind.budget ? `${kind.budget.used}/${kind.budget.limit ?? '∞'}` : '—',
97
100
  silence: rate(kind.silenceRate),
98
101
  })), [
@@ -104,11 +107,12 @@ class FollowupsObservability extends base_command_1.BaseCommand {
104
107
  { key: 'outcomes', header: 'OUTCOMES' },
105
108
  { key: 'escalated', header: 'ESCALATED' },
106
109
  { key: 'fires', header: 'FIRES' },
110
+ { key: 'spend', header: 'SPENT TODAY/ALL' },
107
111
  { key: 'budget', header: 'TODAY/LIMIT' },
108
112
  { key: 'silence', header: 'SILENT' },
109
113
  ], (line) => this.log(line));
110
114
  });
111
- this.log(`\nAs of ${report.generatedAt ?? 'now'}. TODAY/LIMIT is the daily budget (reminders are not metered); SILENT is how often a check-in chose to stay quiet.`);
115
+ this.log(`\nAs of ${report.generatedAt ?? 'now'}. SPENT is the agent turns these follow-ups woke (USD, today / all time); TODAY/LIMIT is the daily creation budget (reminders are not metered); SILENT is how often a check-in chose to stay quiet.`);
112
116
  }
113
117
  }
114
118
  exports.default = FollowupsObservability;
@@ -15,11 +15,14 @@ class FollowupsRemind extends base_command_1.BaseCommand {
15
15
  static description = 'Remind the owner at a time — the reminder follow-up';
16
16
  static examples = [
17
17
  '<%= config.bin %> followups remind --subject task:OSK-42 --content "pick the deploy up" --in 2h',
18
- '<%= config.bin %> followups remind --subject conversation:conv_9 --content "ask how the move went" --at 2026-09-20T09:00:00Z',
18
+ '<%= config.bin %> followups remind --subject conversation:conv_9 --content "ask how the move went" --at 2026-09-20T09:00:00Z --active --agent agent_1',
19
19
  '<%= config.bin %> followups remind --subject task:OSK-42 --content "ping me" --at 2026-09-20T09:00:00Z --active --agent agent_1',
20
20
  ];
21
21
  static flags = {
22
- subject: core_1.Flags.string({ description: 'Subject ref: type:externalKey', required: true }),
22
+ subject: core_1.Flags.string({
23
+ description: 'Subject ref: type:externalKey. A passive reminder attaches to a task, project, goal or wiki_page; use --active for a conversation',
24
+ required: true,
25
+ }),
23
26
  content: core_1.Flags.string({ description: "The reminder's own words", required: true }),
24
27
  at: core_1.Flags.string({ description: 'ISO-8601 instant to remind at', exclusive: ['in'] }),
25
28
  in: core_1.Flags.string({
@@ -42,7 +42,8 @@ class FollowupsReschedule extends base_command_1.BaseCommand {
42
42
  this.log(JSON.stringify(result, null, 2));
43
43
  return;
44
44
  }
45
- this.log(`Follow-up ${args.id} rescheduled to ${at}.`);
45
+ const next = result.followup?.nextRunAt;
46
+ this.log(`Follow-up ${args.id} rescheduled to ${typeof next === 'string' ? next : at}.`);
46
47
  }
47
48
  }
48
49
  exports.default = FollowupsReschedule;
@@ -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,8 +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],
48
+ [
49
+ 'settled',
50
+ row.terminal ? `${row.terminalAt ?? '—'} by ${row.terminalBy ?? 'unknown'}` : undefined,
51
+ ],
38
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
+ ],
39
59
  ['escalated to', row.escalatedInitiativeId],
40
60
  ]) {
41
61
  if (value)
@@ -15,6 +15,7 @@ import { BaseCommand } from '../../base-command';
15
15
  * cannot stop is a leak.
16
16
  */
17
17
  export default class FollowupsWatch extends BaseCommand {
18
+ static summary: string;
18
19
  static description: string;
19
20
  static examples: string[];
20
21
  static flags: {
@@ -21,7 +21,11 @@ const triggers_1 = require("../../lib/triggers");
21
21
  * cannot stop is a leak.
22
22
  */
23
23
  class FollowupsWatch extends base_command_1.BaseCommand {
24
- static description = 'Watch an asynchronous tail until it settles — the watch follow-up';
24
+ static summary = 'Watch an asynchronous tail until it settles — the watch follow-up';
25
+ static description = [
26
+ 'Two forms, and they cost different amounts. Prefer the predicate form whenever the state is readable over HTTPS: --url + --until is evaluated by the server, wakes the agent once when the condition settles, and always ends at --deadline.',
27
+ "The recipe form (--check / --success / --abort) wakes the agent for one turn per check, and every turn counts against the agent's watch budget. Use it only for a check a predicate cannot express.",
28
+ ].join('\n\n');
25
29
  static examples = [
26
30
  '<%= config.bin %> followups watch --subject task:OSK-42 --why "deploy still running" --url https://ci.example.com/api/build/9 --until \'{"path":"status","op":"equals","value":"done"}\' --deadline 6h',
27
31
  '<%= config.bin %> followups watch --subject task:OSK-42 --why "pr review" --check "gh pr view 42 --json state" --success "state == MERGED" --abort "state == CLOSED" --wait event --wait-entity github --wait-event pull_request_merged --wait-fallback 2h',
@@ -34,7 +38,7 @@ class FollowupsWatch extends base_command_1.BaseCommand {
34
38
  // predicate form
35
39
  url: core_1.Flags.string({ description: 'HTTPS endpoint to observe (predicate form)' }),
36
40
  until: core_1.Flags.string({
37
- description: 'Predicate tree as JSON (predicate form): a leaf {"path","op","value"} or {"all"|"any": [...]}. op is equals, not_equals, contains, starts_with, in, gt, lt, exists, … — an unknown op is refused with the full list',
41
+ description: 'Predicate tree as JSON (predicate form — the server evaluates it; no agent turn per check): a leaf {"path","op","value"} or {"all"|"any": [...]}. op is equals, not_equals, contains, starts_with, in, gt, lt, exists, … — an unknown op is refused with the full list',
38
42
  }),
39
43
  grant: core_1.Flags.string({ description: 'Grant id supplying the watch credentials' }),
40
44
  deadline: core_1.Flags.string({ description: 'When the watch must stop (predicate form, required)' }),
@@ -43,7 +47,9 @@ class FollowupsWatch extends base_command_1.BaseCommand {
43
47
  'root-path': core_1.Flags.string({ description: 'JSON root path inside the response' }),
44
48
  'resume-prompt': core_1.Flags.string({ description: 'What to tell the settling turn' }),
45
49
  // recipe form
46
- check: core_1.Flags.string({ description: 'The deterministic check to run (recipe form)' }),
50
+ check: core_1.Flags.string({
51
+ description: 'The check the agent runs (recipe form) — every check wakes the agent for one turn, counted against the watch budget',
52
+ }),
47
53
  success: core_1.Flags.string({ description: 'What state means the wait resolved (recipe form)' }),
48
54
  abort: core_1.Flags.string({ description: 'When to stop and escalate (recipe form)' }),
49
55
  wait: core_1.Flags.string({
@@ -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
  }