@skrr-ai/cli 0.1.37 → 0.1.39

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 (177) hide show
  1. package/bin/dev-fallback.js +175 -4
  2. package/dist/base-command.js +46 -2
  3. package/dist/commands/agents/actions/create.js +18 -7
  4. package/dist/commands/agents/actions/delete.d.ts +1 -0
  5. package/dist/commands/agents/actions/delete.js +7 -0
  6. package/dist/commands/agents/actions/update.js +5 -3
  7. package/dist/commands/agents/api-actions/add.js +5 -2
  8. package/dist/commands/agents/api-actions/index.d.ts +11 -0
  9. package/dist/commands/agents/api-actions/index.js +27 -1
  10. package/dist/commands/agents/avatar/default/index.d.ts +11 -0
  11. package/dist/commands/agents/avatar/default/index.js +17 -0
  12. package/dist/commands/agents/avatar/default/set.d.ts +2 -1
  13. package/dist/commands/agents/avatar/default/set.js +32 -7
  14. package/dist/commands/agents/avatar/upload.js +7 -1
  15. package/dist/commands/agents/categories/delete.js +8 -0
  16. package/dist/commands/agents/categories/index.d.ts +21 -5
  17. package/dist/commands/agents/categories/index.js +68 -15
  18. package/dist/commands/agents/chat.d.ts +21 -0
  19. package/dist/commands/agents/chat.js +94 -7
  20. package/dist/commands/agents/create.js +11 -4
  21. package/dist/commands/agents/list.d.ts +15 -0
  22. package/dist/commands/agents/list.js +31 -1
  23. package/dist/commands/agents/scene-background/upload.d.ts +30 -0
  24. package/dist/commands/agents/scene-background/upload.js +97 -0
  25. package/dist/commands/agents/show.js +13 -0
  26. package/dist/commands/agents/tools/calls.d.ts +23 -5
  27. package/dist/commands/agents/tools/calls.js +81 -18
  28. package/dist/commands/agents/update.js +15 -5
  29. package/dist/commands/code/{oversky-agent.d.ts → skrr-agent.d.ts} +3 -3
  30. package/dist/commands/code/{oversky-agent.js → skrr-agent.js} +11 -8
  31. package/dist/commands/convos/messages.d.ts +13 -0
  32. package/dist/commands/convos/messages.js +40 -16
  33. package/dist/commands/daemon/index.js +8 -1
  34. package/dist/commands/daemon/logs.d.ts +26 -0
  35. package/dist/commands/daemon/logs.js +56 -0
  36. package/dist/commands/daemon/uninstall.js +1 -1
  37. package/dist/commands/followups/act.js +3 -2
  38. package/dist/commands/followups/cancel.d.ts +6 -3
  39. package/dist/commands/followups/cancel.js +16 -6
  40. package/dist/commands/followups/observability.d.ts +23 -0
  41. package/dist/commands/followups/observability.js +114 -0
  42. package/dist/commands/followups/reschedule.js +1 -1
  43. package/dist/commands/followups/resolve.js +3 -3
  44. package/dist/commands/followups/show.js +2 -1
  45. package/dist/commands/followups/watch.js +9 -5
  46. package/dist/commands/harnesses/forget.js +28 -0
  47. package/dist/commands/harnesses/install.d.ts +41 -0
  48. package/dist/commands/harnesses/install.js +165 -0
  49. package/dist/commands/harnesses/installers.d.ts +45 -0
  50. package/dist/commands/harnesses/installers.js +155 -0
  51. package/dist/commands/harnesses/list.d.ts +82 -1
  52. package/dist/commands/harnesses/list.js +104 -12
  53. package/dist/commands/harnesses/models.js +10 -1
  54. package/dist/commands/harnesses/show.d.ts +15 -0
  55. package/dist/commands/harnesses/show.js +44 -3
  56. package/dist/commands/harnesses/update.js +1 -1
  57. package/dist/commands/harnesses/usage.d.ts +25 -0
  58. package/dist/commands/harnesses/usage.js +24 -2
  59. package/dist/commands/instructions/status.js +13 -1
  60. package/dist/commands/machines/dedicated/catalog.js +4 -15
  61. package/dist/commands/machines/dedicated/create.d.ts +8 -0
  62. package/dist/commands/machines/dedicated/create.js +63 -2
  63. package/dist/commands/machines/dedicated/destroy.js +19 -5
  64. package/dist/commands/machines/dedicated/exec.js +3 -1
  65. package/dist/commands/machines/dedicated/list.js +9 -6
  66. package/dist/commands/machines/dedicated/price-book.d.ts +17 -0
  67. package/dist/commands/machines/dedicated/price-book.js +35 -0
  68. package/dist/commands/machines/hosted/list.js +12 -0
  69. package/dist/commands/machines/list.js +11 -0
  70. package/dist/commands/machines/show.d.ts +1 -0
  71. package/dist/commands/machines/show.js +40 -11
  72. package/dist/commands/profiles/list.d.ts +23 -0
  73. package/dist/commands/profiles/list.js +96 -0
  74. package/dist/commands/search/skills.d.ts +11 -1
  75. package/dist/commands/search/skills.js +26 -8
  76. package/dist/commands/skills/details.d.ts +2 -0
  77. package/dist/commands/skills/details.js +24 -1
  78. package/dist/commands/skills/import-as-actions.d.ts +32 -0
  79. package/dist/commands/skills/import-as-actions.js +175 -0
  80. package/dist/commands/skills/install.d.ts +2 -0
  81. package/dist/commands/skills/install.js +24 -1
  82. package/dist/commands/skills/invocable.js +6 -2
  83. package/dist/commands/skills/local.d.ts +40 -0
  84. package/dist/commands/skills/local.js +160 -0
  85. package/dist/commands/skills/search.d.ts +11 -1
  86. package/dist/commands/skills/search.js +29 -8
  87. package/dist/commands/spaces/actions/add.js +23 -5
  88. package/dist/commands/spaces/actions/available.js +3 -0
  89. package/dist/commands/spaces/actions/list.d.ts +5 -0
  90. package/dist/commands/spaces/actions/list.js +49 -3
  91. package/dist/commands/spaces/actions/run.d.ts +28 -0
  92. package/dist/commands/spaces/actions/run.js +146 -0
  93. package/dist/commands/spaces/actions/update.js +27 -5
  94. package/dist/commands/spaces/label-summaries.d.ts +27 -0
  95. package/dist/commands/spaces/label-summaries.js +64 -0
  96. package/dist/commands/spaces/task-rollups.d.ts +5 -0
  97. package/dist/commands/spaces/task-rollups.js +15 -4
  98. package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +63 -3
  99. package/dist/commands/tasks/actions/attach.js +32 -7
  100. package/dist/commands/tasks/complete.js +13 -0
  101. package/dist/commands/tasks/create.js +1 -1
  102. package/dist/commands/tasks/events/append.js +29 -6
  103. package/dist/commands/tasks/expectations/assess.d.ts +1 -0
  104. package/dist/commands/tasks/expectations/assess.js +43 -5
  105. package/dist/commands/tasks/expectations.js +77 -16
  106. package/dist/commands/tasks/follow-up.js +2 -2
  107. package/dist/commands/tasks/lifecycle/definitions.js +21 -2
  108. package/dist/commands/tasks/lifecycle/detach.d.ts +7 -0
  109. package/dist/commands/tasks/lifecycle/detach.js +43 -3
  110. package/dist/commands/tasks/lifecycle/migrate.d.ts +2 -0
  111. package/dist/commands/tasks/lifecycle/migrate.js +42 -3
  112. package/dist/commands/tasks/lifecycle/reopen.d.ts +2 -0
  113. package/dist/commands/tasks/lifecycle/reopen.js +40 -3
  114. package/dist/commands/tasks/lifecycle/transition.js +33 -2
  115. package/dist/commands/tasks/list.js +16 -9
  116. package/dist/commands/tasks/move.js +11 -13
  117. package/dist/commands/tasks/review-queue.d.ts +15 -0
  118. package/dist/commands/tasks/review-queue.js +180 -31
  119. package/dist/commands/tasks/show.d.ts +11 -0
  120. package/dist/commands/tasks/show.js +105 -12
  121. package/dist/commands/tasks/trust.js +7 -2
  122. package/dist/commands/tasks/update.js +46 -10
  123. package/dist/commands/tasks/updates/add.js +1 -1
  124. package/dist/commands/token/list.d.ts +22 -0
  125. package/dist/commands/token/list.js +68 -0
  126. package/dist/commands/token/revoke.d.ts +23 -0
  127. package/dist/commands/token/revoke.js +53 -0
  128. package/dist/lib/agent-actions.d.ts +57 -2
  129. package/dist/lib/agent-actions.js +113 -21
  130. package/dist/lib/agent-pin-mute.d.ts +10 -5
  131. package/dist/lib/agent-pin-mute.js +17 -4
  132. package/dist/lib/agent-visual-refs.d.ts +27 -0
  133. package/dist/lib/agent-visual-refs.js +127 -2
  134. package/dist/lib/agentic-stream.d.ts +6 -0
  135. package/dist/lib/agentic-stream.js +34 -3
  136. package/dist/lib/assignee-resolver.d.ts +1 -1
  137. package/dist/lib/assignee-resolver.js +8 -0
  138. package/dist/lib/bulk-task-targets.d.ts +12 -0
  139. package/dist/lib/bulk-task-targets.js +34 -1
  140. package/dist/lib/cli-installers.d.ts +130 -0
  141. package/dist/lib/cli-installers.js +145 -0
  142. package/dist/lib/command-miss.js +55 -7
  143. package/dist/lib/dedicated-lease-command.d.ts +16 -1
  144. package/dist/lib/dedicated-lease-command.js +26 -3
  145. package/dist/lib/dedicated-machines.d.ts +135 -1
  146. package/dist/lib/dedicated-machines.js +307 -3
  147. package/dist/lib/failed-lookup.d.ts +3 -0
  148. package/dist/lib/failed-lookup.js +40 -0
  149. package/dist/lib/first-party-harness-agent.js +9 -2
  150. package/dist/lib/first-party-harness-doctor.d.ts +15 -0
  151. package/dist/lib/first-party-harness-doctor.js +126 -27
  152. package/dist/lib/followups.d.ts +14 -6
  153. package/dist/lib/followups.js +31 -6
  154. package/dist/lib/harnesses.d.ts +8 -0
  155. package/dist/lib/instruction-input.d.ts +19 -1
  156. package/dist/lib/instruction-input.js +52 -1
  157. package/dist/lib/label-scope.d.ts +15 -0
  158. package/dist/lib/label-scope.js +25 -5
  159. package/dist/lib/local-skills.d.ts +98 -0
  160. package/dist/lib/local-skills.js +195 -0
  161. package/dist/lib/machines.d.ts +10 -10
  162. package/dist/lib/machines.js +23 -1
  163. package/dist/lib/search-query.d.ts +19 -0
  164. package/dist/lib/search-query.js +35 -0
  165. package/dist/lib/space-custom-actions.d.ts +72 -0
  166. package/dist/lib/space-custom-actions.js +117 -0
  167. package/dist/lib/space-resolver.d.ts +1 -21
  168. package/dist/lib/space-resolver.js +19 -10
  169. package/dist/lib/task-closure.d.ts +3 -2
  170. package/dist/lib/task-closure.js +18 -20
  171. package/dist/lib/task-lifecycle-output.js +117 -9
  172. package/dist/lib/task-ref-resolver.d.ts +1 -17
  173. package/dist/lib/task-ref-resolver.js +18 -8
  174. package/dist/lib/tasks.js +9 -3
  175. package/dist/node_modules/@skrr-ai/data-provider/index.js +5076 -4947
  176. package/oclif.manifest.json +15505 -14460
  177. package/package.json +9 -3
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
+ const data_provider_1 = require("@skrr-ai/data-provider");
4
5
  const base_command_1 = require("../../base-command");
