@skrr-ai/cli 0.1.92 → 0.1.93

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 (267) hide show
  1. package/dist/base-command.d.ts +44 -0
  2. package/dist/base-command.js +96 -0
  3. package/dist/commands/agents/badge.d.ts +41 -0
  4. package/dist/commands/agents/badge.js +158 -0
  5. package/dist/commands/agents/create.d.ts +24 -0
  6. package/dist/commands/agents/create.js +212 -0
  7. package/dist/commands/agents/embed.d.ts +20 -0
  8. package/dist/commands/agents/embed.js +61 -0
  9. package/dist/commands/agents/share.d.ts +27 -0
  10. package/dist/commands/agents/share.js +100 -0
  11. package/dist/commands/agents/vanity.d.ts +21 -0
  12. package/dist/commands/agents/vanity.js +77 -0
  13. package/dist/commands/agents/visibility.d.ts +21 -1
  14. package/dist/commands/agents/visibility.js +112 -0
  15. package/dist/commands/commands.d.ts +1 -0
  16. package/dist/commands/commands.js +22 -9
  17. package/dist/commands/earnings/attributions.d.ts +17 -0
  18. package/dist/commands/earnings/attributions.js +82 -0
  19. package/dist/commands/earnings/index.js +2 -1
  20. package/dist/commands/generate-recovery-code.js +4 -3
  21. package/dist/commands/github/allow-tools.d.ts +1 -0
  22. package/dist/commands/github/app/install.d.ts +1 -0
  23. package/dist/commands/github/app/status.d.ts +1 -0
  24. package/dist/commands/github/connect.d.ts +1 -0
  25. package/dist/commands/github/connections.d.ts +1 -0
  26. package/dist/commands/github/disconnect.d.ts +1 -0
  27. package/dist/commands/github/finalize.d.ts +1 -0
  28. package/dist/commands/github/reconnect.d.ts +1 -0
  29. package/dist/commands/github/run.d.ts +1 -0
  30. package/dist/commands/github/tools.d.ts +1 -0
  31. package/dist/commands/integrations/connections/connect.d.ts +1 -0
  32. package/dist/commands/integrations/connections/disconnect.d.ts +1 -0
  33. package/dist/commands/integrations/connections/finalize.d.ts +1 -0
  34. package/dist/commands/integrations/connections/list.d.ts +1 -0
  35. package/dist/commands/integrations/connections/reconnect.d.ts +1 -0
  36. package/dist/commands/integrations/execute.d.ts +1 -0
  37. package/dist/commands/integrations/search.d.ts +1 -0
  38. package/dist/commands/integrations/toolkits.d.ts +1 -0
  39. package/dist/commands/integrations/tools/allow.d.ts +1 -0
  40. package/dist/commands/integrations/tools/list.d.ts +1 -0
  41. package/dist/commands/login.js +12 -0
  42. package/dist/commands/machines/dedicated/logs.d.ts +19 -0
  43. package/dist/commands/machines/dedicated/logs.js +103 -0
  44. package/dist/commands/machines/dedicated/power/set.js +1 -1
  45. package/dist/commands/open.d.ts +24 -0
  46. package/dist/commands/open.js +74 -0
  47. package/dist/commands/projects/archive.d.ts +1 -0
  48. package/dist/commands/projects/archive.js +23 -0
  49. package/dist/commands/projects/delete.d.ts +1 -0
  50. package/dist/commands/projects/delete.js +23 -0
  51. package/dist/commands/projects/list.d.ts +3 -0
  52. package/dist/commands/projects/list.js +12 -2
  53. package/dist/commands/spaces/actions/add.js +4 -3
  54. package/dist/commands/spaces/actions/available.js +3 -2
  55. package/dist/commands/spaces/actions/list.js +5 -4
  56. package/dist/commands/spaces/actions/replace.js +6 -5
  57. package/dist/commands/spaces/archive.js +3 -2
  58. package/dist/commands/spaces/auto-executor/preview.js +4 -3
  59. package/dist/commands/spaces/auto-executor/run.js +4 -3
  60. package/dist/commands/spaces/auto-executor/runs.js +3 -2
  61. package/dist/commands/spaces/auto-executor/update.js +4 -3
  62. package/dist/commands/spaces/bootstrap.js +3 -2
  63. package/dist/commands/spaces/briefs/delete.js +3 -2
  64. package/dist/commands/spaces/briefs/get.js +3 -2
  65. package/dist/commands/spaces/briefs/list.js +3 -2
  66. package/dist/commands/spaces/briefs/set.js +5 -4
  67. package/dist/commands/spaces/capacity.js +5 -4
  68. package/dist/commands/spaces/conversations.js +3 -2
  69. package/dist/commands/spaces/cycles/create.js +3 -2
  70. package/dist/commands/spaces/cycles/graph.js +2 -1
  71. package/dist/commands/spaces/cycles/list.js +3 -2
  72. package/dist/commands/spaces/cycles/settings.js +4 -3
  73. package/dist/commands/spaces/cycles/show.js +3 -2
  74. package/dist/commands/spaces/cycles/update.js +3 -2
  75. package/dist/commands/spaces/delete.js +4 -3
  76. package/dist/commands/spaces/expertise-suggestions.js +4 -3
  77. package/dist/commands/spaces/files/delete.js +3 -2
  78. package/dist/commands/spaces/files/folders/create.js +3 -2
  79. package/dist/commands/spaces/files/folders/delete.js +3 -2
  80. package/dist/commands/spaces/files/folders/list.js +3 -2
  81. package/dist/commands/spaces/files/folders/update.js +3 -2
  82. package/dist/commands/spaces/files/list.js +3 -2
  83. package/dist/commands/spaces/files/move.js +3 -2
  84. package/dist/commands/spaces/files/rename.js +3 -2
  85. package/dist/commands/spaces/files/upload.js +6 -5
  86. package/dist/commands/spaces/github-automations/set.js +3 -2
  87. package/dist/commands/spaces/github-automations/show.js +3 -2
  88. package/dist/commands/spaces/goals.js +2 -1
  89. package/dist/commands/spaces/heartbeats/create.js +4 -3
  90. package/dist/commands/spaces/heartbeats/delete.js +4 -3
  91. package/dist/commands/spaces/heartbeats/list.js +4 -3
  92. package/dist/commands/spaces/heartbeats/status.js +3 -2
  93. package/dist/commands/spaces/heartbeats/update.js +4 -3
  94. package/dist/commands/spaces/insights.js +5 -4
  95. package/dist/commands/spaces/labels/add.js +4 -3
  96. package/dist/commands/spaces/labels/list.js +2 -1
  97. package/dist/commands/spaces/labels/remove.js +4 -3
  98. package/dist/commands/spaces/list.d.ts +3 -0
  99. package/dist/commands/spaces/list.js +11 -2
  100. package/dist/commands/spaces/live/cards/create.js +3 -2
  101. package/dist/commands/spaces/live/cards/list.js +3 -2
  102. package/dist/commands/spaces/live/cards/reorder.js +7 -2
  103. package/dist/commands/spaces/live/scene.js +4 -3
  104. package/dist/commands/spaces/members/add.js +7 -6
  105. package/dist/commands/spaces/members/list.js +4 -3
  106. package/dist/commands/spaces/members/remove.js +7 -6
  107. package/dist/commands/spaces/move.js +3 -2
  108. package/dist/commands/spaces/pin.js +5 -4
  109. package/dist/commands/spaces/preferences/show.js +4 -3
  110. package/dist/commands/spaces/preferences/tabs.js +4 -3
  111. package/dist/commands/spaces/revisions.js +3 -2
  112. package/dist/commands/spaces/room/ensure.js +5 -4
  113. package/dist/commands/spaces/room/messages.js +3 -2
  114. package/dist/commands/spaces/room/show.js +5 -4
  115. package/dist/commands/spaces/skills/add.js +4 -3
  116. package/dist/commands/spaces/skills/list.js +3 -2
  117. package/dist/commands/spaces/skills/pin.js +4 -3
  118. package/dist/commands/spaces/skills/remove.js +4 -3
  119. package/dist/commands/spaces/snapshot.js +3 -2
  120. package/dist/commands/spaces/states/archive.js +3 -2
  121. package/dist/commands/spaces/states/create.js +3 -2
  122. package/dist/commands/spaces/states/list.js +3 -2
  123. package/dist/commands/spaces/states/reorder.js +2 -1
  124. package/dist/commands/spaces/states/update.js +3 -2
  125. package/dist/commands/spaces/summary.js +4 -3
  126. package/dist/commands/spaces/tasks.d.ts +3 -0
  127. package/dist/commands/spaces/tasks.js +14 -3
  128. package/dist/commands/spaces/triggers/delete.js +5 -4
  129. package/dist/commands/spaces/triggers/set.js +4 -3
  130. package/dist/commands/spaces/triggers/show.js +4 -3
  131. package/dist/commands/spaces/unarchive.js +3 -2
  132. package/dist/commands/spaces/update.js +5 -4
  133. package/dist/commands/spaces/views/create.js +3 -2
  134. package/dist/commands/spaces/views/list.js +3 -2
  135. package/dist/commands/spaces/views/reorder.js +6 -3
  136. package/dist/commands/spaces/views/subscribe.js +2 -1
  137. package/dist/commands/spaces/views/subscriptions.js +1 -0
  138. package/dist/commands/spaces/views/unsubscribe.js +2 -1
  139. package/dist/commands/spaces/widgets/add.js +4 -3
  140. package/dist/commands/spaces/widgets/list.js +3 -2
  141. package/dist/commands/spaces/widgets/remove.js +5 -4
  142. package/dist/commands/spaces/work-graph.js +2 -1
  143. package/dist/commands/store/install.d.ts +1 -1
  144. package/dist/commands/store/install.js +24 -8
  145. package/dist/commands/store/show.d.ts +25 -0
  146. package/dist/commands/store/show.js +203 -0
  147. package/dist/commands/store/stats.d.ts +33 -0
  148. package/dist/commands/store/stats.js +258 -0
  149. package/dist/commands/tasks/actions/attach.js +1 -1
  150. package/dist/commands/tasks/actions/detach.js +4 -3
  151. package/dist/commands/tasks/activity.js +3 -2
  152. package/dist/commands/tasks/ancestors.js +2 -1
  153. package/dist/commands/tasks/approvals/create.js +2 -1
  154. package/dist/commands/tasks/approvals/delete.js +3 -2
  155. package/dist/commands/tasks/approvals/list.js +5 -4
  156. package/dist/commands/tasks/approvals/resolve.js +2 -1
  157. package/dist/commands/tasks/archive.js +3 -2
  158. package/dist/commands/tasks/assign.js +4 -3
  159. package/dist/commands/tasks/attachments/delete.js +3 -2
  160. package/dist/commands/tasks/attachments/download.d.ts +1 -0
  161. package/dist/commands/tasks/attachments/download.js +20 -1
  162. package/dist/commands/tasks/attachments/list.js +2 -1
  163. package/dist/commands/tasks/attachments/set-role.js +2 -1
  164. package/dist/commands/tasks/attachments/upload.js +2 -1
  165. package/dist/commands/tasks/bulk-update.d.ts +1 -0
  166. package/dist/commands/tasks/bulk-update.js +106 -15
  167. package/dist/commands/tasks/cancel-cascade.d.ts +1 -0
  168. package/dist/commands/tasks/cancel-cascade.js +13 -3
  169. package/dist/commands/tasks/cascade-status.js +2 -1
  170. package/dist/commands/tasks/checkout.js +3 -2
  171. package/dist/commands/tasks/children.js +2 -1
  172. package/dist/commands/tasks/comments/add.js +7 -6
  173. package/dist/commands/tasks/comments/anchor.js +8 -7
  174. package/dist/commands/tasks/comments/delete.js +3 -2
  175. package/dist/commands/tasks/comments/list.js +2 -1
  176. package/dist/commands/tasks/comments/reopen.js +3 -2
  177. package/dist/commands/tasks/comments/resolve.js +3 -2
  178. package/dist/commands/tasks/create-tree.d.ts +63 -0
  179. package/dist/commands/tasks/create-tree.js +197 -4
  180. package/dist/commands/tasks/delete.js +5 -4
  181. package/dist/commands/tasks/descendants.js +2 -1
  182. package/dist/commands/tasks/dri-plan.js +3 -2
  183. package/dist/commands/tasks/dri-run.js +3 -2
  184. package/dist/commands/tasks/dri-status.js +2 -1
  185. package/dist/commands/tasks/duplicate-pipeline.js +2 -1
  186. package/dist/commands/tasks/execution-summary.js +2 -1
  187. package/dist/commands/tasks/execution-timeline.js +2 -1
  188. package/dist/commands/tasks/fork.js +1 -0
  189. package/dist/commands/tasks/forward.js +4 -3
  190. package/dist/commands/tasks/handover.js +1 -0
  191. package/dist/commands/tasks/interrupt.js +3 -2
  192. package/dist/commands/tasks/labels/attach.js +4 -3
  193. package/dist/commands/tasks/labels/detach.js +4 -3
  194. package/dist/commands/tasks/labels/suggest.js +5 -4
  195. package/dist/commands/tasks/list.d.ts +27 -1
  196. package/dist/commands/tasks/list.js +101 -3
  197. package/dist/commands/tasks/move.js +4 -3
  198. package/dist/commands/tasks/output.js +6 -5
  199. package/dist/commands/tasks/pause-schedule.js +3 -2
  200. package/dist/commands/tasks/promote.js +1 -0
  201. package/dist/commands/tasks/prompt-log.js +2 -1
  202. package/dist/commands/tasks/release.js +5 -4
  203. package/dist/commands/tasks/remind.js +2 -1
  204. package/dist/commands/tasks/replay.js +3 -2
  205. package/dist/commands/tasks/report.js +1 -0
  206. package/dist/commands/tasks/resume-schedule.js +3 -2
  207. package/dist/commands/tasks/retry-schedule.js +3 -2
  208. package/dist/commands/tasks/retry.js +10 -9
  209. package/dist/commands/tasks/revisions.js +2 -1
  210. package/dist/commands/tasks/runs.js +6 -5
  211. package/dist/commands/tasks/schedule.js +3 -2
  212. package/dist/commands/tasks/self-schedule.js +6 -5
  213. package/dist/commands/tasks/skills/pin.js +3 -2
  214. package/dist/commands/tasks/skills/unpin.js +3 -2
  215. package/dist/commands/tasks/snooze.js +6 -5
  216. package/dist/commands/tasks/start-all.js +5 -4
  217. package/dist/commands/tasks/subscribe.js +1 -0
  218. package/dist/commands/tasks/tags/add.js +4 -3
  219. package/dist/commands/tasks/tags/remove.js +4 -3
  220. package/dist/commands/tasks/timeline.js +3 -2
  221. package/dist/commands/tasks/today.d.ts +4 -1
  222. package/dist/commands/tasks/today.js +12 -2
  223. package/dist/commands/tasks/transcript.js +6 -5
  224. package/dist/commands/tasks/unschedule.js +3 -2
  225. package/dist/commands/tasks/unsnooze.js +3 -2
  226. package/dist/commands/tasks/unsubscribe.js +2 -1
  227. package/dist/commands/tasks/update.js +5 -4
  228. package/dist/commands/tasks/wait.js +5 -4
  229. package/dist/lib/agent-distribution.d.ts +37 -0
  230. package/dist/lib/agent-distribution.js +63 -0
  231. package/dist/lib/agent-ref.d.ts +69 -0
  232. package/dist/lib/agent-ref.js +168 -0
  233. package/dist/lib/api-fetch.d.ts +2 -0
  234. package/dist/lib/api-fetch.js +14 -3
  235. package/dist/lib/command-hygiene.d.ts +13 -0
  236. package/dist/lib/command-hygiene.js +62 -2
  237. package/dist/lib/dedicated-power.d.ts +5 -0
  238. package/dist/lib/dedicated-power.js +30 -2
  239. package/dist/lib/dry-run-plans.d.ts +50 -0
  240. package/dist/lib/dry-run-plans.js +85 -0
  241. package/dist/lib/json-output.d.ts +31 -0
  242. package/dist/lib/json-output.js +78 -0
  243. package/dist/lib/list-output.d.ts +75 -0
  244. package/dist/lib/list-output.js +266 -0
  245. package/dist/lib/machine-daemon-log.d.ts +36 -0
  246. package/dist/lib/machine-daemon-log.js +78 -0
  247. package/dist/lib/node-adapter.js +10 -1
  248. package/dist/lib/open-target.d.ts +12 -0
  249. package/dist/lib/open-target.js +108 -0
  250. package/dist/lib/rate-limit-retry.d.ts +45 -0
  251. package/dist/lib/rate-limit-retry.js +88 -0
  252. package/dist/lib/store-listing.d.ts +45 -0
  253. package/dist/lib/store-listing.js +186 -0
  254. package/dist/lib/task-batch.d.ts +67 -0
  255. package/dist/lib/task-batch.js +227 -0
  256. package/dist/lib/terminal-qr.d.ts +55 -0
  257. package/dist/lib/terminal-qr.js +119 -0
  258. package/dist/lib/web-url.d.ts +11 -0
  259. package/dist/lib/web-url.js +24 -0
  260. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.d.ts +68 -0
  261. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.js +47 -1
  262. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.d.ts +68 -0
  263. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.js +46 -0
  264. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  265. package/dist/node_modules/@skrr-ai/data-provider/index.js +6612 -6113
  266. package/oclif.manifest.json +46778 -38019
  267. package/package.json +2 -1
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentRefError = void 0;
4
+ exports.cliFirstPartyHosts = cliFirstPartyHosts;
5
+ exports.resolveAgentRef = resolveAgentRef;
6
+ exports.attributedListingUrl = attributedListingUrl;
7
+ exports.installAttribution = installAttribution;
8
+ exports.shellQuote = shellQuote;
9
+ const data_provider_1 = require("@skrr-ai/data-provider");
10
+ const http_error_1 = require("./http-error");
11
+ const web_url_1 = require("./web-url");
12
+ class AgentRefError extends Error {
13
+ code;
14
+ constructor(message, code) {
15
+ super(message);
16
+ this.code = code;
17
+ this.name = 'AgentRefError';
18
+ }
19
+ }
20
+ exports.AgentRefError = AgentRefError;
21
+ /**
22
+ * An agent id is a single opaque token (`agent_…` today). Anything with a
23
+ * slash, a colon, whitespace or a dot is a link, a path or a typo — none of
24
+ * which may be sent to the API as if it were an id, because the server would
25
+ * answer "not found" and the person would conclude the agent does not exist.
26
+ */
27
+ const AGENT_ID_TOKEN = /^[A-Za-z0-9_-]+$/;
28
+ function hostOf(origin) {
29
+ try {
30
+ return new URL(origin).host.toLowerCase();
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ /**
37
+ * The built-in first-party hosts plus every origin the configured deployment
38
+ * serves (a dev or self-hosted `baseURL` is first-party for its own user).
39
+ */
40
+ function cliFirstPartyHosts(baseURL) {
41
+ const hosts = new Set(data_provider_1.FIRST_PARTY_HOSTS.map((host) => host.toLowerCase()));
42
+ if (baseURL) {
43
+ for (const origin of [baseURL, ...(0, web_url_1.firstPartyWebOrigins)(baseURL)]) {
44
+ const host = hostOf(origin);
45
+ if (host) {
46
+ hosts.add(host);
47
+ if (!host.startsWith('www.'))
48
+ hosts.add(`www.${host}`);
49
+ }
50
+ }
51
+ }
52
+ return [...hosts];
53
+ }
54
+ async function defaultResolveCode(code) {
55
+ try {
56
+ return await data_provider_1.dataService.resolveDistributionCode(code);
57
+ }
58
+ catch (err) {
59
+ if ((0, http_error_1.httpStatusOf)(err) === 404)
60
+ return null;
61
+ throw err;
62
+ }
63
+ }
64
+ async function defaultResolveVanity(handle, slug) {
65
+ try {
66
+ return await data_provider_1.dataService.resolveAgentVanity({ handle, slug });
67
+ }
68
+ catch (err) {
69
+ if ((0, http_error_1.httpStatusOf)(err) === 404)
70
+ return null;
71
+ throw err;
72
+ }
73
+ }
74
+ async function resolveByVanity(value, handle, slug, attribution, options) {
75
+ const resolved = await (options.resolveVanity ?? defaultResolveVanity)(handle, slug);
76
+ if (resolved?.entity?.kind !== 'agent' || !resolved.entity.id) {
77
+ throw new AgentRefError(`No public agent answers to "${value}". The publisher may have unpublished it; their ` +
78
+ `page lists what they publish: skrr open ${(0, data_provider_1.entityPath)({ kind: 'publisher', handle })}`, 'AGENT_REF_VANITY_UNRESOLVED');
79
+ }
80
+ // An alias is an address, not a share: it carries no attribution of its own.
81
+ return { agentId: resolved.entity.id, source: 'vanity', attribution };
82
+ }
83
+ function withChannel(attribution, channel) {
84
+ if (attribution.channel)
85
+ return attribution;
86
+ const normalized = (0, data_provider_1.normalizeDistributionChannel)(channel);
87
+ return normalized ? { ...attribution, channel: normalized } : attribution;
88
+ }
89
+ async function resolveByCode(value, attribution, options) {
90
+ const code = attribution.code;
91
+ const resolved = await (options.resolveCode ?? defaultResolveCode)(code);
92
+ if (!resolved?.entity) {
93
+ throw new AgentRefError(`The share code in "${value}" does not point at a public agent: it may have been ` +
94
+ 'unpublished, or the link was copied incompletely.', 'AGENT_REF_CODE_UNRESOLVED');
95
+ }
96
+ if (resolved.entity.kind !== 'agent') {
97
+ throw new AgentRefError(`"${value}" is a link to a ${resolved.entity.kind.replace(/_/g, ' ')}, not an agent.`, 'AGENT_REF_NOT_AN_AGENT');
98
+ }
99
+ return {
100
+ agentId: resolved.entity.id,
101
+ source: 'code',
102
+ // The link's own `ch` wins; the code's minted channel fills a link without one.
103
+ attribution: withChannel(attribution, resolved.channel),
104
+ };
105
+ }
106
+ async function resolveAgentRef(input, options = {}) {
107
+ const value = String(input ?? '').trim();
108
+ if (!value) {
109
+ throw new AgentRefError(`Name the agent: pass its id (agent_…) or a ${data_provider_1.BRAND.displayName} link to it.`, 'AGENT_REF_EMPTY');
110
+ }
111
+ if (AGENT_ID_TOKEN.test(value)) {
112
+ return { agentId: value, source: 'id', attribution: {} };
113
+ }
114
+ const inspected = (0, data_provider_1.inspectSkrrLink)(value, {
115
+ firstPartyHosts: cliFirstPartyHosts(options.baseURL),
116
+ });
117
+ if (!inspected) {
118
+ throw /^[a-z][a-z0-9+.-]*:\/\//i.test(value)
119
+ ? new AgentRefError(`"${value}" is not a ${data_provider_1.BRAND.displayName} link, so it was not followed. Pass an agent ` +
120
+ `id or a link on ${data_provider_1.BRAND.displayName}'s own site.`, 'AGENT_REF_FOREIGN')
121
+ : unsupported(value);
122
+ }
123
+ const { link, attribution } = inspected;
124
+ switch (link.kind) {
125
+ case 'agent':
126
+ case 'store_agent':
127
+ case 'agent_owner':
128
+ return { agentId: link.agentId, source: 'link', attribution };
129
+ case 'agent_vanity':
130
+ // A code on the link names the agent exactly; a bare alias is resolved
131
+ // by the server's vanity store.
132
+ if (attribution.code)
133
+ return resolveByCode(value, attribution, options);
134
+ return resolveByVanity(value, link.handle, link.slug, attribution, options);
135
+ case 'unknown':
136
+ if (attribution.code)
137
+ return resolveByCode(value, attribution, options);
138
+ throw unsupported(value);
139
+ default:
140
+ throw new AgentRefError(`"${value}" is a link to a ${link.kind.replace(/_/g, ' ')}, not an agent.`, 'AGENT_REF_NOT_AN_AGENT');
141
+ }
142
+ }
143
+ function unsupported(value) {
144
+ return new AgentRefError(`"${value}" is not an agent id or a link to an agent. Pass the id (agent_…) or its ` +
145
+ 'listing link (…/a/<id>).', 'AGENT_REF_UNSUPPORTED');
146
+ }
147
+ /**
148
+ * The canonical public address of a resolved agent, keeping the attribution it
149
+ * arrived with — what a command prints when it echoes a link back (an install
150
+ * command, an `open`), so following the echo credits the same share.
151
+ */
152
+ function attributedListingUrl(baseURL, ref) {
153
+ return (0, data_provider_1.buildShareUrl)((0, web_url_1.webOrigin)(baseURL), { kind: 'agent', id: ref.agentId }, {
154
+ code: ref.attribution.code,
155
+ channel: ref.attribution.channel,
156
+ });
157
+ }
158
+ /** The install body fields an attribution maps to; empty for an unattributed ref. */
159
+ function installAttribution(attribution) {
160
+ return {
161
+ ...(attribution.code ? { distributionCode: attribution.code } : {}),
162
+ ...(attribution.channel ? { channel: attribution.channel } : {}),
163
+ };
164
+ }
165
+ /** Quote a URL for a POSIX shell: `?` and `&` in an attributed link are metacharacters. */
166
+ function shellQuote(value) {
167
+ return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
168
+ }
@@ -37,6 +37,8 @@ export declare class ApiFetchError extends Error {
37
37
  * attributed by guesswork — see `node-adapter.ts`.
38
38
  */
39
39
  readonly requestId?: string;
40
+ /** Milliseconds the server asked us to wait (`Retry-After`) on a 429. */
41
+ retryAfterMs?: number;
40
42
  /**
41
43
  * Set on a 401 when this CLI's own refresh was refused AND the local
42
44
  * daemon, asked for a hand-off, refused too (OSK-12474). The daemon's reason
@@ -47,6 +47,7 @@ const daemonBroker_1 = require("./daemonBroker");
47
47
  const dpop_auth_1 = require("./dpop-auth");
48
48
  const workspace_header_1 = require("./workspace-header");
49
49
  const non_json_response_1 = require("./non-json-response");
50
+ const rate_limit_retry_1 = require("./rate-limit-retry");
50
51
  class ApiFetchError extends Error {
51
52
  status;
52
53
  body;
@@ -63,6 +64,8 @@ class ApiFetchError extends Error {
63
64
  * attributed by guesswork — see `node-adapter.ts`.
64
65
  */
65
66
  requestId;
67
+ /** Milliseconds the server asked us to wait (`Retry-After`) on a 429. */
68
+ retryAfterMs;
66
69
  /**
67
70
  * Set on a 401 when this CLI's own refresh was refused AND the local
68
71
  * daemon, asked for a hand-off, refused too (OSK-12474). The daemon's reason
@@ -186,13 +189,19 @@ async function apiFetch(pathOrUrl, opts = {}) {
186
189
  // OSK-12020 — a fresh proof per attempt for a DPoP-bound hand-off token
187
190
  // (the 401 retry below re-enters here and gets its own). No-op otherwise.
188
191
  Object.assign(headers, await (0, dpop_auth_1.dpopHeadersFor)(credential.token, method, url));
189
- return fetch(url, {
192
+ const sent = (0, workspace_header_1.withWorkspaceHeader)(headers);
193
+ // E6: a 429 on a GET or an idempotency-keyed write is retried (bounded,
194
+ // jittered, honouring Retry-After). The last 429's wait is kept for the error.
195
+ const { response, retryAfterMs } = await (0, rate_limit_retry_1.fetchWithRateLimitRetry)(() => fetch(url, {
190
196
  method,
191
- headers: (0, workspace_header_1.withWorkspaceHeader)(headers),
197
+ headers: sent,
192
198
  body,
193
199
  ...(opts.signal ? { signal: opts.signal } : {}),
194
- });
200
+ }), { method, headers: sent, signal: opts.signal });
201
+ lastRetryAfterMs = retryAfterMs;
202
+ return response;
195
203
  };
204
+ let lastRetryAfterMs;
196
205
  let res = await fetchWithCredential(cred);
197
206
  let brokerFailure = null;
198
207
  if (res.status === 401 && delegatedCli) {
@@ -271,6 +280,8 @@ async function apiFetch(pathOrUrl, opts = {}) {
271
280
  const error = new ApiFetchError(`HTTP ${res.status} ${res.statusText} — ${method} ${pathOrUrl}`, res.status, text, code, method, res.headers.get('x-request-id') || undefined);
272
281
  if (brokerFailure)
273
282
  error.brokerFailure = brokerFailure;
283
+ if (res.status === 429 && lastRetryAfterMs !== undefined)
284
+ error.retryAfterMs = lastRetryAfterMs;
274
285
  throw error;
275
286
  }
276
287
  // 204/205 carry no body by definition; an empty body has no content-type
@@ -26,6 +26,8 @@ export type CommandHygieneReport = {
26
26
  domains: number;
27
27
  budgetErrors: number;
28
28
  };
29
+ /** The verb rules the findings below are measured against (P8). */
30
+ namingGrammar: readonly string[];
29
31
  issues: CommandHygieneIssue[];
30
32
  };
31
33
  export declare function auditCommands({ rootDir, manifest, }: {
@@ -37,3 +39,14 @@ export declare function auditCommands({ rootDir, manifest, }: {
37
39
  */
38
40
  manifest?: CommandManifest;
39
41
  }): CommandHygieneReport;
42
+ /**
43
+ * The domains an agent drives most. A missing description or `--json` output
44
+ * here is a build error (P10); elsewhere it stays a warning until that domain's
45
+ * offenders are fixed, so the gate can ratchet outward one domain at a time.
46
+ */
47
+ export declare const ENFORCED_HYGIENE_DOMAINS: readonly ["tasks", "spaces", "projects"];
48
+ /**
49
+ * The verb grammar `commands --audit` checks, stated once so a reader of the
50
+ * report sees the rule and not only the findings it produced.
51
+ */
52
+ export declare const NAMING_GRAMMAR: readonly ["Verbs: list, show, create, update, delete, archive, unarchive, move. A reversible state flip is its own verb, never a flag on its inverse.", "Scope: a list verb takes its parent scope as an optional filter flag; a verb that cannot work without a scope says so in its description.", "Output: every command in tasks, spaces and projects takes --json and has a description (audit errors)."];
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.NAMING_GRAMMAR = exports.ENFORCED_HYGIENE_DOMAINS = void 0;
36
37
  exports.auditCommands = auditCommands;
37
38
  const fs = __importStar(require("node:fs"));
38
39
  const path = __importStar(require("node:path"));
@@ -120,6 +121,32 @@ function auditCommands({ rootDir, manifest, }) {
120
121
  });
121
122
  }
122
123
  }
124
+ // Without a generated manifest (a source checkout, most CI jobs) the
125
+ // manifest rules below never run, so the P10 gate for tasks/spaces/projects
126
+ // would pass by not looking. Read the same two facts off the source.
127
+ if (!manifest && isEnforcedDomain(command)) {
128
+ // A spread of a shared flag set (`...paginatedExportFlags(...)`) may carry
129
+ // --json from another file; only the manifest can tell, so do not guess.
130
+ const spreadsSharedFlags = /\.\.\.[\w.]*Flags\b/.test(source);
131
+ if (!hasJson && !spreadsSharedFlags && isMachineRelevantCommand(command)) {
132
+ issues.push({
133
+ code: 'JSON_SUCCESS_OUTPUT_MISSING',
134
+ severity: 'error',
135
+ command,
136
+ file: rel,
137
+ message: 'Machine-relevant command has no --json success-output flag. Keep human-only commands intentional.',
138
+ });
139
+ }
140
+ if (!/static (?:override )?description\s*=/.test(source)) {
141
+ issues.push({
142
+ code: 'COMMAND_DESCRIPTION_MISSING',
143
+ severity: 'error',
144
+ command,
145
+ file: rel,
146
+ message: 'Command is missing a description.',
147
+ });
148
+ }
149
+ }
123
150
  if (hasFromJson && !hasJson) {
124
151
  issues.push({
125
152
  code: 'FROM_JSON_WITHOUT_JSON_OUTPUT',
@@ -153,7 +180,7 @@ function auditCommands({ rootDir, manifest, }) {
153
180
  if (!command?.description?.trim()) {
154
181
  issues.push({
155
182
  code: 'COMMAND_DESCRIPTION_MISSING',
156
- severity: 'warning',
183
+ severity: isEnforcedDomain(id) ? 'error' : 'warning',
157
184
  command: id,
158
185
  message: 'Command is missing a description in the manifest.',
159
186
  });
@@ -161,11 +188,22 @@ function auditCommands({ rootDir, manifest, }) {
161
188
  if (!hasJsonFlag(command?.flags) && isMachineRelevantCommand(id)) {
162
189
  issues.push({
163
190
  code: 'JSON_SUCCESS_OUTPUT_MISSING',
164
- severity: 'warning',
191
+ severity: isEnforcedDomain(id) ? 'error' : 'warning',
165
192
  command: id,
166
193
  message: 'Machine-relevant command has no --json success-output flag. Keep human-only commands intentional.',
167
194
  });
168
195
  }
196
+ // P8: a state flip is a verb (`archive` / `unarchive`), not a flag on its
197
+ // inverse. `spaces unarchive` follows the grammar; `tasks archive
198
+ // --unarchive` and `projects archive --unarchive` do not.
199
+ if (isEnforcedDomain(id) && hasFlag(command?.flags, 'unarchive')) {
200
+ issues.push({
201
+ code: 'VERB_GRAMMAR_INVERSE_AS_FLAG',
202
+ severity: 'warning',
203
+ command: id,
204
+ message: `Naming grammar: a state flip is its own verb (\`${id.replace(/:archive$/, ':unarchive').replace(/:/g, ' ')}\`), not --unarchive on the forward verb. Keep the flag as a deprecated alias when adding the verb.`,
205
+ });
206
+ }
169
207
  }
170
208
  }
171
209
  const errors = issues.filter((issue) => issue.severity === 'error').length;
@@ -190,6 +228,7 @@ function auditCommands({ rootDir, manifest, }) {
190
228
  domains: (0, usage_discovery_1.usageDomains)(manifestForCatalog).length,
191
229
  budgetErrors: usageBudgetIssues.length,
192
230
  },
231
+ namingGrammar: exports.NAMING_GRAMMAR,
193
232
  issues,
194
233
  };
195
234
  }
@@ -213,6 +252,27 @@ function commandIdFromRelativePath(relativePath) {
213
252
  function hasJsonFlag(flags) {
214
253
  return Boolean(flags && typeof flags === 'object' && 'json' in flags);
215
254
  }
255
+ /**
256
+ * The domains an agent drives most. A missing description or `--json` output
257
+ * here is a build error (P10); elsewhere it stays a warning until that domain's
258
+ * offenders are fixed, so the gate can ratchet outward one domain at a time.
259
+ */
260
+ exports.ENFORCED_HYGIENE_DOMAINS = ['tasks', 'spaces', 'projects'];
261
+ function isEnforcedDomain(id) {
262
+ return exports.ENFORCED_HYGIENE_DOMAINS.some((domain) => id === domain || id.startsWith(`${domain}:`));
263
+ }
264
+ function hasFlag(flags, name) {
265
+ return Boolean(flags && typeof flags === 'object' && name in flags);
266
+ }
267
+ /**
268
+ * The verb grammar `commands --audit` checks, stated once so a reader of the
269
+ * report sees the rule and not only the findings it produced.
270
+ */
271
+ exports.NAMING_GRAMMAR = [
272
+ 'Verbs: list, show, create, update, delete, archive, unarchive, move. A reversible state flip is its own verb, never a flag on its inverse.',
273
+ 'Scope: a list verb takes its parent scope as an optional filter flag; a verb that cannot work without a scope says so in its description.',
274
+ 'Output: every command in tasks, spaces and projects takes --json and has a description (audit errors).',
275
+ ];
216
276
  function isMachineRelevantCommand(id) {
217
277
  return !(id === 'login' ||
218
278
  id === 'logout' ||
@@ -63,6 +63,11 @@ export declare function describeLastAutoStopLines(last: DedicatedRuntimePowerSum
63
63
  };
64
64
  /** The latest wake decision and who asked, as lines; empty when there is none. */
65
65
  export declare function describeLastWakeDecisionLines(wake: DedicatedRuntimePowerSummary['lastWakeDecision'], when?: WhenOptions): string[];
66
+ /**
67
+ * The announced-but-not-yet-done stop, in the shared words (`describePendingStop`). Empty when the
68
+ * summary carries none, which is also what an older server sends: absent is not "no stop coming".
69
+ */
70
+ export declare function describePendingStopLines(summary: Pick<DedicatedRuntimePowerSummary, 'leaseId' | 'pendingStop'>, when?: WhenOptions): string[];
66
71
  /** The lines of `power show`. */
67
72
  export declare function describePowerSummary(summary: DedicatedRuntimePowerSummary, when?: WhenOptions): string[];
68
73
  /** The lines of `power explain`; the fix is always last. */
@@ -12,6 +12,7 @@ exports.describePrewarm = describePrewarm;
12
12
  exports.describePowerDecisionLines = describePowerDecisionLines;
13
13
  exports.describeLastAutoStopLines = describeLastAutoStopLines;
14
14
  exports.describeLastWakeDecisionLines = describeLastWakeDecisionLines;
15
+ exports.describePendingStopLines = describePendingStopLines;
15
16
  exports.describePowerSummary = describePowerSummary;
16
17
  exports.describePowerExplanation = describePowerExplanation;
17
18
  exports.describeBudget = describeBudget;
@@ -50,7 +51,7 @@ async function setDedicatedPowerPolicy(leaseId, request, scope = {}) {
50
51
  return {
51
52
  ok: false,
52
53
  refusal: {
53
- error: String(body.error ?? 'on_demand is not available for this machine yet.'),
54
+ error: String(body.error ?? 'On demand is not available for this machine yet.'),
54
55
  code: 'ON_DEMAND_INELIGIBLE',
55
56
  reason: reason,
56
57
  detail: String(body.detail ?? copy?.detail ?? ''),
@@ -204,7 +205,10 @@ function describePrewarm(prewarm, when = {}) {
204
205
  function describePowerDecisionLines(decision) {
205
206
  const copy = (0, data_provider_1.describePowerReason)(decision.reason);
206
207
  const VERBS = { stay: 'Staying on', stop: 'Stopped', wake: 'Wake' };
207
- const verb = VERBS[decision.question] ?? decision.question;
208
+ // `stop_scheduled` answers the stop question for a machine that is still running.
209
+ const verb = decision.reason === 'stop_scheduled'
210
+ ? 'Scheduled to stop'
211
+ : (VERBS[decision.question] ?? decision.question);
208
212
  return {
209
213
  // The headline is the human label only; the reason token lives in `--json` and `power explain`.
210
214
  // With no shared copy and no server detail there is no sentence, so the token is the last resort.
@@ -275,6 +279,28 @@ function describeLastWakeDecisionLines(wake, when = {}) {
275
279
  lines.push(` ${wake.fix}`);
276
280
  return lines;
277
281
  }
282
+ /** What the Keep awake offered by the stop notice means in the CLI: two hours. */
283
+ const PENDING_STOP_KEEP_AWAKE_HOURS = 2;
284
+ /**
285
+ * The announced-but-not-yet-done stop, in the shared words (`describePendingStop`). Empty when the
286
+ * summary carries none, which is also what an older server sends: absent is not "no stop coming".
287
+ */
288
+ function describePendingStopLines(summary, when = {}) {
289
+ const pending = summary.pendingStop;
290
+ if (!pending)
291
+ return [];
292
+ const words = (0, data_provider_1.describePendingStop)(pending, {
293
+ nowMs: (when.now ?? new Date()).getTime(),
294
+ when: (0, format_1.formatWhen)(pending.stopsAt, when),
295
+ capWhen: pending.unreachableStopsAt ? (0, format_1.formatWhen)(pending.unreachableStopsAt, when) : null,
296
+ keepAwakeHours: PENDING_STOP_KEEP_AWAKE_HOURS,
297
+ });
298
+ const lines = [`Stop: ${words.headline}`];
299
+ if (words.detail)
300
+ lines.push(` ${words.detail}`);
301
+ lines.push(` ${words.fix} skrr machines dedicated power keep-awake ${summary.leaseId} --for ${PENDING_STOP_KEEP_AWAKE_HOURS}h`);
302
+ return lines;
303
+ }
278
304
  /** The lines of `power show`. */
279
305
  function describePowerSummary(summary, when = {}) {
280
306
  const { power } = summary;
@@ -303,6 +329,7 @@ function describePowerSummary(summary, when = {}) {
303
329
  lines.push('Decision: nothing to explain for this state');
304
330
  }
305
331
  }
332
+ lines.push(...describePendingStopLines(summary, when));
306
333
  lines.push(...describeLastAutoStopLines(summary.lastAutoStop, summary.decision?.detail, when).lines);
307
334
  lines.push(...describeLastWakeDecisionLines(summary.lastWakeDecision, when));
308
335
  if (summary.holders === null) {
@@ -347,6 +374,7 @@ function describePowerExplanation(summary, when = {}) {
347
374
  lines.push(`At: ${(0, format_1.formatWhen)(d.at, when)}`);
348
375
  if (text.detail)
349
376
  lines.push(`Detail: ${text.detail}`);
377
+ lines.push(...describePendingStopLines(summary, when));
350
378
  const stop = describeLastAutoStopLines(summary.lastAutoStop, d.detail, when);
351
379
  lines.push(...stop.lines);
352
380
  lines.push(...wakeLines);
@@ -0,0 +1,50 @@
1
+ type Row = Record<string, unknown>;
2
+ export type ProjectAction = 'archive' | 'unarchive' | 'delete';
3
+ export declare function planProjectChange(action: ProjectAction, project: Row, projectRef: string): {
4
+ object: string;
5
+ source: string;
6
+ ok: boolean;
7
+ action: ProjectAction;
8
+ project: {
9
+ id: unknown;
10
+ ref: {};
11
+ title: unknown;
12
+ archived: boolean;
13
+ };
14
+ changes: {
15
+ deletesProject: boolean;
16
+ detachedTasksEstimate: number | null;
17
+ reversible: boolean;
18
+ archived?: undefined;
19
+ } | {
20
+ archived: boolean;
21
+ reversible: boolean;
22
+ deletesProject?: undefined;
23
+ detachedTasksEstimate?: undefined;
24
+ };
25
+ errors: string[];
26
+ notes: string[];
27
+ };
28
+ export type BulkUpdateInput = {
29
+ id: string;
30
+ patch: Row;
31
+ };
32
+ export declare function planBulkUpdate(updates: BulkUpdateInput[], currentById: Record<string, Row | null | undefined>, failedReads?: Record<string, string>): {
33
+ object: string;
34
+ source: string;
35
+ ok: boolean;
36
+ requested: number;
37
+ wouldChange: number;
38
+ invalid: number;
39
+ results: {
40
+ id: string;
41
+ ok: boolean;
42
+ changes: Record<string, {
43
+ from: unknown;
44
+ to: unknown;
45
+ }>;
46
+ noop: boolean;
47
+ errors: string[];
48
+ }[];
49
+ };
50
+ export {};
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.planProjectChange = planProjectChange;
4
+ exports.planBulkUpdate = planBulkUpdate;
5
+ /**
6
+ * Client-side validated previews for `--dry-run` (audit P5).
7
+ *
8
+ * Each plan is built from READS only (a GET of the current row) and answers three
9
+ * things a caller needs before a destructive call: would it be accepted, what
10
+ * would change, and what is the blast radius. It is a preview, not a promise:
11
+ * the real call re-validates server-side, and a concurrent writer can still
12
+ * change the outcome. Where the server has no validate-only path the plan says
13
+ * `source: 'client_preview'` so nobody mistakes it for one.
14
+ */
15
+ const data_provider_1 = require("@skrr-ai/data-provider");
16
+ function planProjectChange(action, project, projectRef) {
17
+ const archived = Boolean(project.archivedAt);
18
+ const taskCounts = (project.taskCounts ?? {});
19
+ const errors = [];
20
+ const notes = [];
21
+ if (action === 'archive' && archived)
22
+ notes.push('Already archived; archiving again is a no-op.');
23
+ if (action === 'unarchive' && !archived)
24
+ notes.push('Not archived; unarchiving is a no-op.');
25
+ return {
26
+ object: 'project_change_dry_run',
27
+ source: 'client_preview',
28
+ ok: errors.length === 0,
29
+ action,
30
+ project: {
31
+ id: project.id,
32
+ ref: project.identifier ?? projectRef,
33
+ title: project.title,
34
+ archived,
35
+ },
36
+ changes: action === 'delete'
37
+ ? {
38
+ deletesProject: true,
39
+ // Member tasks are detached, never deleted (invariant P3).
40
+ detachedTasksEstimate: taskCounts.total ?? null,
41
+ reversible: false,
42
+ }
43
+ : { archived: action === 'archive', reversible: true },
44
+ errors,
45
+ notes,
46
+ };
47
+ }
48
+ function planBulkUpdate(updates, currentById, failedReads = {}) {
49
+ const rows = updates.map(({ id, patch }) => {
50
+ const errors = [];
51
+ const current = currentById[id];
52
+ if (failedReads[id])
53
+ errors.push(`could not read task: ${failedReads[id]}`);
54
+ else if (!current)
55
+ errors.push('task not found');
56
+ const status = patch.status;
57
+ if (status !== undefined && !data_provider_1.TASK_STATUSES.includes(status)) {
58
+ errors.push(`status must be one of: ${data_provider_1.TASK_STATUSES.join(', ')}`);
59
+ }
60
+ const changes = {};
61
+ for (const [field, to] of Object.entries(patch)) {
62
+ if (to === undefined)
63
+ continue;
64
+ const from = current?.[field];
65
+ if (JSON.stringify(from) !== JSON.stringify(to))
66
+ changes[field] = { from, to };
67
+ }
68
+ return {
69
+ id,
70
+ ok: errors.length === 0,
71
+ changes,
72
+ noop: errors.length === 0 && Object.keys(changes).length === 0,
73
+ errors,
74
+ };
75
+ });
76
+ return {
77
+ object: 'task_bulk_update_dry_run',
78
+ source: 'client_preview',
79
+ ok: rows.every((r) => r.ok),
80
+ requested: rows.length,
81
+ wouldChange: rows.filter((r) => r.ok && !r.noop).length,
82
+ invalid: rows.filter((r) => !r.ok).length,
83
+ results: rows,
84
+ };
85
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Storage-field stripping for `--json` output (audit P9).
3
+ *
4
+ * The API returns Mongo documents, so every row carries `_id` and `__v` beside
5
+ * the real `id`: 47 keys per task row, two of which are storage detail an agent
6
+ * pays tokens for and can mistake for the identifier (OSK-12205 was exactly
7
+ * that: a 24-hex `_id` passed where a task id was wanted).
8
+ *
9
+ * This runs at the CLI SERIALIZER only (`BaseCommand.log`), never in the API or
10
+ * the data layer, so no other consumer's payload changes. `--raw` (or
11
+ * `OVERSKY_CLI_RAW=1`) turns it off for the caller that does want them.
12
+ *
13
+ * Two rules, both conservative:
14
+ * - `__v` is the Mongoose version key and is always dropped.
15
+ * - `_id` is dropped only when the same object has a usable `id`. A row that
16
+ * has ONLY `_id` (some legacy collections) keeps it: removing the sole
17
+ * identifier would turn a cosmetic cleanup into data loss.
18
+ */
19
+ type Json = unknown;
20
+ /**
21
+ * Returns the SAME reference when nothing needed stripping, so a caller can tell
22
+ * "unchanged" apart from "rebuilt" and leave the original text alone.
23
+ */
24
+ export declare function stripStorageFields(value: Json): Json;
25
+ /**
26
+ * Apply {@link stripStorageFields} to one line of CLI output. Anything that is
27
+ * not a JSON object/array (prose, a bare string, malformed text) is returned
28
+ * untouched, as is JSON with nothing to strip, so existing formatting survives.
29
+ */
30
+ export declare function stripStorageFieldsFromText(text: string): string;
31
+ export {};