@skrr-ai/cli 0.1.13 → 0.1.15

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 (136) hide show
  1. package/dist/base-command.d.ts +12 -36
  2. package/dist/base-command.js +96 -11
  3. package/dist/commands/agents/create.d.ts +1 -0
  4. package/dist/commands/agents/create.js +6 -1
  5. package/dist/commands/agents/instructions/add.d.ts +21 -0
  6. package/dist/commands/agents/instructions/add.js +115 -0
  7. package/dist/commands/agents/instructions/disable.d.ts +13 -0
  8. package/dist/commands/agents/instructions/disable.js +50 -0
  9. package/dist/commands/agents/instructions/edit.d.ts +16 -0
  10. package/dist/commands/agents/instructions/edit.js +79 -0
  11. package/dist/commands/agents/instructions/enable.d.ts +13 -0
  12. package/dist/commands/agents/instructions/enable.js +50 -0
  13. package/dist/commands/agents/instructions/move.d.ts +15 -0
  14. package/dist/commands/agents/instructions/move.js +61 -0
  15. package/dist/commands/agents/instructions/preview.d.ts +13 -0
  16. package/dist/commands/agents/instructions/preview.js +59 -0
  17. package/dist/commands/agents/instructions/remove.d.ts +13 -0
  18. package/dist/commands/agents/instructions/remove.js +49 -0
  19. package/dist/commands/agents/instructions/set.d.ts +17 -0
  20. package/dist/commands/agents/instructions/set.js +79 -0
  21. package/dist/commands/agents/instructions/show.d.ts +15 -0
  22. package/dist/commands/agents/instructions/show.js +76 -0
  23. package/dist/commands/agents/instructions/upgrade.d.ts +16 -0
  24. package/dist/commands/agents/instructions/upgrade.js +77 -0
  25. package/dist/commands/agents/list.d.ts +10 -0
  26. package/dist/commands/agents/list.js +16 -1
  27. package/dist/commands/agents/show.js +5 -0
  28. package/dist/commands/agents/triggers/policy.js +8 -2
  29. package/dist/commands/agents/update.d.ts +1 -0
  30. package/dist/commands/agents/update.js +8 -2
  31. package/dist/commands/browser.js +5 -5
  32. package/dist/commands/commitments/create.d.ts +2 -0
  33. package/dist/commands/commitments/create.js +30 -13
  34. package/dist/commands/commitments/update.d.ts +2 -0
  35. package/dist/commands/commitments/update.js +24 -4
  36. package/dist/commands/daemon/byok.js +5 -5
  37. package/dist/commands/daemon/index.js +5 -3
  38. package/dist/commands/daemon/install.js +8 -5
  39. package/dist/commands/daemon/login.js +5 -5
  40. package/dist/commands/daemon/logs.js +5 -5
  41. package/dist/commands/daemon/recover-offline-queue.d.ts +9 -0
  42. package/dist/commands/daemon/recover-offline-queue.js +24 -0
  43. package/dist/commands/daemon/restart.js +5 -5
  44. package/dist/commands/daemon/start.js +34 -6
  45. package/dist/commands/daemon/status.js +5 -5
  46. package/dist/commands/daemon/stop.js +5 -5
  47. package/dist/commands/daemon/uninstall.js +5 -5
  48. package/dist/commands/daemon/unlock.js +5 -5
  49. package/dist/commands/daemon/usage.js +6 -6
  50. package/dist/commands/instruction-blocks/create.d.ts +19 -0
  51. package/dist/commands/instruction-blocks/create.js +94 -0
  52. package/dist/commands/instruction-blocks/delete.d.ts +13 -0
  53. package/dist/commands/instruction-blocks/delete.js +35 -0
  54. package/dist/commands/instruction-blocks/list.d.ts +13 -0
  55. package/dist/commands/instruction-blocks/list.js +71 -0
  56. package/dist/commands/instruction-blocks/show.d.ts +12 -0
  57. package/dist/commands/instruction-blocks/show.js +35 -0
  58. package/dist/commands/instruction-blocks/update.d.ts +23 -0
  59. package/dist/commands/instruction-blocks/update.js +94 -0
  60. package/dist/commands/login.js +7 -2
  61. package/dist/commands/spaces/auto-executor/preview.d.ts +1 -0
  62. package/dist/commands/spaces/auto-executor/preview.js +62 -3
  63. package/dist/commands/spaces/auto-executor/update.d.ts +4 -0
  64. package/dist/commands/spaces/auto-executor/update.js +78 -7
  65. package/dist/commands/spaces/capacity.js +14 -2
  66. package/dist/commands/spaces/list.d.ts +13 -0
  67. package/dist/commands/spaces/list.js +20 -1
  68. package/dist/commands/spaces/update.d.ts +4 -0
  69. package/dist/commands/spaces/update.js +49 -2
  70. package/dist/commands/tasks/complete.d.ts +12 -5
  71. package/dist/commands/tasks/complete.js +50 -28
  72. package/dist/commands/tasks/continue-ready.js +9 -1
  73. package/dist/commands/tasks/create.d.ts +1 -0
  74. package/dist/commands/tasks/create.js +25 -1
  75. package/dist/commands/tasks/deliverable/add.d.ts +23 -0
  76. package/dist/commands/tasks/deliverable/add.js +70 -0
  77. package/dist/commands/tasks/events/append.d.ts +1 -1
  78. package/dist/commands/tasks/events/append.js +4 -4
  79. package/dist/commands/tasks/expectations.d.ts +12 -0
  80. package/dist/commands/tasks/expectations.js +92 -0
  81. package/dist/commands/tasks/judge.d.ts +29 -0
  82. package/dist/commands/tasks/judge.js +68 -0
  83. package/dist/commands/tasks/list.d.ts +11 -0
  84. package/dist/commands/tasks/list.js +82 -11
  85. package/dist/commands/tasks/report.d.ts +1 -1
  86. package/dist/commands/tasks/report.js +3 -3
  87. package/dist/commands/tasks/update.d.ts +1 -0
  88. package/dist/commands/tasks/update.js +34 -4
  89. package/dist/commands/tasks/waive.d.ts +25 -0
  90. package/dist/commands/tasks/waive.js +58 -0
  91. package/dist/commands/wiki/cat.js +2 -1
  92. package/dist/commands/wiki/mv.js +5 -1
  93. package/dist/commands/wiki/write.js +5 -1
  94. package/dist/lib/agent-config.js +5 -0
  95. package/dist/lib/agent-instructions.d.ts +15 -0
  96. package/dist/lib/agent-instructions.js +55 -0
  97. package/dist/lib/api-fetch.d.ts +2 -1
  98. package/dist/lib/api-fetch.js +9 -2
  99. package/dist/lib/command-hygiene.d.ts +6 -1
  100. package/dist/lib/command-hygiene.js +49 -29
  101. package/dist/lib/commitments.d.ts +44 -0
  102. package/dist/lib/commitments.js +123 -0
  103. package/dist/lib/daemon-binding.d.ts +3 -4
  104. package/dist/lib/daemon-binding.js +11 -10
  105. package/dist/lib/daemon-handoff-opt-out.d.ts +8 -0
  106. package/dist/lib/daemon-handoff-opt-out.js +17 -0
  107. package/dist/lib/daemon-installer.d.ts +1 -1
  108. package/dist/lib/daemon-installer.js +2 -2
  109. package/dist/lib/daemon-setup.js +13 -7
  110. package/dist/lib/daemonHandoff.d.ts +1 -1
  111. package/dist/lib/daemonHandoff.js +20 -7
  112. package/dist/lib/delegated-cli.d.ts +0 -9
  113. package/dist/lib/delegated-cli.js +149 -6
  114. package/dist/lib/{exec-oversky.d.ts → exec-runtime-binary.d.ts} +28 -4
  115. package/dist/lib/{exec-oversky.js → exec-runtime-binary.js} +138 -37
  116. package/dist/lib/git-task.d.ts +37 -0
  117. package/dist/lib/git-task.js +151 -0
  118. package/dist/lib/instruction-blocks.d.ts +29 -0
  119. package/dist/lib/instruction-blocks.js +159 -0
  120. package/dist/lib/node-adapter.js +6 -0
  121. package/dist/lib/runtime-start-verification.d.ts +49 -0
  122. package/dist/lib/runtime-start-verification.js +108 -0
  123. package/dist/lib/sky-code.d.ts +1 -1
  124. package/dist/lib/sky-code.js +1 -1
  125. package/dist/lib/task-execution.d.ts +18 -2
  126. package/dist/lib/task-execution.js +19 -14
  127. package/dist/lib/task-transcript.js +1 -1
  128. package/dist/lib/tasks.d.ts +50 -0
  129. package/dist/lib/tasks.js +66 -1
  130. package/dist/lib/usage-discovery.js +1 -1
  131. package/dist/lib/web-url.d.ts +16 -0
  132. package/dist/lib/web-url.js +25 -0
  133. package/dist/node_modules/@skrr-ai/data-provider/index.js +4075 -3905
  134. package/dist/node_modules/@skrr-ai/inference-broker/package.json +1 -1
  135. package/oclif.manifest.json +12364 -10488
  136. package/package.json +4 -1
