@skrr-ai/cli 0.1.36 → 0.1.38

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 (266) hide show
  1. package/README.md +17 -4
  2. package/bin/dev-fallback.js +167 -4
  3. package/dist/base-command.js +46 -2
  4. package/dist/commands/agents/actions/create.d.ts +5 -0
  5. package/dist/commands/agents/actions/create.js +42 -5
  6. package/dist/commands/agents/actions/delete.d.ts +1 -0
  7. package/dist/commands/agents/actions/delete.js +7 -0
  8. package/dist/commands/agents/actions/update.d.ts +5 -0
  9. package/dist/commands/agents/actions/update.js +27 -1
  10. package/dist/commands/agents/api-actions/add.d.ts +41 -0
  11. package/dist/commands/agents/api-actions/add.js +205 -0
  12. package/dist/commands/agents/api-actions/index.d.ts +39 -0
  13. package/dist/commands/agents/api-actions/index.js +103 -0
  14. package/dist/commands/agents/api-actions/remove.d.ts +14 -0
  15. package/dist/commands/agents/api-actions/remove.js +36 -0
  16. package/dist/commands/agents/avatar/default/apply.d.ts +25 -0
  17. package/dist/commands/agents/avatar/default/apply.js +56 -0
  18. package/dist/commands/agents/avatar/default/index.d.ts +29 -0
  19. package/dist/commands/agents/avatar/default/index.js +61 -0
  20. package/dist/commands/agents/avatar/default/set.d.ts +24 -0
  21. package/dist/commands/agents/avatar/default/set.js +120 -0
  22. package/dist/commands/agents/avatar/library/delete.d.ts +20 -0
  23. package/dist/commands/agents/avatar/library/delete.js +47 -0
  24. package/dist/commands/agents/avatar/library/index.d.ts +16 -0
  25. package/dist/commands/agents/avatar/library/index.js +56 -0
  26. package/dist/commands/agents/avatar/presets.d.ts +19 -0
  27. package/dist/commands/agents/avatar/presets.js +56 -0
  28. package/dist/commands/agents/avatar/upload.js +7 -1
  29. package/dist/commands/agents/categories/create.d.ts +14 -0
  30. package/dist/commands/agents/categories/create.js +46 -0
  31. package/dist/commands/agents/categories/delete.d.ts +13 -0
  32. package/dist/commands/agents/categories/delete.js +41 -0
  33. package/dist/commands/agents/categories/index.d.ts +36 -0
  34. package/dist/commands/agents/categories/index.js +135 -0
  35. package/dist/commands/agents/categories/update.d.ts +22 -0
  36. package/dist/commands/agents/categories/update.js +59 -0
  37. package/dist/commands/agents/chat.d.ts +21 -0
  38. package/dist/commands/agents/chat.js +94 -7
  39. package/dist/commands/agents/create.d.ts +10 -0
  40. package/dist/commands/agents/create.js +64 -3
  41. package/dist/commands/agents/ensure-default.d.ts +27 -0
  42. package/dist/commands/agents/ensure-default.js +65 -0
  43. package/dist/commands/agents/list.d.ts +15 -0
  44. package/dist/commands/agents/list.js +31 -1
  45. package/dist/commands/agents/mute.d.ts +16 -0
  46. package/dist/commands/agents/mute.js +38 -0
  47. package/dist/commands/agents/pin.d.ts +16 -0
  48. package/dist/commands/agents/pin.js +38 -0
  49. package/dist/commands/agents/scene-background/upload.d.ts +30 -0
  50. package/dist/commands/agents/scene-background/upload.js +97 -0
  51. package/dist/commands/agents/show.js +13 -0
  52. package/dist/commands/agents/tools/auth.d.ts +19 -0
  53. package/dist/commands/agents/tools/auth.js +41 -0
  54. package/dist/commands/agents/tools/calls.d.ts +36 -0
  55. package/dist/commands/agents/tools/calls.js +117 -0
  56. package/dist/commands/agents/tools/index.d.ts +20 -0
  57. package/dist/commands/agents/tools/index.js +77 -0
  58. package/dist/commands/agents/unmute.d.ts +16 -0
  59. package/dist/commands/agents/unmute.js +38 -0
  60. package/dist/commands/agents/unpin.d.ts +16 -0
  61. package/dist/commands/agents/unpin.js +38 -0
  62. package/dist/commands/agents/update.d.ts +12 -0
  63. package/dist/commands/agents/update.js +74 -3
  64. package/dist/commands/code/{oversky-agent.d.ts → skrr-agent.d.ts} +3 -3
  65. package/dist/commands/code/{oversky-agent.js → skrr-agent.js} +11 -8
  66. package/dist/commands/convos/messages.d.ts +13 -0
  67. package/dist/commands/convos/messages.js +40 -16
  68. package/dist/commands/daemon/index.js +8 -1
  69. package/dist/commands/daemon/logs.d.ts +26 -0
  70. package/dist/commands/daemon/logs.js +56 -0
  71. package/dist/commands/daemon/uninstall.js +1 -1
  72. package/dist/commands/followups/act.d.ts +22 -0
  73. package/dist/commands/followups/act.js +103 -0
  74. package/dist/commands/followups/cancel.d.ts +20 -0
  75. package/dist/commands/followups/cancel.js +50 -0
  76. package/dist/commands/followups/checkin.d.ts +27 -0
  77. package/dist/commands/followups/checkin.js +107 -0
  78. package/dist/commands/followups/list.d.ts +24 -0
  79. package/dist/commands/followups/list.js +90 -0
  80. package/dist/commands/followups/observability.d.ts +23 -0
  81. package/dist/commands/followups/observability.js +114 -0
  82. package/dist/commands/followups/remind.d.ts +26 -0
  83. package/dist/commands/followups/remind.js +106 -0
  84. package/dist/commands/followups/reschedule.d.ts +15 -0
  85. package/dist/commands/followups/reschedule.js +48 -0
  86. package/dist/commands/followups/resolve.d.ts +23 -0
  87. package/dist/commands/followups/resolve.js +54 -0
  88. package/dist/commands/followups/show.d.ts +12 -0
  89. package/dist/commands/followups/show.js +46 -0
  90. package/dist/commands/followups/watch.d.ts +51 -0
  91. package/dist/commands/followups/watch.js +211 -0
  92. package/dist/commands/goals/labels/add.js +19 -2
  93. package/dist/commands/goals/labels/remove.js +22 -2
  94. package/dist/commands/harnesses/forget.js +28 -0
  95. package/dist/commands/harnesses/install.d.ts +41 -0
  96. package/dist/commands/harnesses/install.js +165 -0
  97. package/dist/commands/harnesses/installers.d.ts +45 -0
  98. package/dist/commands/harnesses/installers.js +155 -0
  99. package/dist/commands/harnesses/list.d.ts +82 -1
  100. package/dist/commands/harnesses/list.js +104 -12
  101. package/dist/commands/harnesses/models.js +10 -1
  102. package/dist/commands/harnesses/show.d.ts +15 -0
  103. package/dist/commands/harnesses/show.js +44 -3
  104. package/dist/commands/harnesses/update.js +1 -1
  105. package/dist/commands/harnesses/usage.d.ts +25 -0
  106. package/dist/commands/harnesses/usage.js +24 -2
  107. package/dist/commands/instructions/status.js +13 -1
  108. package/dist/commands/labels/list.d.ts +24 -1
  109. package/dist/commands/labels/list.js +62 -19
  110. package/dist/commands/machines/dedicated/catalog.js +4 -15
  111. package/dist/commands/machines/dedicated/create.d.ts +8 -0
  112. package/dist/commands/machines/dedicated/create.js +58 -1
  113. package/dist/commands/machines/dedicated/destroy.js +18 -4
  114. package/dist/commands/machines/dedicated/list.js +9 -6
  115. package/dist/commands/machines/hosted/list.js +12 -0
  116. package/dist/commands/machines/list.js +11 -0
  117. package/dist/commands/machines/show.d.ts +1 -0
  118. package/dist/commands/machines/show.js +40 -11
  119. package/dist/commands/memory/governance/index.d.ts +21 -0
  120. package/dist/commands/memory/governance/index.js +64 -0
  121. package/dist/commands/memory/governance/set.d.ts +24 -0
  122. package/dist/commands/memory/governance/set.js +95 -0
  123. package/dist/commands/memory/provenance.d.ts +18 -0
  124. package/dist/commands/memory/provenance.js +66 -0
  125. package/dist/commands/profiles/list.d.ts +23 -0
  126. package/dist/commands/profiles/list.js +96 -0
  127. package/dist/commands/search/skills.d.ts +11 -1
  128. package/dist/commands/search/skills.js +26 -8
  129. package/dist/commands/skills/details.d.ts +2 -0
  130. package/dist/commands/skills/details.js +24 -1
  131. package/dist/commands/skills/import-as-actions.d.ts +32 -0
  132. package/dist/commands/skills/import-as-actions.js +175 -0
  133. package/dist/commands/skills/install.d.ts +2 -0
  134. package/dist/commands/skills/install.js +24 -1
  135. package/dist/commands/skills/invocable.js +6 -2
  136. package/dist/commands/skills/local.d.ts +40 -0
  137. package/dist/commands/skills/local.js +160 -0
  138. package/dist/commands/skills/search.d.ts +11 -1
  139. package/dist/commands/skills/search.js +29 -8
  140. package/dist/commands/spaces/actions/add.js +23 -5
  141. package/dist/commands/spaces/actions/available.js +3 -0
  142. package/dist/commands/spaces/actions/list.d.ts +5 -0
  143. package/dist/commands/spaces/actions/list.js +49 -3
  144. package/dist/commands/spaces/actions/run.d.ts +28 -0
  145. package/dist/commands/spaces/actions/run.js +146 -0
  146. package/dist/commands/spaces/actions/update.js +27 -5
  147. package/dist/commands/spaces/create.d.ts +3 -0
  148. package/dist/commands/spaces/create.js +32 -2
  149. package/dist/commands/spaces/label-summaries.d.ts +27 -0
  150. package/dist/commands/spaces/label-summaries.js +64 -0
  151. package/dist/commands/spaces/labels/add.js +19 -2
  152. package/dist/commands/spaces/labels/remove.js +22 -2
  153. package/dist/commands/spaces/show.js +2 -0
  154. package/dist/commands/spaces/task-rollups.d.ts +5 -0
  155. package/dist/commands/spaces/task-rollups.js +15 -4
  156. package/dist/commands/spaces/update.d.ts +5 -0
  157. package/dist/commands/spaces/update.js +41 -4
  158. package/dist/commands/spaces/workflows/lifecycle-diagnostics.d.ts +11 -0
  159. package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +105 -0
  160. package/dist/commands/tasks/actions/attach.js +32 -7
  161. package/dist/commands/tasks/complete.d.ts +1 -0
  162. package/dist/commands/tasks/complete.js +21 -1
  163. package/dist/commands/tasks/create.d.ts +3 -0
  164. package/dist/commands/tasks/create.js +34 -2
  165. package/dist/commands/tasks/events/append.js +29 -6
  166. package/dist/commands/tasks/expectations/assess.d.ts +12 -0
  167. package/dist/commands/tasks/expectations/assess.js +123 -14
  168. package/dist/commands/tasks/expectations.js +95 -17
  169. package/dist/commands/tasks/follow-up.js +2 -2
  170. package/dist/commands/tasks/labels/attach.js +20 -3
  171. package/dist/commands/tasks/labels/detach.js +21 -3
  172. package/dist/commands/tasks/lifecycle/attach.d.ts +14 -0
  173. package/dist/commands/tasks/lifecycle/attach.js +47 -0
  174. package/dist/commands/tasks/lifecycle/definitions.d.ts +11 -0
  175. package/dist/commands/tasks/lifecycle/definitions.js +59 -0
  176. package/dist/commands/tasks/lifecycle/detach.d.ts +19 -0
  177. package/dist/commands/tasks/lifecycle/detach.js +72 -0
  178. package/dist/commands/tasks/lifecycle/history.d.ts +13 -0
  179. package/dist/commands/tasks/lifecycle/history.js +38 -0
  180. package/dist/commands/tasks/lifecycle/migrate.d.ts +14 -0
  181. package/dist/commands/tasks/lifecycle/migrate.js +68 -0
  182. package/dist/commands/tasks/lifecycle/reconcile.d.ts +11 -0
  183. package/dist/commands/tasks/lifecycle/reconcile.js +27 -0
  184. package/dist/commands/tasks/lifecycle/reopen.d.ts +15 -0
  185. package/dist/commands/tasks/lifecycle/reopen.js +71 -0
  186. package/dist/commands/tasks/lifecycle/show.d.ts +11 -0
  187. package/dist/commands/tasks/lifecycle/show.js +27 -0
  188. package/dist/commands/tasks/lifecycle/start.d.ts +12 -0
  189. package/dist/commands/tasks/lifecycle/start.js +30 -0
  190. package/dist/commands/tasks/lifecycle/transition.d.ts +15 -0
  191. package/dist/commands/tasks/lifecycle/transition.js +109 -0
  192. package/dist/commands/tasks/list.js +16 -9
  193. package/dist/commands/tasks/move.d.ts +1 -0
  194. package/dist/commands/tasks/move.js +29 -0
  195. package/dist/commands/tasks/result/submit.js +11 -1
  196. package/dist/commands/tasks/review-queue.d.ts +44 -0
  197. package/dist/commands/tasks/review-queue.js +314 -0
  198. package/dist/commands/tasks/show.d.ts +11 -0
  199. package/dist/commands/tasks/show.js +139 -4
  200. package/dist/commands/tasks/trust.js +7 -2
  201. package/dist/commands/tasks/update.d.ts +1 -0
  202. package/dist/commands/tasks/update.js +83 -0
  203. package/dist/commands/tasks/updates/add.js +1 -1
  204. package/dist/commands/token/list.d.ts +22 -0
  205. package/dist/commands/token/list.js +68 -0
  206. package/dist/commands/token/revoke.d.ts +23 -0
  207. package/dist/commands/token/revoke.js +53 -0
  208. package/dist/lib/agent-actions.d.ts +92 -11
  209. package/dist/lib/agent-actions.js +171 -25
  210. package/dist/lib/agent-config.d.ts +11 -0
  211. package/dist/lib/agent-config.js +85 -3
  212. package/dist/lib/agent-pin-mute.d.ts +29 -0
  213. package/dist/lib/agent-pin-mute.js +30 -0
  214. package/dist/lib/agent-visual-refs.d.ts +33 -0
  215. package/dist/lib/agent-visual-refs.js +267 -0
  216. package/dist/lib/agentic-stream.d.ts +6 -0
  217. package/dist/lib/agentic-stream.js +34 -3
  218. package/dist/lib/assignee-resolver.d.ts +1 -1
  219. package/dist/lib/assignee-resolver.js +8 -0
  220. package/dist/lib/bulk-task-targets.d.ts +12 -0
  221. package/dist/lib/bulk-task-targets.js +34 -1
  222. package/dist/lib/cli-installers.d.ts +130 -0
  223. package/dist/lib/cli-installers.js +145 -0
  224. package/dist/lib/command-miss.js +55 -7
  225. package/dist/lib/dedicated-lease-command.d.ts +13 -0
  226. package/dist/lib/dedicated-lease-command.js +22 -1
  227. package/dist/lib/dedicated-machines.d.ts +95 -1
  228. package/dist/lib/dedicated-machines.js +236 -2
  229. package/dist/lib/failed-lookup.d.ts +3 -0
  230. package/dist/lib/failed-lookup.js +40 -0
  231. package/dist/lib/first-party-harness-agent.js +16 -2
  232. package/dist/lib/first-party-harness-doctor.d.ts +15 -0
  233. package/dist/lib/first-party-harness-doctor.js +126 -27
  234. package/dist/lib/followups.d.ts +115 -0
  235. package/dist/lib/followups.js +127 -0
  236. package/dist/lib/harnesses.d.ts +8 -0
  237. package/dist/lib/instruction-input.d.ts +19 -1
  238. package/dist/lib/instruction-input.js +52 -1
  239. package/dist/lib/label-ref.d.ts +95 -0
  240. package/dist/lib/label-ref.js +163 -0
  241. package/dist/lib/label-scope.d.ts +17 -2
  242. package/dist/lib/label-scope.js +27 -7
  243. package/dist/lib/local-skills.d.ts +98 -0
  244. package/dist/lib/local-skills.js +195 -0
  245. package/dist/lib/machines.d.ts +10 -10
  246. package/dist/lib/machines.js +23 -1
  247. package/dist/lib/search-query.d.ts +19 -0
  248. package/dist/lib/search-query.js +35 -0
  249. package/dist/lib/space-custom-actions.d.ts +72 -0
  250. package/dist/lib/space-custom-actions.js +117 -0
  251. package/dist/lib/space-resolver.d.ts +1 -21
  252. package/dist/lib/space-resolver.js +19 -10
  253. package/dist/lib/task-closure.d.ts +33 -0
  254. package/dist/lib/task-closure.js +104 -0
  255. package/dist/lib/task-lifecycle-output.d.ts +14 -0
  256. package/dist/lib/task-lifecycle-output.js +209 -0
  257. package/dist/lib/task-ref-resolver.d.ts +1 -17
  258. package/dist/lib/task-ref-resolver.js +18 -8
  259. package/dist/lib/tasks.d.ts +3 -0
  260. package/dist/lib/tasks.js +40 -3
  261. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.js +4 -0
  262. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.js +4 -0
  263. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  264. package/dist/node_modules/@skrr-ai/data-provider/index.js +5559 -4897
  265. package/oclif.manifest.json +33890 -28690
  266. package/package.json +24 -6