5
6
  const followups_1 = require("../../lib/followups");
6
7
  const agent_resolver_1 = require("../../lib/agent-resolver");
@@ -12,13 +13,13 @@ const triggers_1 = require("../../lib/triggers");
12
13
  class FollowupsAct extends base_command_1.BaseCommand {
13
14
  static description = 'Run an agreed action at a time — the act follow-up';
14
15
  static examples = [
15
- '<%= config.bin %> followups act --subject task:OSK-42 --why "user approved the merge" --action \'{"builtinType":"task_merge","config":{"taskId":"OSK-42"}}\' --at 2026-09-20T09:00:00Z',
16
+ '<%= config.bin %> followups act --subject task:OSK-42 --why "user approved the merge" --action \'{"builtinType":"task_mark_done","config":{"taskId":"OSK-42"}}\' --at 2026-09-20T09:00:00Z',
16
17
  ];
17
18
  static flags = {
18
19
  subject: core_1.Flags.string({ description: 'Subject ref: type:externalKey', required: true }),
19
20
  why: core_1.Flags.string({ description: 'Why this wake exists, in one line', required: true }),
20
21
  action: core_1.Flags.string({
21
- description: 'Action payload JSON: {"builtinType": "...", "config": {...}, "refId?": "..."}',
22
+ description: `Action payload JSON: {"builtinType": "...", "config": {...}, "refId?": "..."}. builtinType is one of: ${data_provider_1.BUILTIN_ACTION_TYPES.join(', ')}`,
22
23
  required: true,
23
24
  }),
24
25
  at: core_1.Flags.string({ description: 'ISO-8601 instant to act at', exclusive: ['in'] }),
@@ -1,11 +1,14 @@
1
1
  import { BaseCommand } from '../../base-command';
2
2
  /**
3
- * `skrr followups cancel <id>` — abandon a follow-up. If the wait already
4
- * paid off, say so instead (`resolve --outcome resolved`): the audit keeps
5
- * the difference.
3
+ * `skrr followups cancel <id>` — "never mind". It records exactly what
4
+ * `resolve --outcome abandoned` records (terminal `abandoned`, the reason as
5
+ * the note); it exists so calling one off does not require knowing the
6
+ * terminal vocabulary. Neither help text used to say so, and a user had to
7
+ * read the service to choose between them (OSK-9679).
6
8
  */
7
9
  export default class FollowupsCancel extends BaseCommand {
8
10
  static description: string;
11
+ static examples: string[];
9
12
  static args: {
10
13
  id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
11
14
  };
@@ -4,17 +4,27 @@ const core_1 = require("@oclif/core");
4
4
  const base_command_1 = require("../../base-command");
5
5
  const followups_1 = require("../../lib/followups");
6
6
  /**
7
- * `skrr followups cancel <id>` — abandon a follow-up. If the wait already
8
- * paid off, say so instead (`resolve --outcome resolved`): the audit keeps
9
- * the difference.
7
+ * `skrr followups cancel <id>` — "never mind". It records exactly what
8
+ * `resolve --outcome abandoned` records (terminal `abandoned`, the reason as
9
+ * the note); it exists so calling one off does not require knowing the
10
+ * terminal vocabulary. Neither help text used to say so, and a user had to
11
+ * read the service to choose between them (OSK-9679).
10
12
  */
11
13
  class FollowupsCancel extends base_command_1.BaseCommand {
12
- static description = 'Abandon a follow-up';
14
+ static description = 'Call off a follow-up you no longer want (same record as resolve --outcome abandoned)';
15
+ static examples = [
16
+ '<%= config.bin %> followups cancel fu_abc',
17
+ '<%= config.bin %> followups cancel fu_abc --reason "handled it by hand"',
18
+ '# If what you were waiting for happened, record that instead:',
19
+ '<%= config.bin %> followups resolve fu_abc --outcome resolved',
20
+ ];
13
21
  static args = {
14
- id: core_1.Args.string({ description: 'Follow-up (trigger) id', required: true, ignoreStdin: true }),
22
+ id: followups_1.followUpIdArg,
15
23
  };
16
24
  static flags = {
17
- reason: core_1.Flags.string({ description: 'Why it is being cancelled' }),
25
+ reason: core_1.Flags.string({
26
+ description: 'Why it is being called off — stored as the note (default: "cancelled")',
27
+ }),
18
28
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
19
29
  };
20
30
  async run() {
@@ -0,0 +1,23 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * `skrr followups observability` — how follow-ups are doing, per (agent, kind).
4
+ *
5
+ * `list` answers "what is still owed", one row per follow-up. This answers the
6
+ * question a page of rows cannot: how many were created, how they ended, how
7
+ * often they escalated, today's budget, and how often a check-in chose to stay
8
+ * quiet. The API served this rollup from the day the service shipped and no
9
+ * command reached it (OSK-9678).
10
+ *
11
+ * Scope follows `list`: one agent by default (the session's, or `--agent`),
12
+ * every agent only with `--all-agents`.
13
+ */
14
+ export default class FollowupsObservability extends BaseCommand {
15
+ static description: string;
16
+ static examples: string[];
17
+ static flags: {
18
+ agent: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
19
+ 'all-agents': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
20
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
21
+ };
22
+ run(): Promise<void>;
23
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../base-command");
5
+ const format_1 = require("../../lib/format");
6
+ const followups_1 = require("../../lib/followups");
7
+ const agent_resolver_1 = require("../../lib/agent-resolver");
8
+ const rate = (value) => value === null ? '—' : `${Math.round(value * 100)}%`;
9
+ /** `resolved 3 · abandoned 1` — the outcomes that happened, not five zeros. */
10
+ const outcomes = (terminal) => Object.entries(terminal)
11
+ .filter(([, count]) => count > 0)
12
+ .map(([outcome, count]) => `${outcome} ${count}`)
13
+ .join(' · ') || '—';
14
+ /**
15
+ * `skrr followups observability` — how follow-ups are doing, per (agent, kind).
16
+ *
17
+ * `list` answers "what is still owed", one row per follow-up. This answers the
18
+ * question a page of rows cannot: how many were created, how they ended, how
19
+ * often they escalated, today's budget, and how often a check-in chose to stay
20
+ * quiet. The API served this rollup from the day the service shipped and no
21
+ * command reached it (OSK-9678).
22
+ *
23
+ * Scope follows `list`: one agent by default (the session's, or `--agent`),
24
+ * every agent only with `--all-agents`.
25
+ */
26
+ class FollowupsObservability extends base_command_1.BaseCommand {
27
+ static description = 'Per-agent, per-kind rollup — created, outcomes, escalation, budget, check-in silence';
28
+ static examples = [
29
+ '<%= config.bin %> followups observability',
30
+ '<%= config.bin %> followups observability --agent agent_1',
31
+ '<%= config.bin %> followups observability --all-agents --json',
32
+ ];
33
+ static flags = {
34
+ agent: core_1.Flags.string({
35
+ description: 'Agent ID (default: bound session agent)',
36
+ exclusive: ['all-agents'],
37
+ }),
38
+ 'all-agents': core_1.Flags.boolean({
39
+ description: 'Roll up every agent — the human view',
40
+ exclusive: ['agent'],
41
+ default: false,
42
+ }),
43
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
44
+ };
45
+ async run() {
46
+ this.requireAuth();
47
+ const { flags } = await this.parse(FollowupsObservability);
48
+ let agentId;
49
+ if (!flags['all-agents']) {
50
+ try {
51
+ agentId = (0, followups_1.resolveFollowUpAgent)(flags.agent).id;
52
+ }
53
+ catch (err) {
54
+ const failure = (0, agent_resolver_1.agentResolutionFailure)(err);
55
+ this.failWithCliError({
56
+ message: failure.message,
57
+ code: failure.code,
58
+ exit: 2,
59
+ retryable: false,
60
+ details: { suggestion: failure.suggestion, context: failure.details },
61
+ });
62
+ }
63
+ }
64
+ let report;
65
+ try {
66
+ report = await followups_1.followupsApi.observability(agentId ? { agentId } : {});
67
+ }
68
+ catch (err) {
69
+ this.handleApiError(err);
70
+ return;
71
+ }
72
+ if (flags.json) {
73
+ this.log(JSON.stringify(report, null, 2));
74
+ return;
75
+ }
76
+ const agents = (report.agents ?? []).filter((agent) => agent.kinds.length > 0);
77
+ if (agents.length === 0) {
78
+ this.log('No follow-ups.');
79
+ return;
80
+ }
81
+ // One heading per agent rather than an AGENT column: an agent id repeated
82
+ // on every row pushed the table past any normal terminal width.
83
+ agents.forEach((agent, index) => {
84
+ if (index > 0)
85
+ this.log('');
86
+ this.log(agent.agentId === 'user' ? 'user (reminders with no agent)' : agent.agentId);
87
+ (0, format_1.renderTable)(agent.kinds.map((kind) => ({
88
+ kind: kind.kind,
89
+ created: String(kind.created),
90
+ open: String(kind.armed),
91
+ proposed: String(kind.proposed),
92
+ settled: String(kind.settled),
93
+ outcomes: outcomes(kind.terminal),
94
+ escalated: rate(kind.escalationRate),
95
+ fires: String(kind.fires),
96
+ budget: kind.budget ? `${kind.budget.used}/${kind.budget.limit ?? '∞'}` : '—',
97
+ silence: rate(kind.silenceRate),
98
+ })), [
99
+ { key: 'kind', header: 'KIND' },
100
+ { key: 'created', header: 'CREATED' },
101
+ { key: 'open', header: 'OPEN' },
102
+ { key: 'proposed', header: 'PROPOSED' },
103
+ { key: 'settled', header: 'SETTLED' },
104
+ { key: 'outcomes', header: 'OUTCOMES' },
105
+ { key: 'escalated', header: 'ESCALATED' },
106
+ { key: 'fires', header: 'FIRES' },
107
+ { key: 'budget', header: 'TODAY/LIMIT' },
108
+ { key: 'silence', header: 'SILENT' },
109
+ ], (line) => this.log(line));
110
+ });
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.`);
112
+ }
113
+ }
114
+ exports.default = FollowupsObservability;
@@ -11,7 +11,7 @@ class FollowupsReschedule extends base_command_1.BaseCommand {
11
11
  '<%= config.bin %> followups reschedule fu_abc --at 2026-09-20T09:00:00Z',
12
12
  ];
13
13
  static args = {
14
- id: core_1.Args.string({ description: 'Follow-up (trigger) id', required: true, ignoreStdin: true }),
14
+ id: followups_1.followUpIdArg,
15
15
  };
16
16
  static flags = {
17
17
  at: core_1.Flags.string({ description: 'New instant (ISO-8601)', exclusive: ['in'] }),
@@ -13,13 +13,13 @@ const followups_1 = require("../../lib/followups");
13
13
  * are not accepted here.
14
14
  */
15
15
  class FollowupsResolve extends base_command_1.BaseCommand {
16
- static description = 'Settle a follow-up with its honest outcome';
16
+ static description = 'Settle a follow-up with its honest outcome — resolved (it paid off) or abandoned (never mind; `cancel` is the shortcut)';
17
17
  static examples = [
18
18
  '<%= config.bin %> followups resolve fu_abc --outcome resolved --note "the deploy landed"',
19
- '<%= config.bin %> followups resolve fu_abc --outcome abandoned',
19
+ '<%= config.bin %> followups resolve fu_abc --outcome abandoned --note "no longer needed"',
20
20
  ];
21
21
  static args = {
22
- id: core_1.Args.string({ description: 'Follow-up (trigger) id', required: true, ignoreStdin: true }),
22
+ id: followups_1.followUpIdArg,
23
23
  };
24
24
  static flags = {
25
25
  outcome: core_1.Flags.string({
@@ -7,7 +7,7 @@ const followups_1 = require("../../lib/followups");
7
7
  class FollowupsShow extends base_command_1.BaseCommand {
8
8
  static description = 'Show one follow-up';
9
9
  static args = {
10
- id: core_1.Args.string({ description: 'Follow-up (trigger) id', required: true, ignoreStdin: true }),
10
+ id: followups_1.followUpIdArg,
11
11
  };
12
12
  static flags = {
13
13
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
@@ -30,6 +30,7 @@ class FollowupsShow extends base_command_1.BaseCommand {
30
30
  const row = response.followup;
31
31
  this.log((0, followups_1.followupSummaryLine)(row));
32
32
  for (const [label, value] of [
33
+ ['says', row.content],
33
34
  ['why', row.why],
34
35
  ['intent', row.intentKey],
35
36
  ['producer', row.producer],
@@ -23,9 +23,9 @@ const triggers_1 = require("../../lib/triggers");
23
23
  class FollowupsWatch extends base_command_1.BaseCommand {
24
24
  static description = 'Watch an asynchronous tail until it settles — the watch follow-up';
25
25
  static examples = [
26
- '<%= config.bin %> followups watch --subject task:OSK-42 --why "deploy still running" --url https://ci.example.com/api/build/9 --until \'{"field":"status","eq":"done"}\' --deadline 6h',
27
- '<%= 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 --wait-fallback 2h',
28
- '<%= config.bin %> followups watch --subject conv:abc --why "vendor reply" --check "check inbox" --success "reply arrived" --abort "3 days" --wait clock --wait-at 2026-09-20T09:00:00Z',
26
+ '<%= 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
+ '<%= 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',
28
+ '<%= config.bin %> followups watch --subject conversation:abc --why "vendor reply" --check "check inbox" --success "reply arrived" --abort "3 days" --wait clock --wait-at 2026-09-20T09:00:00Z',
29
29
  ];
30
30
  static flags = {
31
31
  subject: core_1.Flags.string({ description: 'Subject ref: type:externalKey', required: true }),
@@ -33,7 +33,9 @@ class FollowupsWatch extends base_command_1.BaseCommand {
33
33
  agent: core_1.Flags.string({ description: 'Agent ID (default: bound session agent)' }),
34
34
  // predicate form
35
35
  url: core_1.Flags.string({ description: 'HTTPS endpoint to observe (predicate form)' }),
36
- until: core_1.Flags.string({ description: 'Predicate tree as JSON (predicate form)' }),
36
+ 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',
38
+ }),
37
39
  grant: core_1.Flags.string({ description: 'Grant id supplying the watch credentials' }),
38
40
  deadline: core_1.Flags.string({ description: 'When the watch must stop (predicate form, required)' }),
39
41
  'check-every': core_1.Flags.string({ description: 'Observation interval (predicate form)' }),
@@ -59,7 +61,9 @@ class FollowupsWatch extends base_command_1.BaseCommand {
59
61
  'wait-filter-value': core_1.Flags.string({ description: 'Event filter value' }),
60
62
  'wait-interval-seconds': core_1.Flags.integer({ description: 'Poll interval for --wait poll_until' }),
61
63
  'wait-max-attempts': core_1.Flags.integer({ description: 'Attempt cap for --wait poll_until' }),
62
- 'wait-fallback': core_1.Flags.string({ description: 'SLA backstop for --wait event/poll_until' }),
64
+ 'wait-fallback': core_1.Flags.string({
65
+ description: 'SLA backstop — required for --wait event (an event may never arrive), optional for poll_until',
66
+ }),
63
67
  // delivery
64
68
  endpoint: core_1.Flags.string({ description: 'Delivery endpoint id (EndpointRegistry)' }),
65
69
  'endpoint-target': core_1.Flags.string({ description: 'Channel target for channel endpoints' }),
@@ -3,6 +3,8 @@ 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 harnesses_1 = require("../../lib/harnesses");
6
+ const api_fetch_1 = require("../../lib/api-fetch");
7
+ const show_1 = require("./show");
6
8
  /**
7
9
  * Forget an harness — `forget`, not `delete`.
8
10
  *
@@ -26,6 +28,24 @@ class HarnessesForget extends base_command_1.BaseCommand {
26
28
  async run() {
27
29
  this.requireAuth();
28
30
  const { args, flags } = await this.parse(HarnessesForget);
31
+ // Refuse a reference that cannot name a harness BEFORE asking anything.
32
+ //
33
+ // The confirmation used to run first, so `harnesses forget notanid` asked
34
+ // whether to delete something that could never exist, and a scripted caller
35
+ // who added `--yes` to get past it then hit a second, different failure:
36
+ // a bare `Invalid harness ID / HTTP_400` from the server. Two round trips
37
+ // for one typo, on the command where a typo matters most.
38
+ //
39
+ // The sibling `harnesses show` already explains this class of mistake
40
+ // (`describeWrongHarnessRef` names which KIND of id you passed and which
41
+ // command takes it). `forget` is the DESTRUCTIVE one and had the worse
42
+ // message; it now shares the explanation.
43
+ if (!/^[0-9a-f]{24}$/i.test(args.harness.trim())) {
44
+ this.error(`Not a harness id: ${args.harness}\n${(0, show_1.describeWrongHarnessRef)(args.harness)}`, {
45
+ exit: 1,
46
+ });
47
+ return;
48
+ }
29
49
  // Name it before removing it. An id alone does not tell the operator which
30
50
  // of their machines they are about to drop from the fleet.
31
51
  if (!flags.yes) {
@@ -52,6 +72,14 @@ class HarnessesForget extends base_command_1.BaseCommand {
52
72
  this.log(`Forgot ${args.harness}.`);
53
73
  }
54
74
  catch (err) {
75
+ // A well-formed id the server still rejects: say so in the same words
76
+ // `show` uses, rather than relaying `Invalid harness ID`.
77
+ if (err instanceof api_fetch_1.ApiFetchError && err.status === 400) {
78
+ this.error(`Not a harness id: ${args.harness}\n${(0, show_1.describeWrongHarnessRef)(args.harness)}`, {
79
+ exit: 1,
80
+ });
81
+ return;
82
+ }
55
83
  this.handleApiError(err);
56
84
  }
57
85
  }
@@ -0,0 +1,41 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * Install one managed coding-agent CLI on a machine.
4
+ *
5
+ * ## Why this did not exist
6
+ *
7
+ * The daemon has installed any of seven providers since `d878b79581`, and
8
+ * `POST /api/daemons/:id/cli-installers/:provider/install` is the request the
9
+ * web app's Install button sends. From the CLI only ONE of them was reachable:
10
+ * `skrr code install`, whose provider is a constant. Everything else had to be
11
+ * installed by hand, or from the web app.
12
+ *
13
+ * `skrr harnesses install-token` made that worse rather than neutral. It is the
14
+ * only entry in `harnesses --help` with "install" in its name, so a user
15
+ * scanning for "how do I install a harness" lands on it — and it mints a daemon
16
+ * enrolment token for another machine, which is a different thing entirely.
17
+ *
18
+ * ## The provider name is validated here, against the shared vocabulary
19
+ *
20
+ * Unlike `harnesses list --provider`, where the set is open because a daemon
21
+ * registers a row per capability it detects, the INSTALLABLE set is closed and
22
+ * owned: `MANAGED_CLI_PROVIDER_IDS` in `@skrr-ai/data-provider`, which the
23
+ * daemon's own list is pinned against by `managedCliProviders.spec.ts`. So a
24
+ * typo can be rejected with the real options, rather than sent to the daemon to
25
+ * come back as an opaque refusal.
26
+ */
27
+ export default class HarnessesInstall extends BaseCommand {
28
+ static description: string;
29
+ static examples: string[];
30
+ static args: {
31
+ provider: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
32
+ };
33
+ static flags: {
34
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
35
+ daemon: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
36
+ };
37
+ run(): Promise<void>;
38
+ /** What the daemon says about this provider right now, best effort. */
39
+ private installerNote;
40
+ private daemonIdFor;
41
+ }
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const data_provider_1 = require("@skrr-ai/data-provider");
5
+ const base_command_1 = require("../../base-command");
6
+ const cli_installers_1 = require("../../lib/cli-installers");
7
+ const daemon_ref_1 = require("../../lib/daemon-ref");
8
+ const daemon_target_1 = require("../../lib/daemon-target");
9
+ /**
10
+ * Install one managed coding-agent CLI on a machine.
11
+ *
12
+ * ## Why this did not exist
13
+ *
14
+ * The daemon has installed any of seven providers since `d878b79581`, and
15
+ * `POST /api/daemons/:id/cli-installers/:provider/install` is the request the
16
+ * web app's Install button sends. From the CLI only ONE of them was reachable:
17
+ * `skrr code install`, whose provider is a constant. Everything else had to be
18
+ * installed by hand, or from the web app.
19
+ *
20
+ * `skrr harnesses install-token` made that worse rather than neutral. It is the
21
+ * only entry in `harnesses --help` with "install" in its name, so a user
22
+ * scanning for "how do I install a harness" lands on it — and it mints a daemon
23
+ * enrolment token for another machine, which is a different thing entirely.
24
+ *
25
+ * ## The provider name is validated here, against the shared vocabulary
26
+ *
27
+ * Unlike `harnesses list --provider`, where the set is open because a daemon
28
+ * registers a row per capability it detects, the INSTALLABLE set is closed and
29
+ * owned: `MANAGED_CLI_PROVIDER_IDS` in `@skrr-ai/data-provider`, which the
30
+ * daemon's own list is pinned against by `managedCliProviders.spec.ts`. So a
31
+ * typo can be rejected with the real options, rather than sent to the daemon to
32
+ * come back as an opaque refusal.
33
+ */
34
+ class HarnessesInstall extends base_command_1.BaseCommand {
35
+ static description = 'Install a managed coding-agent CLI (claude, codex, devin, …) on one of your machines';
36
+ static examples = [
37
+ '<%= config.bin %> harnesses install devin',
38
+ '<%= config.bin %> harnesses install codex --daemon daemon_07c086e3c249_cli_default',
39
+ ];
40
+ static args = {
41
+ provider: core_1.Args.string({
42
+ ignoreStdin: true,
43
+ required: true,
44
+ description: `Which CLI to install (${data_provider_1.MANAGED_CLI_PROVIDER_IDS.join(', ')})`,
45
+ }),
46
+ };
47
+ static flags = {
48
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
49
+ daemon: core_1.Flags.string({
50
+ description: 'Which machine to install on. Defaults to your only active daemon; required when several are active.',
51
+ }),
52
+ };
53
+ async run() {
54
+ this.requireAuth();
55
+ const { args, flags } = await this.parse(HarnessesInstall);
56
+ // Case-insensitive, and every accepted spelling of the first-party harness
57
+ // resolves to its canonical id — the same normalization the route applies.
58
+ const provider = (0, data_provider_1.normalizeManagedCliProvider)(args.provider);
59
+ if (!provider) {
60
+ this.error(`"${args.provider}" is not a managed CLI provider. Installable: ${data_provider_1.MANAGED_CLI_PROVIDER_IDS.join(', ')}.`, { exit: 2 });
61
+ return;
62
+ }
63
+ const target = await (0, daemon_target_1.resolveDaemonTarget)({
64
+ explicit: flags.daemon,
65
+ local: (0, daemon_target_1.localMachine)(),
66
+ listActiveDaemons: () => (0, daemon_target_1.listActiveDaemons)(this.resolvedCredential),
67
+ });
68
+ const daemonId = this.daemonIdFor(target);
69
+ let start;
70
+ try {
71
+ start = await (0, cli_installers_1.startManagedCliInstall)(daemonId, provider, this.resolvedCredential);
72
+ }
73
+ catch (err) {
74
+ this.handleApiError(err);
75
+ return;
76
+ }
77
+ const jobId = start.job?.id;
78
+ if (!start.started || !jobId) {
79
+ // The daemon declined and already knows why. Read its own reason off the
80
+ // provider state rather than inventing an explanation for the silence —
81
+ // "Already installed." and "Not available on this platform." want very
82
+ // different responses, and a generic "install failed" hides both.
83
+ const reason = start.provider?.installDisabledReason?.trim() ||
84
+ (await this.installerNote(daemonId, provider));
85
+ this.log(reason ? `${provider}: ${reason}` : `${provider}: the daemon started no install job.`);
86
+ return;
87
+ }
88
+ if (!flags.json)
89
+ this.logToStderr(`Installing ${provider} on ${(0, daemon_ref_1.formatDaemonRef)(daemonId)}…`);
90
+ const outcome = await (0, cli_installers_1.followManagedCliInstallJob)({
91
+ daemonId,
92
+ jobId,
93
+ credential: this.resolvedCredential,
94
+ onLogLine: flags.json ? undefined : (line) => this.logToStderr(` ${line}`),
95
+ });
96
+ if (outcome.kind === 'missing') {
97
+ this.error(`The daemon no longer has install job ${jobId}.`, { exit: 1 });
98
+ return;
99
+ }
100
+ if (outcome.kind === 'timeout') {
101
+ // Not a failure: the daemon is still working, and calling this failed
102
+ // would send someone to debug a download that is going fine.
103
+ this.error(`Still installing after ${Math.round(cli_installers_1.INSTALL_MAX_WAIT_MS / 60000)} minutes — the daemon is still working on job ${jobId}. Check \`${this.config.bin} harnesses installers\` in a while.`, { exit: 1 });
104
+ return;
105
+ }
106
+ const job = outcome.job;
107
+ if (flags.json) {
108
+ this.log(JSON.stringify({ daemonId, provider, job }, null, 2));
109
+ if (job.status !== 'succeeded')
110
+ process.exitCode = 1;
111
+ return;
112
+ }
113
+ if (job.status === 'failed') {
114
+ this.error(`Installing ${provider} failed: ${job.error || 'the daemon gave no reason.'}`, {
115
+ exit: 1,
116
+ });
117
+ return;
118
+ }
119
+ // Report the state the install actually produced, not "done". Installing a
120
+ // binary is not the same as being able to run it: Devin lands
121
+ // `interactive-required`, and a user told only "installed" would find that
122
+ // out on their first turn instead.
123
+ const state = await this.installerNote(daemonId, provider, true);
124
+ this.log(`${provider} installed.${state ? ` ${state}` : ''}`);
125
+ }
126
+ /** What the daemon says about this provider right now, best effort. */
127
+ async installerNote(daemonId, provider, readiness = false) {
128
+ try {
129
+ const res = await (0, cli_installers_1.listCliInstallers)(daemonId, this.resolvedCredential);
130
+ const row = (res.providers ?? []).find((p) => (0, data_provider_1.normalizeManagedCliProvider)(p.provider) === provider);
131
+ if (!row)
132
+ return null;
133
+ if (readiness) {
134
+ const verdict = (0, cli_installers_1.readinessOf)(row);
135
+ return `${verdict.label}: ${verdict.detail}`;
136
+ }
137
+ const reason = row.installDisabledReason?.trim();
138
+ if (!reason)
139
+ return null;
140
+ return row.version ? `${reason} (${row.version})` : reason;
141
+ }
142
+ catch {
143
+ return null;
144
+ }
145
+ }
146
+ daemonIdFor(target) {
147
+ if (target.kind === 'daemon')
148
+ return target.daemonId;
149
+ if (target.kind === 'this-machine') {
150
+ if (target.daemonId)
151
+ return target.daemonId;
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
+ }
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 });
156
+ }
157
+ const choices = target.daemons
158
+ .slice(0, 10)
159
+ .map((d) => ` ${(0, daemon_ref_1.formatDaemonRef)(d.id)} ${d.name || '—'}`)
160
+ .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 });
163
+ }
164
+ }
165
+ exports.default = HarnessesInstall;
@@ -0,0 +1,45 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * What each managed provider's state is on one machine.
4
+ *
5
+ * ## Why this command exists
6
+ *
7
+ * `d878b79581` replaced "is the binary there" with four states — installed,
8
+ * ready, interactive-required, headless-key-required — and routed them from
9
+ * `cli-installers` through health to the web harness create modal. It reached
10
+ * exactly one surface. From the CLI there was no way to ask.
11
+ *
12
+ * That gap is not cosmetic, because `skrr harnesses list` answers a DIFFERENT
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
15
+ * at all, and `harnesses ping` answered it in 78ms — the daemon was there, the
16
+ * harness was not. A user could not discover that from inside the product; it
17
+ * took `command -v` in a shell.
18
+ *
19
+ * So this prints what the DAEMON says about each provider: whether the binary
20
+ * is present, which one and where, and whether it is signed in. The four states
21
+ * keep their server-side names here rather than being re-prosed, so that a
22
+ * message quoted from the CLI matches the one in the modal.
23
+ *
24
+ * `unknown` is a first-class answer and is not `ready`. A provider whose recipe
25
+ * declares no credential resolver genuinely cannot be assessed from here, and
26
+ * saying so is the point — the failure this whole vocabulary exists to prevent
27
+ * is a surface that reports ready for a harness that will refuse on first use.
28
+ */
29
+ export default class HarnessesInstallers extends BaseCommand {
30
+ static description: string;
31
+ static examples: string[];
32
+ static flags: {
33
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
34
+ daemon: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
35
+ };
36
+ run(): Promise<void>;
37
+ /**
38
+ * Refuse rather than guess which machine to report on.
39
+ *
40
+ * Reading is harmless, but naming the wrong machine is not: the whole value
41
+ * here is "is the thing on MY laptop usable", and an answer about a sandbox
42
+ * the user forgot about is worse than no answer, because it looks like one.
43
+ */
44
+ private daemonIdFor;
45
+ }