@@ -5,12 +5,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.RUNTIME_BINARY_NAMES = void 0;
7
7
  exports.forwardedFlagsNote = forwardedFlagsNote;
8
- exports.findOverskyBinary = findOverskyBinary;
8
+ exports.findRuntimeBinary = findRuntimeBinary;
9
+ exports.isCurrentRuntimeBinary = isCurrentRuntimeBinary;
10
+ exports.normalizeRuntimeGlobalArgs = normalizeRuntimeGlobalArgs;
9
11
  exports.notInstalledMessage = notInstalledMessage;
10
- exports.execOversky = execOversky;
11
- exports.overskySupportsSubcommand = overskySupportsSubcommand;
12
+ exports.execRuntimeBinary = execRuntimeBinary;
13
+ exports.execRuntimeBinaryCapture = execRuntimeBinaryCapture;
14
+ exports.runtimeBinarySupportsSubcommand = runtimeBinarySupportsSubcommand;
12
15
  /**
13
- * exec-oversky.ts — locate and forward to the local runtime binary.
16
+ * exec-runtime-binary.ts — locate and forward to the local runtime binary.
14
17
  *
15
18
  * The `gh → git` pattern: `skrr daemon ...` is a thin proxy. `skrr` is a REST
16
19
  * client; `skrrd` is the long-running runtime. Keeping them as separate
@@ -60,9 +63,13 @@ exports.RUNTIME_BINARY_NAMES = ['skrrd', 'oversky'];
60
63
  * `description` alone.
61
64
  */