@@ -0,0 +1,115 @@
1
+ import { withQuery } from './triggers';
2
+ type Query = Parameters<typeof withQuery>[1];
3
+ type Json = Record<string, unknown>;
4
+ /**
5
+ * The `id` argument every follow-up command that addresses one takes.
6
+ *
7
+ * oclif treats an empty string as a PRESENT required argument, so a scripted
8
+ * `skrr followups show "$ID"` with an empty variable built `/api/followups/` —
9
+ * the LIST route — and answered with the list's requirement, "agentId is
10
+ * required unless allAgents is set", naming two flags `show` does not have
11
+ * (OSK-9676). Refused at parse time instead, so it renders where a missing
12
+ * argument does, with usage, before any request is sent.
13
+ */
14
+ export declare const followUpIdArg: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
15
+ export { FOLLOWUP_KINDS, FOLLOWUP_STATES, FOLLOWUP_RESOLVE_OUTCOMES, type FollowUpKind, } from '@skrr-ai/data-provider';
16
+ export declare const WATCH_WAIT_KINDS: readonly ["clock", "event", "poll_until"];
17
+ export interface FollowUpRow {
18
+ id: string;
19
+ kind?: string;
20
+ form?: string | null;
21
+ producer?: string;
22
+ why?: string;
23
+ /** A reminder's own words. */
24
+ content?: string | null;
25
+ intentKey?: string;
26
+ subject?: {
27
+ type?: string;
28
+ externalKey?: string;
29
+ label?: string;
30
+ url?: string;
31
+ } | null;
32
+ agentId?: string | null;
33
+ state?: string;
34
+ terminal?: string | null;
35
+ terminalAt?: string | null;
36
+ terminalReason?: string | null;
37
+ escalatedInitiativeId?: string | null;
38
+ capsule?: unknown;
39
+ nextRunAt?: string | null;
40
+ deadlineAt?: string | null;
41
+ createdAt?: string;
42
+ label?: string;
43
+ }
44
+ export interface FollowUpCreateResponse {
45
+ followup: FollowUpRow | null;
46
+ reused?: boolean;
47
+ dropped?: false | 'budget';
48
+ proposal?: boolean;
49
+ budget?: Json;
50
+ fallbackTriggerId?: string | null;
51
+ }
52
+ export interface FollowUpObservability {
53
+ generatedAt?: string;
54
+ agents?: Array<{
55
+ agentId: string;
56
+ kinds: Array<{
57
+ kind: string;
58
+ created: number;
59
+ armed: number;
60
+ proposed: number;
61
+ settled: number;
62
+ terminal: Record<string, number>;
63
+ escalationRate: number | null;
64
+ fires: number;
65
+ budget: {
66
+ pool: string | null;
67
+ used: number;
68
+ limit: number | null;
69
+ } | null;
70
+ silenceRate: number | null;
71
+ silentFires: number | null;
72
+ }>;
73
+ }>;
74
+ }
75
+ export type ResolvedFollowUpAgent = {
76
+ id: string;
77
+ /**
78
+ * True when the identity came from the session binding rather than a flag —
79
+ * a follow-up declared inside an agent's own turn is `agent_in_turn`; one
80
+ * declared by a human at a shell is `user_explicit`. The producer field is
81
+ * provenance, not authority, and this is the only honest signal a CLI has.
82
+ */
83
+ boundToSession: boolean;
84
+ };
85
+ /**
86
+ * Which agent a follow-up belongs to.
87
+ *
88
+ * Deliberately NOT `resolveAgentIdentity`: that resolver's last step picks
89
+ * "the only visible agent", and a follow-up bound to the wrong agent wakes
90
+ * the wrong agent in the wrong main chat. The only honest sources are an
91
+ * explicit `--agent` and the session binding the runtime already proves
92
+ * (`OVERSKY_SESSION_AGENT_ID`, or the legacy `OVERSKY_AGENT_ID` a manual
93
+ * local runtime sets). Anything else throws AGENT_CONTEXT_AMBIGUOUS and
94
+ * names the fix rather than guessing.
95
+ */
96
+ export declare function resolveFollowUpAgent(explicitAgentId?: string, env?: Record<string, string | undefined>): ResolvedFollowUpAgent;
97
+ /** A `type:externalKey` subject ref → the subject block the API wants. */
98
+ export declare function parseSubjectRef(ref: string): {
99
+ type: string;
100
+ externalKey: string;
101
+ };
102
+ export declare const followupsApi: {
103
+ create: (body: Json) => Promise<FollowUpCreateResponse>;
104
+ list: (query?: Query) => Promise<{
105
+ followups: FollowUpRow[];
106
+ }>;
107
+ observability: (query?: Query) => Promise<FollowUpObservability>;
108
+ show: (id: string) => Promise<{
109
+ followup: FollowUpRow;
110
+ }>;
111
+ resolve: (id: string, body: Json) => Promise<Json>;
112
+ cancel: (id: string, body?: Json) => Promise<Json>;
113
+ reschedule: (id: string, body: Json) => Promise<Json>;
114
+ };
115
+ export declare function followupSummaryLine(row: FollowUpRow): string;
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.followupsApi = exports.WATCH_WAIT_KINDS = exports.FOLLOWUP_RESOLVE_OUTCOMES = exports.FOLLOWUP_STATES = exports.FOLLOWUP_KINDS = exports.followUpIdArg = void 0;
4
+ exports.resolveFollowUpAgent = resolveFollowUpAgent;
5
+ exports.parseSubjectRef = parseSubjectRef;
6
+ exports.followupSummaryLine = followupSummaryLine;
7
+ /**
8
+ * followups.ts — the `skrr followups` domain lib.
9
+ *
10
+ * A follow-up is ONE resource with four kinds (`reminder`, `watch`, `checkin`,
11
+ * `act`), declared by intent and derived into mechanism entirely server-side
12
+ * (`FollowUps/` + `docs/architecture/followup-service-2026-09-15.md`). The CLI
13
+ * is the universal substrate — a daemon-run local harness reaches the platform
14
+ * only through `skrr` — so every verb the API exposes lives here, and commands
15
+ * own flags only. The `followup_*` MCP tools wrap the same surface.
16
+ *
17
+ * What this file deliberately does NOT contain: executor modes, trigger kinds,
18
+ * destination logic, or budget policy. A caller names `kind` + `subject` +
19
+ * `why` and the server derives the rest; a second derivation here would be
20
+ * exactly the drift the service exists to kill.
21
+ */
22
+ const core_1 = require("@oclif/core");
23
+ const data_provider_1 = require("@skrr-ai/data-provider");
24
+ const runtime_context_1 = require("./runtime-context");
25
+ const triggers_1 = require("./triggers");
26
+ const agent_resolver_1 = require("./agent-resolver");
27
+ const MOUNT = '/api/followups';
28
+ const base = (id) => `${MOUNT}/${encodeURIComponent(id)}`;
29
+ /**
30
+ * The `id` argument every follow-up command that addresses one takes.
31
+ *
32
+ * oclif treats an empty string as a PRESENT required argument, so a scripted
33
+ * `skrr followups show "$ID"` with an empty variable built `/api/followups/` —
34
+ * the LIST route — and answered with the list's requirement, "agentId is
35
+ * required unless allAgents is set", naming two flags `show` does not have
36
+ * (OSK-9676). Refused at parse time instead, so it renders where a missing
37
+ * argument does, with usage, before any request is sent.
38
+ */
39
+ exports.followUpIdArg = core_1.Args.string({
40
+ description: 'Follow-up (trigger) id',
41
+ required: true,
42
+ ignoreStdin: true,
43
+ parse: async (input) => {
44
+ const id = input.trim();
45
+ if (!id)
46
+ throw new Error('id is empty — pass the follow-up id (see `skrr followups list`)');
47
+ return id;
48
+ },
49
+ });
50
+ /* ------------------------------------------------------------------ *
51
+ * Vocabulary (client-side validation only — the server is the authority) *
52
+ * ------------------------------------------------------------------ */
53
+ // Owned by `@skrr-ai/data-provider`, which the server validates against. These
54
+ // were declared a second time here, and the state list is where the two copies
55
+ // disagreed.
56
+ var data_provider_2 = require("@skrr-ai/data-provider");
57
+ Object.defineProperty(exports, "FOLLOWUP_KINDS", { enumerable: true, get: function () { return data_provider_2.FOLLOWUP_KINDS; } });
58
+ Object.defineProperty(exports, "FOLLOWUP_STATES", { enumerable: true, get: function () { return data_provider_2.FOLLOWUP_STATES; } });
59
+ Object.defineProperty(exports, "FOLLOWUP_RESOLVE_OUTCOMES", { enumerable: true, get: function () { return data_provider_2.FOLLOWUP_RESOLVE_OUTCOMES; } });
60
+ exports.WATCH_WAIT_KINDS = ['clock', 'event', 'poll_until'];
61
+ /**
62
+ * Which agent a follow-up belongs to.
63
+ *
64
+ * Deliberately NOT `resolveAgentIdentity`: that resolver's last step picks
65
+ * "the only visible agent", and a follow-up bound to the wrong agent wakes
66
+ * the wrong agent in the wrong main chat. The only honest sources are an
67
+ * explicit `--agent` and the session binding the runtime already proves
68
+ * (`OVERSKY_SESSION_AGENT_ID`, or the legacy `OVERSKY_AGENT_ID` a manual
69
+ * local runtime sets). Anything else throws AGENT_CONTEXT_AMBIGUOUS and
70
+ * names the fix rather than guessing.
71
+ */
72
+ function resolveFollowUpAgent(explicitAgentId, env = process.env) {
73
+ const runtime = (0, runtime_context_1.readRuntimeContext)(env);
74
+ const sessionAgent = runtime.agent?.source === 'OVERSKY_SESSION_AGENT_ID' ||
75
+ runtime.agent?.source === 'OVERSKY_AGENT_ID'
76
+ ? runtime.agent
77
+ : undefined;
78
+ if (sessionAgent) {
79
+ if (explicitAgentId && explicitAgentId !== sessionAgent.id) {
80
+ throw new agent_resolver_1.AgentResolutionError({
81
+ code: 'AGENT_CONTEXT_CONFLICT',
82
+ message: 'This local runtime is bound to a session agent. Do not override it with another agent ID.',
83
+ suggestion: 'Use the bound session agent, or start a new daemon session for a different agent.',
84
+ details: { explicitAgentId, sessionAgentId: sessionAgent.id, source: sessionAgent.source },
85
+ });
86
+ }
87
+ return { id: sessionAgent.id, boundToSession: true };
88
+ }
89
+ if (explicitAgentId)
90
+ return { id: explicitAgentId, boundToSession: false };
91
+ throw new agent_resolver_1.AgentResolutionError({
92
+ code: 'AGENT_CONTEXT_AMBIGUOUS',
93
+ message: 'No agent: this session is not bound to one, so name the agent these follow-ups are for.',
94
+ suggestion: 'Pass `--agent <agent-id>`, run inside a daemon session that sets OVERSKY_SESSION_AGENT_ID, or use `--all-agents` where listing is the intent.',
95
+ details: { runtimeKind: runtime.kind },
96
+ });
97
+ }
98
+ /** A `type:externalKey` subject ref → the subject block the API wants. */
99
+ function parseSubjectRef(ref) {
100
+ const idx = ref.indexOf(':');
101
+ if (idx <= 0 || idx === ref.length - 1) {
102
+ throw new Error(`--subject must be "type:externalKey" (e.g. task:OSK-42, conversation:conv_123), got "${ref}"`);
103
+ }
104
+ return { type: ref.slice(0, idx), externalKey: ref.slice(idx + 1) };
105
+ }
106
+ /* ------------------------------------------------------------------ *
107
+ * API *
108
+ * ------------------------------------------------------------------ */
109
+ exports.followupsApi = {
110
+ create: (body) => data_provider_1.request.post(MOUNT, body),
111
+ list: (query = {}) => data_provider_1.request.get((0, triggers_1.withQuery)(MOUNT, query)),
112
+ observability: (query = {}) => data_provider_1.request.get((0, triggers_1.withQuery)(`${MOUNT}/observability`, query)),
113
+ show: (id) => data_provider_1.request.get(base(id)),
114
+ resolve: (id, body) => data_provider_1.request.post(`${base(id)}/resolve`, body),
115
+ cancel: (id, body = {}) => data_provider_1.request.post(`${base(id)}/cancel`, body),
116
+ reschedule: (id, body) => data_provider_1.request.post(`${base(id)}/reschedule`, body),
117
+ };
118
+ /* ------------------------------------------------------------------ *
119
+ * Rendering *
120
+ * ------------------------------------------------------------------ */
121
+ const fmtWhen = (iso) => (iso ? iso.slice(0, 16).replace('T', ' ') + 'Z' : '—');
122
+ function followupSummaryLine(row) {
123
+ const subject = row.subject ? `${row.subject.type}:${row.subject.externalKey}` : '—';
124
+ const kindForm = row.form ? `${row.kind}/${row.form}` : (row.kind ?? '—');
125
+ const when = row.state === 'settled' ? `settled:${row.terminal ?? '?'}` : `next:${fmtWhen(row.nextRunAt)}`;
126
+ return `${row.id} [${row.state ?? '?'}] ${kindForm} ${subject} ${when}`;
127
+ }
@@ -42,6 +42,14 @@ export interface Harness {
42
42
  _id: string;
43
43
  provider: string;
44
44
  name?: string;
45
+ /**
46
+ * The name everyone else sees, when YOUR alias is covering it. `name` is the
47
+ * effective one. Present only while an alias is actually shadowing a
48
+ * different value, so a reader can tell "no alias" from "alias equals the
49
+ * shared name".
50
+ */
51
+ sharedName?: string;
52
+ aliased?: boolean;
45
53
  status?: string;
46
54
  harnessLocation?: 'local' | 'cloud';
47
55
  daemonId?: string;
@@ -50,11 +50,29 @@ export declare function resolveBundleLabel(bundleId: string | undefined, names:
50
50
  * the daemon is genuinely gone. A remedy that 409s is worse than no remedy — it
51
51
  * spends the reader's attention and returns them to where they started.
52
52
  */
53
+ /**
54
+ * A daemon id is `daemon_<hash>_<source>[_<profile>]`, where the hash covers
55
+ * the MACHINE and the cwd and `<source>` is the supervision mode
56
+ * (`cli`, `launchd`, `electron`, …). So two ids that differ only in that
57
+ * segment describe the SAME machine and directory, started a different way.
58
+ *
59
+ * That matters because switching how the daemon is started — the desktop app
60
+ * installs a launchd agent, `skrr daemon start` runs a cli one — mints a new
61
+ * id and orphans every managed-instruction target pinned to the old one.
62
+ */
63
+ export declare function parseDaemonId(daemonId: string): {
64
+ place: string;
65
+ source: string;
66
+ profile: string;
67
+ } | null;
68
+ /** The live daemon that is this target's machine and directory, started differently. */
69
+ export declare function sameMachineDaemon(targetDaemonId: string, liveDaemonIds: string[]): string | null;
53
70
  export declare function describeTargetSync(target: {
54
71
  status: string;
72
+ daemonId?: string;
55
73
  lastError?: string;
56
74
  lastSyncedAt?: string;
57
75
  lastAttemptedAt?: string;
58
76
  updatedAt?: string;
59
77
  driftCount?: number;
60
- }, now?: number): string;
78
+ }, now?: number, liveDaemonIds?: string[]): string;
@@ -4,6 +4,8 @@ exports.readInstructionBody = readInstructionBody;
4
4
  exports.formatSyncStatus = formatSyncStatus;