62
65
  function forwardedFlagsNote(verb, opts = {}) {
63
- const base = `Flags are forwarded to \`skrrd ${verb}\` unchanged, so they are not listed here —` +
64
- ` run \`skrrd ${verb} --help\` for this verb's own flags, or \`skrrd --help\` for` +
65
- ' global ones such as `--profile`.';
66
+ const base = opts.bootstrapsRuntime
67
+ ? `If no local runtime exists, this command first fetches the signed release. After it is ` +
68
+ `installed, flags are forwarded to \`skrrd ${verb}\` unchanged; run \`skrrd ${verb} --help\` ` +
69
+ `for this verb's own flags, or \`skrrd --help\` for global ones such as \`--profile\`.`
70
+ : `Flags are forwarded to \`skrrd ${verb}\` unchanged, so they are not listed here —` +
71
+ ` run \`skrrd ${verb} --help\` for this verb's own flags, or \`skrrd --help\` for` +
72
+ ' global ones such as `--profile`.';
66
73
  // `install` and `login` are not pure pass-throughs: they fill in the server
67
74
  // and profile from the CLI's own session when the caller named neither, so a
68
75
  // daemon cannot silently end up on a different deployment than the CLI that
@@ -72,41 +79,91 @@ function forwardedFlagsNote(verb, opts = {}) {
72
79
  ? `${base} \`--server\` and \`--profile\` are filled in from your CLI session unless you pass them.`
73
80
  : base;
74
81
  }
75
- /** Extra well-known install locations to check when PATH lookup misses. */
76
- const FALLBACK_PATHS = exports.RUNTIME_BINARY_NAMES.flatMap((name) => [
77
- `/opt/homebrew/bin/${name}`,
78
- `/usr/local/bin/${name}`,
79
- // Linux script-installer default; the daemon's install script drops here.
80
- `/usr/local/bin/${name}-daemon`,
81
- // EC2 / pm2 layout used in daemon/CLAUDE.md examples.
82
- `${process.env.HOME ?? ''}/.skrr/bin/${name}`,
83
- ]);
84
- /** Resolve the absolute path of `oversky` if it exists, else return null. */
85
- function findOverskyBinary() {
82
+ /** Extra well-known install locations for one runtime name, checked after PATH. */
83
+ function fallbackPathsForRuntime(name) {
84
+ return [
85
+ `/opt/homebrew/bin/${name}`,
86
+ `/usr/local/bin/${name}`,
87
+ // Linux script-installer default; the daemon's install script drops here.
88
+ `/usr/local/bin/${name}-daemon`,
89
+ // EC2 / pm2 layout used in daemon/CLAUDE.md examples.
90
+ `${process.env.HOME ?? ''}/.skrr/bin/${name}`,
91
+ ];
92
+ }
93
+ /** Resolve the local runtime binary — `skrrd`, or the legacy `oversky` still on older PATHs. */
94
+ function findRuntimeBinary() {
86
95
  // PATH lookup — Node 20 doesn't ship `which()` but we can mimic it.
87
96
  // On Windows, npm globals install a `.cmd` wrapper (not `.exe`) so we
88
97
  // must check both; installers that ship a native build still win
89
98
  // because `.exe` is checked first.
90
99
  const PATH = process.env.PATH ?? '';
91
100
  const sep = process.platform === 'win32' ? ';' : ':';
92
- const exeNames = process.platform === 'win32'
93
- ? exports.RUNTIME_BINARY_NAMES.flatMap((n) => [`${n}.exe`, `${n}.cmd`])
94
- : [...exports.RUNTIME_BINARY_NAMES];
95
- for (const dir of PATH.split(sep)) {
96
- if (!dir)
97
- continue;
98
- for (const exeName of exeNames) {
99
- const candidate = node_path_1.default.join(dir, exeName);
100
- if (isExecutable(candidate))
101
+ const executableNamesFor = (name) => process.platform === 'win32' ? [`${name}.exe`, `${name}.cmd`] : [name];
102
+ // Check EVERY location for the current name before falling back to the
103
+ // legacy name. The former loop was directory-first, so an `oversky` binary
104
+ // in an early PATH entry won over a current `skrrd` in a later managed
105
+ // location. That stranded onboarding on the old binary precisely when it
106
+ // needed the current runtime to bootstrap its service.
107
+ const dirs = PATH.split(sep).filter(Boolean);
108
+ for (const runtimeName of exports.RUNTIME_BINARY_NAMES) {
109
+ for (const exeName of executableNamesFor(runtimeName)) {
110
+ for (const dir of dirs) {
111
+ const candidate = node_path_1.default.join(dir, exeName);
112
+ if (isExecutable(candidate))
113
+ return candidate;
114
+ }
115
+ }
116
+ for (const candidate of fallbackPathsForRuntime(runtimeName)) {
117
+ if (candidate && isExecutable(candidate))
101
118
  return candidate;
102
119
  }
103
120
  }
104
- for (const candidate of FALLBACK_PATHS) {
105
- if (candidate && isExecutable(candidate))
106
- return candidate;
107
- }
108
121
  return null;
109
122
  }
123
+ /** Whether a resolved path names the current runtime rather than legacy oversky. */
124
+ function isCurrentRuntimeBinary(binary) {
125
+ if (!binary)
126
+ return false;
127
+ const basename = node_path_1.default.basename(binary).toLowerCase();
128
+ return process.platform === 'win32'
129
+ ? basename === 'skrrd.exe' || basename === 'skrrd.cmd'
130
+ : basename === 'skrrd';
131
+ }
132
+ /**
133
+ * Commander reads runtime global options before the subcommand. The CLI
134
+ * wrappers naturally receive pass-through flags after a verb (`install
135
+ * --profile work`), which otherwise silently resolves the runtime profile as
136
+ * default. Move a well-formed profile flag ahead of the verb without changing
137
+ * the order of any command-specific flags or `--` payload.
138
+ */
139
+ function normalizeRuntimeGlobalArgs(args) {
140
+ const rest = [];
141
+ let profile = null;
142
+ for (let index = 0; index < args.length; index += 1) {
143
+ const arg = args[index];
144
+ if (arg === '--') {
145
+ rest.push(...args.slice(index));
146
+ break;
147
+ }
148
+ if (arg === '--profile') {
149
+ const value = args[index + 1];
150
+ if (value && !value.startsWith('-')) {
151
+ profile = value;
152
+ index += 1;
153
+ continue;
154
+ }
155
+ }
156
+ if (arg.startsWith('--profile=')) {
157
+ const value = arg.slice('--profile='.length);
158
+ if (value) {
159
+ profile = value;
160
+ continue;
161
+ }
162
+ }
163
+ rest.push(arg);
164
+ }
165
+ return profile ? ['--profile', profile, ...rest] : rest;
166
+ }
110
167
  function isExecutable(p) {
111
168
  try {
112
169
  (0, node_fs_1.accessSync)(p, node_fs_1.constants.X_OK);
@@ -155,11 +212,11 @@ function notInstalledMessage() {
155
212
  * preserving "exit code = the daemon's exit code" semantics that scripts
156
213
  * may depend on.
157
214
  */
158
- function execOversky(args) {
215
+ function execRuntimeBinary(args) {
159
216
  return new Promise((resolve, reject) => {
160
- const bin = findOverskyBinary();
217
+ const bin = findRuntimeBinary();
161
218
  if (!bin) {
162
- reject(new Error('oversky-not-installed'));
219
+ reject(new Error('runtime-binary-not-installed'));
163
220
  return;
164
221
  }
165
222
  // On Windows, `.cmd` / `.bat` wrappers cannot be executed directly by
@@ -169,7 +226,7 @@ function execOversky(args) {
169
226
  // by package.json engines `>=20.0.0` — pin at install time) quotes
170
227
  // args when shell:true on Windows per CVE-2024-27980's fix.
171
228
  const isWinShim = process.platform === 'win32' && /\.(cmd|bat)$/i.test(bin);
172
- const child = (0, node_child_process_1.spawn)(bin, args, {
229
+ const child = (0, node_child_process_1.spawn)(bin, normalizeRuntimeGlobalArgs(args), {
173
230
  stdio: 'inherit',
174
231
  ...(isWinShim ? { shell: true } : {}),
175
232
  });
@@ -186,6 +243,50 @@ function execOversky(args) {
186
243
  });
187
244
  });
188
245
  }
246
+ /**
247
+ * Invoke the installed runtime without inheriting its streams. The regular
248
+ * lifecycle command still owns the terminal; this is only for the wrapper's
249
+ * post-condition probes, where output is evidence rather than user-facing
250
+ * command output.
251
+ */
252
+ function execRuntimeBinaryCapture(args) {
253
+ return new Promise((resolve, reject) => {
254
+ const bin = findRuntimeBinary();
255
+ if (!bin) {
256
+ reject(new Error('runtime-binary-not-installed'));
257
+ return;
258
+ }
259
+ const isWinShim = process.platform === 'win32' && /\.(cmd|bat)$/i.test(bin);
260
+ const child = (0, node_child_process_1.spawn)(bin, normalizeRuntimeGlobalArgs(args), {
261
+ stdio: ['ignore', 'pipe', 'pipe'],
262
+ ...(isWinShim ? { shell: true } : {}),
263
+ });
264
+ let stdout = '';
265
+ let stderr = '';
266
+ // A status response is tiny. Keep a broken runtime from making its wrapper
267
+ // retain an unbounded stream merely to decide whether `start` worked.
268
+ const append = (current, chunk) => {
269
+ const limit = 256 * 1024;
270
+ if (current.length >= limit)
271
+ return current;
272
+ return (current + chunk.toString()).slice(0, limit);
273
+ };
274
+ child.stdout?.on('data', (chunk) => {
275
+ stdout = append(stdout, chunk);
276
+ });
277
+ child.stderr?.on('data', (chunk) => {
278
+ stderr = append(stderr, chunk);
279
+ });
280
+ child.on('error', reject);
281
+ child.on('exit', (code, signal) => {
282
+ resolve({
283
+ code: signal ? 128 + signalNumber(signal) : (code ?? 0),
284
+ stdout,
285
+ stderr,
286
+ });
287
+ });
288
+ });
289
+ }
189
290
  /**
190
291
  * Check an installed daemon's top-level help before forwarding a command that
191
292
  * was added after older daemon releases shipped. A false result is useful to
@@ -195,8 +296,8 @@ function execOversky(args) {
195
296
  * This invokes only `oversky --help`: it never starts, stops, configures, or
196
297
  * otherwise mutates the local daemon.
197
298
  */
198
- function overskySupportsSubcommand(subcommand) {
199
- const bin = findOverskyBinary();
299
+ function runtimeBinarySupportsSubcommand(subcommand) {
300
+ const bin = findRuntimeBinary();
200
301
  if (!bin)
201
302
  return Promise.resolve(null);
202
303
  return new Promise((resolve) => {
@@ -2,6 +2,42 @@ export type GitTaskBranch = {
2
2
  branch: string;
3
3
  taskRef: string;
4
4
  };
5
+ export type TaskDeliveryReceipt = {
6
+ kind: 'commit';
7
+ state: 'branch_ready' | 'integrated';
8
+ commitShas: string[];
9
+ headSha: string;
10
+ branch?: string;
11
+ integrationBranch?: string;
12
+ mergeCommit?: string;
13
+ } | {
14
+ kind: 'pull_request';
15
+ state: 'pr_open' | 'integrated';
16
+ prUrl: string;
17
+ branch?: string;
18
+ headSha?: string;
19
+ integrationBranch?: string;
20
+ mergeCommit?: string;
21
+ };
22
+ type CommandResult = {
23
+ status: number | null;
24
+ stdout: string;
25
+ stderr: string;
26
+ };
27
+ type CommandRunner = (command: string, args: string[], cwd: string) => CommandResult;
28
+ export declare function commitArtifacts(deliverables: string): string[];
29
+ /**
30
+ * Build a receipt for commit/PR deliverables before `tasks complete` writes
31
+ * anything. Commit reachability is checked against a freshly fetched remote
32
+ * default branch. A locally dangling commit is first anchored under refs/heads
33
+ * so even a held completion cannot leave it exposed to `git gc`.
34
+ */
35
+ export declare function inspectTaskDeliveryArtifacts(opts: {
36
+ deliverables: string;
37
+ taskRef: string;
38
+ cwd?: string;
39
+ runner?: CommandRunner;
40
+ }): TaskDeliveryReceipt | null;
5
41
  export declare class GitTaskError extends Error {
6
42
  readonly code: 'GIT_REPOSITORY_REQUIRED' | 'TASK_BRANCH_CONTEXT_MISSING' | 'GIT_BRANCH_SWITCH_FAILED';
7
43
  constructor(message: string, code: 'GIT_REPOSITORY_REQUIRED' | 'TASK_BRANCH_CONTEXT_MISSING' | 'GIT_BRANCH_SWITCH_FAILED');
@@ -23,3 +59,4 @@ export declare function checkoutTaskBranch(taskRef: string, cwd?: string): {
23
59
  branch: string;
24
60
  created: boolean;
25
61
  };
62
+ export {};
@@ -34,11 +34,162 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.GitTaskError = void 0;
37
+ exports.commitArtifacts = commitArtifacts;
38
+ exports.inspectTaskDeliveryArtifacts = inspectTaskDeliveryArtifacts;
37
39
  exports.currentGitBranch = currentGitBranch;
38
40
  exports.currentGitTaskBranch = currentGitTaskBranch;
39
41
  exports.checkoutTaskBranch = checkoutTaskBranch;
40
42
  const node_child_process_1 = require("node:child_process");
41
43
  const taskReferences = __importStar(require("@skrr-ai/data-provider"));
44
+ const COMMIT_ARTIFACT_RE = /(?:^|[\s,;:(/])(?:commit\s+)?([0-9a-f]{7,40})(?=$|[\s,;:)?#])/gi;
45
+ const GITHUB_PR_ARTIFACT_RE = /https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/\d+/i;
46
+ function runCommand(command, args, cwd) {
47
+ const result = (0, node_child_process_1.spawnSync)(command, args, {
48
+ cwd,
49
+ encoding: 'utf8',
50
+ stdio: ['ignore', 'pipe', 'pipe'],
51
+ timeout: 15_000,
52
+ });
53
+ return {
54
+ status: result.status,
55
+ stdout: typeof result.stdout === 'string' ? result.stdout.trim() : '',
56
+ stderr: typeof result.stderr === 'string' ? result.stderr.trim() : '',
57
+ };
58
+ }
59
+ function commitArtifacts(deliverables) {
60
+ const commits = [];
61
+ for (const match of deliverables.matchAll(COMMIT_ARTIFACT_RE)) {
62
+ const sha = match[1].toLowerCase();
63
+ if (!commits.includes(sha))
64
+ commits.push(sha);
65
+ }
66
+ return commits;
67
+ }
68
+ function safeTaskBranchRef(taskRef, sha) {
69
+ const match = taskRef.match(/[A-Za-z][A-Za-z0-9]{1,31}-\d{1,12}/);
70
+ const label = match?.[0].toUpperCase() || 'recovery';
71
+ return `oversky/task/${label}/recovery-${sha.slice(0, 12)}`;
72
+ }
73
+ function resolveRemoteDefault(cwd, remote, runner) {
74
+ const live = runner('git', ['ls-remote', '--symref', remote, 'HEAD'], cwd);
75
+ if (live.status === 0) {
76
+ const match = live.stdout.match(/^ref:\s+refs\/heads\/(\S+)\s+HEAD$/m);
77
+ if (match)
78
+ return match[1];
79
+ }
80
+ const symbolic = runner('git', ['symbolic-ref', '--quiet', '--short', `refs/remotes/${remote}/HEAD`], cwd);
81
+ if (symbolic.status === 0 && symbolic.stdout.startsWith(`${remote}/`)) {
82
+ return symbolic.stdout.slice(remote.length + 1);
83
+ }
84
+ for (const fallback of ['main', 'master']) {
85
+ if (runner('git', ['show-ref', '--verify', '--quiet', `refs/remotes/${remote}/${fallback}`], cwd)
86
+ .status === 0) {
87
+ return fallback;
88
+ }
89
+ }
90
+ return null;
91
+ }
92
+ /**
93
+ * Build a receipt for commit/PR deliverables before `tasks complete` writes
94
+ * anything. Commit reachability is checked against a freshly fetched remote
95
+ * default branch. A locally dangling commit is first anchored under refs/heads
96
+ * so even a held completion cannot leave it exposed to `git gc`.
97
+ */
98
+ function inspectTaskDeliveryArtifacts(opts) {
99
+ const cwd = opts.cwd ?? process.cwd();
100
+ const runner = opts.runner ?? runCommand;
101
+ const commits = commitArtifacts(opts.deliverables);
102
+ const verifiedCommits = [];
103
+ let containingBranch;
104
+ for (const claimed of commits) {
105
+ const resolved = runner('git', ['rev-parse', '--verify', `${claimed}^{commit}`], cwd);
106
+ if (resolved.status !== 0 || !/^[0-9a-f]{40}$/i.test(resolved.stdout))
107
+ continue;
108
+ const sha = resolved.stdout.toLowerCase();
109
+ verifiedCommits.push(sha);
110
+ const branches = runner('git', ['for-each-ref', '--format=%(refname:short)', '--contains', sha, 'refs/heads'], cwd);
111
+ const firstBranch = branches.stdout.split('\n').find(Boolean);
112
+ if (firstBranch) {
113
+ containingBranch ||= firstBranch;
114
+ continue;
115
+ }
116
+ const recoveryBranch = safeTaskBranchRef(opts.taskRef, sha);
117
+ const existing = runner('git', ['show-ref', '--verify', '--quiet', `refs/heads/${recoveryBranch}`], cwd);
118
+ if (existing.status !== 0) {
119
+ const anchored = runner('git', ['update-ref', `refs/heads/${recoveryBranch}`, sha], cwd);
120
+ if (anchored.status !== 0) {
121
+ throw new Error(`Commit ${sha} is not reachable from a named local branch, and ${recoveryBranch} ` +
122
+ 'could not be created. Completion was refused so the commit is not reported as safe.');
123
+ }
124
+ containingBranch ||= recoveryBranch;
125
+ }
126
+ else {
127
+ containingBranch ||= recoveryBranch;
128
+ }
129
+ }
130
+ const prUrl = opts.deliverables.match(GITHUB_PR_ARTIFACT_RE)?.[0];
131
+ if (prUrl) {
132
+ const viewed = runner('gh', [
133
+ 'pr',
134
+ 'view',
135
+ prUrl,
136
+ '--json',
137
+ 'mergedAt,headRefName,headRefOid,baseRefName,mergeCommit,url',
138
+ ], cwd);
139
+ if (viewed.status === 0) {
140
+ try {
141
+ const pr = JSON.parse(viewed.stdout);
142
+ const headSha = typeof pr.headRefOid === 'string' ? pr.headRefOid : undefined;
143
+ const mergeCommit = pr.mergeCommit && typeof pr.mergeCommit === 'object'
144
+ ? pr.mergeCommit.oid
145
+ : undefined;
146
+ return {
147
+ kind: 'pull_request',
148
+ state: pr.mergedAt ? 'integrated' : 'pr_open',
149
+ prUrl,
150
+ ...(typeof pr.headRefName === 'string' ? { branch: pr.headRefName } : {}),
151
+ ...(headSha && /^[0-9a-f]{7,40}$/i.test(headSha) ? { headSha } : {}),
152
+ ...(typeof pr.baseRefName === 'string' ? { integrationBranch: pr.baseRefName } : {}),
153
+ ...(typeof mergeCommit === 'string' && /^[0-9a-f]{7,40}$/i.test(mergeCommit)
154
+ ? { mergeCommit }
155
+ : {}),
156
+ };
157
+ }
158
+ catch {
159
+ // Invalid/unavailable gh evidence is not proof of integration.
160
+ }
161
+ }
162
+ return { kind: 'pull_request', state: 'pr_open', prUrl };
163
+ }
164
+ if (!commits.length || verifiedCommits.length !== commits.length)
165
+ return null;
166
+ const remotes = runner('git', ['remote'], cwd).stdout.split('\n').filter(Boolean);
167
+ const remote = remotes.includes('origin') ? 'origin' : remotes[0];
168
+ const integrationBranch = remote ? resolveRemoteDefault(cwd, remote, runner) : null;
169
+ let integrated = false;
170
+ if (remote && integrationBranch) {
171
+ const fetched = runner('git', [
172
+ 'fetch',
173
+ '--quiet',
174
+ '--no-tags',
175
+ remote,
176
+ `+refs/heads/${integrationBranch}:refs/remotes/${remote}/${integrationBranch}`,
177
+ ], cwd);
178
+ integrated =
179
+ fetched.status === 0 &&
180
+ verifiedCommits.every((sha) => runner('git', ['merge-base', '--is-ancestor', sha, `refs/remotes/${remote}/${integrationBranch}`], cwd).status === 0);
181
+ }
182
+ const headSha = verifiedCommits[verifiedCommits.length - 1];
183
+ return {
184
+ kind: 'commit',
185
+ state: integrated ? 'integrated' : 'branch_ready',
186
+ commitShas: verifiedCommits,
187
+ headSha,
188
+ ...(containingBranch ? { branch: containingBranch } : {}),
189
+ ...(integrationBranch ? { integrationBranch } : {}),
190
+ ...(integrated ? { mergeCommit: headSha } : {}),
191
+ };
192
+ }
42
193
  class GitTaskError extends Error {
43
194
  code;
44
195
  constructor(message, code) {
@@ -0,0 +1,29 @@
1
+ import type { AgentPromptBlockEntry, AgentPromptComposition, InstructionBlock, InstructionBlockCatalogResponse } from '@skrr-ai/data-provider';
2
+ /**
3
+ * Helpers shared by the reusable Block Library commands and the per-Agent
4
+ * composition commands. Keep this deliberately about data shape, not HTTP:
5
+ * the server remains the authority that resolves versions, bindings and
6
+ * rendered text before an Agent is saved.
7
+ */
8
+ export declare function parseJsonFlag<T>(value: string | undefined, flag: string): T | undefined;
9
+ export declare function parseStringArrayFlag(value: string | undefined, flag: string): string[] | undefined;
10
+ /** Parse repeated `--binding key=value` flags without ever guessing a key. */
11
+ export declare function parseBindings(values: string[] | undefined): Record<string, string> | undefined;
12
+ /**
13
+ * Read the structured source when it exists; otherwise preserve the legacy
14
+ * prompt byte-for-byte as one inline entry before changing anything. This is
15
+ * the same compatibility move as the server-side resolver, so `add` never
16
+ * drops an Agent's existing instructions merely because it predates Blocks.
17
+ */
18
+ export declare function compositionForAgent(agent: {
19
+ promptComposition?: unknown;
20
+ instructions?: unknown;
21
+ }): AgentPromptComposition;
22
+ export declare function nextInstanceId(kind?: 'inline' | 'library'): string;
23
+ export declare function entryByInstance(composition: AgentPromptComposition, instanceId: string): AgentPromptBlockEntry;
24
+ export declare function replaceEntry(composition: AgentPromptComposition, instanceId: string, replacement: AgentPromptBlockEntry): AgentPromptComposition;
25
+ export declare function removeEntry(composition: AgentPromptComposition, instanceId: string): AgentPromptComposition;
26
+ /** Move one instance immediately before or after a different existing instance. */
27
+ export declare function moveEntry(composition: AgentPromptComposition, instanceId: string, anchorId: string, placement: 'before' | 'after'): AgentPromptComposition;
28
+ export declare function findBlock(catalog: InstructionBlockCatalogResponse, reference: string): InstructionBlock | undefined;
29
+ export declare function renderInstructionBlockDetail(block: InstructionBlock): string[];
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseJsonFlag = parseJsonFlag;
4
+ exports.parseStringArrayFlag = parseStringArrayFlag;
5
+ exports.parseBindings = parseBindings;
6
+ exports.compositionForAgent = compositionForAgent;
7
+ exports.nextInstanceId = nextInstanceId;
8
+ exports.entryByInstance = entryByInstance;
9
+ exports.replaceEntry = replaceEntry;
10
+ exports.removeEntry = removeEntry;
11
+ exports.moveEntry = moveEntry;
12
+ exports.findBlock = findBlock;
13
+ exports.renderInstructionBlockDetail = renderInstructionBlockDetail;
14
+ const node_crypto_1 = require("node:crypto");
15
+ /**
16
+ * Helpers shared by the reusable Block Library commands and the per-Agent
17
+ * composition commands. Keep this deliberately about data shape, not HTTP:
18
+ * the server remains the authority that resolves versions, bindings and
19
+ * rendered text before an Agent is saved.
20
+ */
21
+ function parseJsonFlag(value, flag) {
22
+ if (value === undefined)
23
+ return undefined;
24
+ try {
25
+ return JSON.parse(value);
26
+ }
27
+ catch {
28
+ throw new Error(`${flag} must be valid JSON`);
29
+ }
30
+ }
31
+ function parseStringArrayFlag(value, flag) {
32
+ const parsed = parseJsonFlag(value, flag);
33
+ if (parsed === undefined)
34
+ return undefined;
35
+ if (!Array.isArray(parsed) || parsed.some((entry) => typeof entry !== 'string')) {
36
+ throw new Error(`${flag} must be a JSON array of strings`);
37
+ }
38
+ return parsed;
39
+ }
40
+ /** Parse repeated `--binding key=value` flags without ever guessing a key. */
41
+ function parseBindings(values) {
42
+ if (!values?.length)
43
+ return undefined;
44
+ const bindings = {};
45
+ for (const value of values) {
46
+ const separator = value.indexOf('=');
47
+ if (separator < 1) {
48
+ throw new Error(`Invalid binding "${value}". Use --binding key=value.`);
49
+ }
50
+ const key = value.slice(0, separator).trim();
51
+ if (!/^[a-z][a-z0-9_]{0,63}$/.test(key)) {
52
+ throw new Error(`Invalid binding key "${key}".`);
53
+ }
54
+ if (Object.prototype.hasOwnProperty.call(bindings, key)) {
55
+ throw new Error(`Binding "${key}" was supplied more than once.`);
56
+ }
57
+ bindings[key] = value.slice(separator + 1).trim();
58
+ }
59
+ return bindings;
60
+ }
61
+ /**
62
+ * Read the structured source when it exists; otherwise preserve the legacy
63
+ * prompt byte-for-byte as one inline entry before changing anything. This is
64
+ * the same compatibility move as the server-side resolver, so `add` never
65
+ * drops an Agent's existing instructions merely because it predates Blocks.
66
+ */
67
+ function compositionForAgent(agent) {
68
+ const composition = agent.promptComposition;
69
+ if (composition &&
70
+ typeof composition === 'object' &&
71
+ !Array.isArray(composition) &&
72
+ composition.schemaVersion === 1 &&
73
+ Array.isArray(composition.entries)) {
74
+ return composition;
75
+ }
76
+ const instructions = agent.instructions;
77
+ if (typeof instructions === 'string' && instructions.length) {
78
+ return {
79
+ schemaVersion: 1,
80
+ entries: [
81
+ {
82
+ instanceId: nextInstanceId('inline'),
83
+ enabled: true,
84
+ source: { kind: 'inline', title: 'Core instructions', body: instructions },
85
+ },
86
+ ],
87
+ };
88
+ }
89
+ return { schemaVersion: 1, entries: [] };
90
+ }
91
+ function nextInstanceId(kind = 'inline') {
92
+ return `${kind}_${(0, node_crypto_1.randomUUID)()}`;
93
+ }
94
+ function entryByInstance(composition, instanceId) {
95
+ const entry = composition.entries.find((candidate) => candidate.instanceId === instanceId);
96
+ if (!entry) {
97
+ throw new Error(`No instruction block instance "${instanceId}". Run \`skrr agents instructions show\` to list this Agent's instances.`);
98
+ }
99
+ return entry;
100
+ }
101
+ function replaceEntry(composition, instanceId, replacement) {
102
+ entryByInstance(composition, instanceId);
103
+ return {
104
+ schemaVersion: 1,
105
+ entries: composition.entries.map((entry) => entry.instanceId === instanceId ? replacement : entry),
106
+ };
107
+ }
108
+ function removeEntry(composition, instanceId) {
109
+ entryByInstance(composition, instanceId);
110
+ return {
111
+ schemaVersion: 1,
112
+ entries: composition.entries.filter((entry) => entry.instanceId !== instanceId),
113
+ };
114
+ }
115
+ /** Move one instance immediately before or after a different existing instance. */
116
+ function moveEntry(composition, instanceId, anchorId, placement) {
117
+ if (instanceId === anchorId)
118
+ throw new Error('A block cannot be moved relative to itself.');
119
+ const entry = entryByInstance(composition, instanceId);
120
+ entryByInstance(composition, anchorId);
121
+ const remaining = composition.entries.filter((candidate) => candidate.instanceId !== instanceId);
122
+ const anchor = remaining.findIndex((candidate) => candidate.instanceId === anchorId);
123
+ const insertAt = placement === 'before' ? anchor : anchor + 1;
124
+ return {
125
+ schemaVersion: 1,
126
+ entries: [...remaining.slice(0, insertAt), entry, ...remaining.slice(insertAt)],
127
+ };
128
+ }
129
+ function findBlock(catalog, reference) {
130
+ return [...catalog.official, ...catalog.library].find((block) => block.id === reference);
131
+ }
132
+ function renderInstructionBlockDetail(block) {
133
+ const current = block.versions.find((version) => version.version === block.currentVersion);
134
+ const lines = [
135
+ `${block.name} (${block.id})`,
136
+ `Source: ${block.source}`,
137
+ `Visibility: ${block.visibility}`,
138
+ `Status: ${block.status}`,
139
+ `Version: v${block.currentVersion}`,
140
+ ];
141
+ if (block.summary)
142
+ lines.push(`Summary: ${block.summary}`);
143
+ if (block.tags.length)
144
+ lines.push(`Tags: ${block.tags.join(', ')}`);
145
+ if (current) {
146
+ if (current.requirements.length) {
147
+ lines.push(`Needs: ${current.requirements
148
+ .map((requirement) => `${requirement.kind}:${requirement.key}${requirement.access ? ` (${requirement.access})` : ''}`)
149
+ .join(', ')} — declarative only; this does not grant access`);
150
+ }
151
+ if (current.parameterSchema.length) {
152
+ lines.push(`Parameters: ${current.parameterSchema
153
+ .map((parameter) => `${parameter.key}${parameter.required ? ' (required)' : ''}`)
154
+ .join(', ')}`);
155
+ }
156
+ lines.push('', 'Instructions:', ...current.bodyTemplate.split('\n').map((line) => ` ${line}`));
157
+ }
158
+ return lines;
159
+ }
@@ -317,9 +317,15 @@ function createNodeAdapter(opts = {}) {
317
317
  // can contain internal stack traces we don't want to echo to
318
318
  // terminals by default.
319
319
  const text = await res.text().catch(() => '');
320
+ // `method` is attached because the 403 branch of `handleApiError` needs
321
+ // to know whether the failing request even HAD a body before it can
322
+ // advise the user to "try again with less of the request body"
323
+ // (OSK-6765): `agents show` is a GET and had no body, so the advice
324
+ // could not be followed.
320
325
  const err = new Error(`HTTP ${res.status} ${res.statusText} — ${method} ${url}`);
321
326
  err.status = res.status;
322
327
  err.body = text;
328
+ err.method = method;
323
329
  throw err;
324
330
  }
325
331
  if (options?.responseType === 'arraybuffer') {