5
5
  exports.buildBundleNameIndex = buildBundleNameIndex;
6
6
  exports.resolveBundleLabel = resolveBundleLabel;
7
+ exports.parseDaemonId = parseDaemonId;
8
+ exports.sameMachineDaemon = sameMachineDaemon;
7
9
  exports.describeTargetSync = describeTargetSync;
8
10
  const promises_1 = require("node:fs/promises");
9
11
  async function readStdin() {
@@ -93,7 +95,39 @@ const PENDING_IS_SUSPECT_AFTER_MS = 24 * 60 * 60 * 1000;
93
95
  * the daemon is genuinely gone. A remedy that 409s is worse than no remedy — it
94
96
  * spends the reader's attention and returns them to where they started.
95
97
  */
96
- function describeTargetSync(target, now = Date.now()) {
98
+ /**
99
+ * A daemon id is `daemon_<hash>_<source>[_<profile>]`, where the hash covers
100
+ * the MACHINE and the cwd and `<source>` is the supervision mode
101
+ * (`cli`, `launchd`, `electron`, …). So two ids that differ only in that
102
+ * segment describe the SAME machine and directory, started a different way.
103
+ *
104
+ * That matters because switching how the daemon is started — the desktop app
105
+ * installs a launchd agent, `skrr daemon start` runs a cli one — mints a new
106
+ * id and orphans every managed-instruction target pinned to the old one.
107
+ */
108
+ function parseDaemonId(daemonId) {
109
+ const parts = String(daemonId || '').split('_');
110
+ if (parts.length < 3 || parts[0] !== 'daemon')
111
+ return null;
112
+ return { place: parts[1], source: parts[2], profile: parts.slice(3).join('_') };
113
+ }
114
+ /** The live daemon that is this target's machine and directory, started differently. */
115
+ function sameMachineDaemon(targetDaemonId, liveDaemonIds) {
116
+ const target = parseDaemonId(targetDaemonId);
117
+ if (!target)
118
+ return null;
119
+ for (const live of liveDaemonIds) {
120
+ if (live === targetDaemonId)
121
+ continue;
122
+ const other = parseDaemonId(live);
123
+ if (!other)
124
+ continue;
125
+ if (other.place === target.place && other.profile === target.profile)
126
+ return live;
127
+ }
128
+ return null;
129
+ }
130
+ function describeTargetSync(target, now = Date.now(), liveDaemonIds = []) {
97
131
  const base = formatSyncStatus(target.status, target.lastError);
98
132
  // Say when skrr has overwritten something you typed.
99
133
  //
@@ -131,5 +165,22 @@ function describeTargetSync(target, now = Date.now()) {
131
165
  // remedy off the end — the same truncation e51abf3362 exists to prevent. A
132
166
  // stalled target is not reconciling, so its drift is historical and the
133
167
  // remedy is the actionable half; the count reappears once it is unstuck.
168
+ // CHECK BEFORE ASSERTING, and never ship `--force` with a guess.
169
+ //
170
+ // "daemon may be gone" was stated unconditionally. On this machine it was
171
+ // false — the daemon had answered a heartbeat 112 ms earlier — because the
172
+ // target is pinned to `daemon_<hash>_launchd_default` while the live daemon
173
+ // is `daemon_<hash>_cli_default`: same machine, same directory, started a
174
+ // different way. So the CLI confidently recommended
175
+ // `remove, then purge-target --force` against live, correctly-installed
176
+ // managed blocks. An error that is confidently wrong AND carries its own
177
+ // `--force` is worse than one that says it does not know.
178
+ const elsewhere = sameMachineDaemon(target.daemonId || '', liveDaemonIds);
179
+ if (elsewhere) {
180
+ const live = parseDaemonId(elsewhere);
181
+ const pinned = parseDaemonId(target.daemonId || '');
182
+ // Kept under the STATUS column's `maxWidth: 78`, same constraint as below.
183
+ return `${base} — stalled ${days}d; daemon here now runs as ${live?.source}, not ${pinned?.source}: re-install`;
184
+ }
134
185
  return `${base} — stalled ${days}d; daemon may be gone: remove, then purge-target --force`;
135
186
  }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * label-ref.ts — let a human type `--label dogfood` instead of a uuid.
3
+ *
4
+ * WHY
5
+ * ---
6
+ * Labels are addressed by id everywhere: `spaces labels add <space> --label
7
+ * <uuid>`. That is correct for the wire and hostile at a keyboard, and the cost
8
+ * was measurable — the fleet reached nine labels, every one of them
9
+ * `applicableTo: ['task']`, and not one space-applicable label had ever been
10
+ * created. Applying one meant `labels list`, eyeball the uuid, paste it. Three
11
+ * steps to do the thing the flag already existed for.
12
+ *
13
+ * THE RULE, AND WHY IT IS NOT "CREATE IT IF MISSING"
14
+ * -------------------------------------------------
15
+ * An unknown name is REFUSED, with the closest matches named. It is never
16
+ * created.
17
+ *
18
+ * Auto-create is the obvious convenience and it is the one thing that destroys
19
+ * the vocabulary: `--label dogfod` would mint a second label nobody meant, and
20
+ * a label is worth exactly as much as the completeness of a filter on it. The
21
+ * server draws the same line for every agent-supplied key — the operator
22
+ * declares the vocabulary, the caller chooses from it (`WakeIntentResolver`,
23
+ * the `agent_run` watched source, and `Labels/labelGuidance.js`). This is that
24
+ * rule at the keyboard instead of at the model.
25
+ *
26
+ * Refusing is also what makes the typo cheap: the caller sees
27
+ * `did you mean: dogfood?` and retries, instead of discovering six weeks later
28
+ * that half the program is under a name nobody can spell twice.
29
+ *
30
+ * An id passes through UNTOUCHED and costs no round trip — scripts and agents
31
+ * that already hold ids keep working exactly as before, and nothing here is on
32
+ * their path.
33
+ */
34
+ import { dataService } from '@skrr-ai/data-provider';
35
+ import type { LabelApplicable } from './label-scope';
36
+ export declare function looksLikeLabelId(ref: string): boolean;
37
+ /**
38
+ * A label row as the API returns it. Narrow on purpose at the fields this
39
+ * module reasons about; everything else rides along untouched so callers that
40
+ * RENDER labels (`labels list`) can use the same merge.
41
+ */
42
+ export interface LabelRow {
43
+ id: string;
44
+ name: string;
45
+ scope?: string;
46
+ scopeId?: string;
47
+ color?: string;
48
+ applicableTo?: string[];
49
+ usageCount?: number;
50
+ archivedAt?: string | null;
51
+ [key: string]: unknown;
52
+ }
53
+ /**
54
+ * Names close enough to be worth printing. A suggestion list that includes
55
+ * everything is the same as no suggestion list — the caller still has to read
56
+ * all of it — so the threshold scales with the typed length and caps at three.
57
+ */
58
+ export declare function closestNames(typed: string, names: string[], limit?: number): string[];
59
+ export interface LabelVocabularyContext {
60
+ /** Narrow to labels attachable to this entity type. Omit for all of them. */
61
+ applicableTo?: LabelApplicable;
62
+ /** Narrows to the workspace scope; `--workspace` / OVERSKY_WORKSPACE_ID. */
63
+ workspaceId?: string;
64
+ /** Archived labels are excluded unless asked for. */
65
+ includeArchived?: boolean;
66
+ /** Injected in tests. */
67
+ getLabels?: typeof dataService.getLabels;
68
+ }
69
+ export interface LabelRefContext extends LabelVocabularyContext {
70
+ /** Which entity the resolved labels will be attached to. */
71
+ applicableTo: LabelApplicable;
72
+ }
73
+ /**
74
+ * Every scope this caller could legitimately attach from, merged.
75
+ *
76
+ * `GET /api/labels` takes ONE scope, and a label the caller may attach can live
77
+ * in either of two. Asking for one and calling it "the vocabulary" would refuse
78
+ * a name that exists — which is the failure mode that makes people stop using
79
+ * names at all. A scope that cannot be read contributes nothing and throws
80
+ * nothing: this runs to produce a better error message, and must never become
81
+ * the error.
82
+ */
83
+ export declare function collectLabelVocabulary(context: LabelVocabularyContext): Promise<LabelRow[]>;
84
+ export declare class LabelRefError extends Error {
85
+ }
86
+ /**
87
+ * Resolve `--label` / `--label-id` values, each either an id or a name.
88
+ *
89
+ * Throws `LabelRefError` with an actionable message when a name does not
90
+ * resolve, or resolves to more than one label. Ambiguity is a refusal rather
91
+ * than a pick: the same name may legitimately exist in a user scope and a
92
+ * workspace scope, and guessing which one the caller meant is how the wrong
93
+ * label ends up attached to a year of work.
94
+ */
95
+ export declare function resolveLabelRefs(refs: string[] | undefined, context: LabelRefContext): Promise<string[]>;
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ /**
3
+ * label-ref.ts — let a human type `--label dogfood` instead of a uuid.
4
+ *
5
+ * WHY
6
+ * ---
7
+ * Labels are addressed by id everywhere: `spaces labels add <space> --label
8
+ * <uuid>`. That is correct for the wire and hostile at a keyboard, and the cost
9
+ * was measurable — the fleet reached nine labels, every one of them
10
+ * `applicableTo: ['task']`, and not one space-applicable label had ever been
11
+ * created. Applying one meant `labels list`, eyeball the uuid, paste it. Three
12
+ * steps to do the thing the flag already existed for.
13
+ *
14
+ * THE RULE, AND WHY IT IS NOT "CREATE IT IF MISSING"
15
+ * -------------------------------------------------
16
+ * An unknown name is REFUSED, with the closest matches named. It is never
17
+ * created.
18
+ *
19
+ * Auto-create is the obvious convenience and it is the one thing that destroys
20
+ * the vocabulary: `--label dogfod` would mint a second label nobody meant, and
21
+ * a label is worth exactly as much as the completeness of a filter on it. The
22
+ * server draws the same line for every agent-supplied key — the operator
23
+ * declares the vocabulary, the caller chooses from it (`WakeIntentResolver`,
24
+ * the `agent_run` watched source, and `Labels/labelGuidance.js`). This is that
25
+ * rule at the keyboard instead of at the model.
26
+ *
27
+ * Refusing is also what makes the typo cheap: the caller sees
28
+ * `did you mean: dogfood?` and retries, instead of discovering six weeks later
29
+ * that half the program is under a name nobody can spell twice.
30
+ *
31
+ * An id passes through UNTOUCHED and costs no round trip — scripts and agents
32
+ * that already hold ids keep working exactly as before, and nothing here is on
33
+ * their path.
34
+ */
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.LabelRefError = void 0;
37
+ exports.looksLikeLabelId = looksLikeLabelId;
38
+ exports.closestNames = closestNames;
39
+ exports.collectLabelVocabulary = collectLabelVocabulary;
40
+ exports.resolveLabelRefs = resolveLabelRefs;
41
+ const data_provider_1 = require("@skrr-ai/data-provider");
42
+ /** A canonical v4 uuid, which is what every label id is. */
43
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
44
+ function looksLikeLabelId(ref) {
45
+ return UUID_RE.test(ref.trim());
46
+ }
47
+ /** Levenshtein, iterative, two rows. Only ever runs on a refusal path. */
48
+ function distance(a, b) {
49
+ if (a === b)
50
+ return 0;
51
+ if (!a.length)
52
+ return b.length;
53
+ if (!b.length)
54
+ return a.length;
55
+ let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
56
+ for (let i = 1; i <= a.length; i += 1) {
57
+ const row = [i];
58
+ for (let j = 1; j <= b.length; j += 1) {
59
+ row[j] = Math.min(prev[j] + 1, row[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
60
+ }
61
+ prev = row;
62
+ }
63
+ return prev[b.length];
64
+ }
65
+ /**
66
+ * Names close enough to be worth printing. A suggestion list that includes
67
+ * everything is the same as no suggestion list — the caller still has to read
68
+ * all of it — so the threshold scales with the typed length and caps at three.
69
+ */
70
+ function closestNames(typed, names, limit = 3) {
71
+ const needle = typed.trim().toLowerCase();
72
+ const budget = Math.max(2, Math.ceil(needle.length / 3));
73
+ return names
74
+ .map((name) => ({ name, d: distance(needle, name.toLowerCase()) }))
75
+ .filter((row) => row.d <= budget || row.name.toLowerCase().includes(needle))
76
+ .sort((a, b) => a.d - b.d)
77
+ .slice(0, limit)
78
+ .map((row) => row.name);
79
+ }
80
+ /**
81
+ * Every scope this caller could legitimately attach from, merged.
82
+ *
83
+ * `GET /api/labels` takes ONE scope, and a label the caller may attach can live
84
+ * in either of two. Asking for one and calling it "the vocabulary" would refuse
85
+ * a name that exists — which is the failure mode that makes people stop using
86
+ * names at all. A scope that cannot be read contributes nothing and throws
87
+ * nothing: this runs to produce a better error message, and must never become
88
+ * the error.
89
+ */
90
+ async function collectLabelVocabulary(context) {
91
+ const getLabels = context.getLabels ?? data_provider_1.dataService.getLabels;
92
+ const requests = [{ scope: 'user' }];
93
+ if (context.workspaceId)
94
+ requests.push({ scope: 'workspace', scopeId: context.workspaceId });
95
+ const settled = await Promise.allSettled(requests.map((request) => getLabels({
96
+ ...request,
97
+ ...(context.applicableTo ? { applicableTo: context.applicableTo } : {}),
98
+ ...(context.includeArchived ? { includeArchived: true } : {}),
99
+ })));
100
+ const byId = new Map();
101
+ for (const outcome of settled) {
102
+ if (outcome.status !== 'fulfilled')
103
+ continue;
104
+ for (const row of outcome.value?.data ?? []) {
105
+ if (row?.id && !byId.has(row.id))
106
+ byId.set(row.id, row);
107
+ }
108
+ }
109
+ return [...byId.values()];
110
+ }
111
+ class LabelRefError extends Error {
112
+ }
113
+ exports.LabelRefError = LabelRefError;
114
+ /**
115
+ * Resolve `--label` / `--label-id` values, each either an id or a name.
116
+ *
117
+ * Throws `LabelRefError` with an actionable message when a name does not
118
+ * resolve, or resolves to more than one label. Ambiguity is a refusal rather
119
+ * than a pick: the same name may legitimately exist in a user scope and a
120
+ * workspace scope, and guessing which one the caller meant is how the wrong
121
+ * label ends up attached to a year of work.
122
+ */
123
+ async function resolveLabelRefs(refs, context) {
124
+ const input = (refs ?? []).map((ref) => ref.trim()).filter(Boolean);
125
+ if (input.length === 0)
126
+ return [];
127
+ // Ids are the wire form and the common case for scripts. Resolve nothing,
128
+ // fetch nothing, and let the server be the authority on whether they exist.
129
+ if (input.every(looksLikeLabelId))
130
+ return input;
131
+ const vocabulary = await collectLabelVocabulary(context);
132
+ const resolved = [];
133
+ for (const ref of input) {
134
+ if (looksLikeLabelId(ref)) {
135
+ resolved.push(ref);
136
+ continue;
137
+ }
138
+ const needle = ref.toLowerCase();
139
+ const matches = vocabulary.filter((row) => (row.name ?? '').toLowerCase() === needle);
140
+ if (matches.length === 1) {
141
+ resolved.push(matches[0].id);
142
+ continue;
143
+ }
144
+ if (matches.length > 1) {
145
+ const listed = matches
146
+ .map((row) => ` ${row.id} ${row.name}${row.scope ? ` (${row.scope})` : ''}`)
147
+ .join('\n');
148
+ throw new LabelRefError(`"${ref}" names ${matches.length} labels. Pass the id you mean:\n${listed}`);
149
+ }
150
+ if (vocabulary.length === 0) {
151
+ throw new LabelRefError(`No label named "${ref}" — and no label applies to ${context.applicableTo}s yet. ` +
152
+ `Create one with \`labels create --name ${ref} --applicable-to ${context.applicableTo}\`.`);
153
+ }
154
+ const near = closestNames(ref, vocabulary.map((row) => row.name));
155
+ throw new LabelRefError(`No label named "${ref}" applies to ${context.applicableTo}s.` +
156
+ (near.length ? ` Did you mean: ${near.join(', ')}?` : '') +
157
+ ` Run \`labels list\` for the vocabulary, or create it deliberately with ` +
158
+ `\`labels create --name ${ref} --applicable-to ${context.applicableTo}\` — ` +
159
+ 'a name is never created implicitly, because a second spelling of a label ' +
160
+ 'is worse than no label.');
161
+ }
162
+ return resolved;
163
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Shared plumbing for the unified label commands (`skrr labels …`).
3
3
  *
4
- * Labels are anchored to exactly one scope — a user, a space, or a workspace —
4
+ * Labels are anchored to exactly one scope — a user or a workspace —
5
5
  * via the `(scope, scopeId)` pair. That pair is simultaneously the uniqueness
6
6
  * key and the permission boundary server-side (see
7
7
  * `api/server/services/Labels/LabelService.js`), so every label command has to
@@ -9,8 +9,23 @@
9
9
  * always explicit; `user` is the default because that is the only scope whose
10
10
  * id the server can infer (it falls back to the caller).
11
11
  */
12
- export declare const LABEL_SCOPES: readonly ["user", "space", "workspace"];
12
+ export declare const LABEL_SCOPES: readonly ["user", "workspace"];
13
13
  export type LabelScope = (typeof LABEL_SCOPES)[number];
14
+ /**
15
+ * Scopes whose id the server cannot infer, so `--scope-id` must name it.
16
+ *
17
+ * `resolveLabelScope` below and the `--scope-id` help text are BOTH derived
18
+ * from this, rather than each restating the list. They drifted once already:
19
+ * `0298b7e4fd` collapsed the vocabulary from three scopes to two, and the help
20
+ * kept advertising `--scope space` for months after the validator had stopped
21
+ * accepting it — so following `--help` produced `Expected --scope=space to be
22
+ * one of: user, workspace`, whose last line points back at the help that was
23
+ * wrong. Static prose describing a value set is how that happens; deriving it
24
+ * is what stops the next collapse doing the same.
25
+ */
26
+ export declare const LABEL_SCOPES_REQUIRING_ID: readonly ["workspace"];
27
+ /** Scopes the server resolves from the caller — passing an id is an error. */
28
+ export declare const LABEL_SCOPES_INFERRED: "user"[];
14
29
  export declare const LABEL_APPLICABLE: readonly ["task", "space", "goal", "routine"];
15
30
  export type LabelApplicable = (typeof LABEL_APPLICABLE)[number];
16
31
  /**