@krapht/ork 0.4.0 → 1.0.0-alpha.1

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 (37) hide show
  1. package/README.md +16 -12
  2. package/dist/{__build_info__-CtSVVuZE.mjs → __build_info__-D9lSlMFy.mjs} +36 -112
  3. package/dist/bin.mjs +688 -306
  4. package/dist/{clean-Dcr807be.mjs → clean-CA_Oi2Wl.mjs} +1 -1
  5. package/dist/completion-DBGfL-gN.mjs +405 -0
  6. package/dist/{context-C_bksb0C.mjs → context-6x3kXREO.mjs} +11 -88
  7. package/dist/decide-CoIJh34e.mjs +113 -0
  8. package/dist/{find-DptmbhGd.mjs → find-rBBk7Jui.mjs} +1 -1
  9. package/dist/{format-J-a21Yvn.mjs → format-zMmhqyLE.mjs} +71 -37
  10. package/dist/help-groups-BwYoAWsh.mjs +354 -0
  11. package/dist/{in-a-run-D1Qc5Mit.mjs → in-a-run-DdAakpRR.mjs} +1 -1
  12. package/dist/{limits-BDZCmGEu.mjs → limits-BPM9wUlV.mjs} +1 -1
  13. package/dist/{list-ZvY2OcLq.mjs → list-Bv5Lxu7z.mjs} +1 -1
  14. package/dist/{mcp-CHVKKg3B.mjs → mcp-DM30MFBw.mjs} +7 -7
  15. package/dist/{output-CXSJO00X.mjs → output-TV_j7Ni0.mjs} +67 -17
  16. package/dist/{pause-BPSmg4yz.mjs → pause-C7lJkSma.mjs} +4 -4
  17. package/dist/{relay-Do-aNbSD.mjs → relay-BipJYszF.mjs} +2 -2
  18. package/dist/{remove-AwaMMaHf.mjs → remove-BDs9isB6.mjs} +3 -3
  19. package/dist/{send-DhGBkowy.mjs → send-nbkjf3sc.mjs} +3 -3
  20. package/dist/{src-CpDKIulI.mjs → src-B0TT7DBi.mjs} +28 -15
  21. package/dist/{src-BHnxpJyH.mjs → src-B9v0lDc6.mjs} +58 -10
  22. package/dist/{src-UWkk1sl9.mjs → src-BxUJPGa5.mjs} +252 -18
  23. package/dist/{src-CGqMati3.mjs → src-hrj2h0IZ.mjs} +640 -220
  24. package/dist/ui/THIRD_PARTY_LICENSES.txt +2 -2
  25. package/dist/ui/assets/app-BNT0SuIz.css +1 -0
  26. package/dist/ui/assets/app-CmkynZyW.js +67 -0
  27. package/dist/ui/assets/{index-BiO54L90.css → index-BPIbqRRb.css} +1 -1
  28. package/dist/ui/assets/{index-MQNNgsyT.js → index-CPx74lFf.js} +2 -2
  29. package/dist/ui/index.html +2 -3
  30. package/dist/{ui-DhutUMgQ.mjs → ui-B98FSboa.mjs} +5 -5
  31. package/dist/{usage-CT7Z-yjr.mjs → usage-EW7DI6Z7.mjs} +6 -6
  32. package/dist/{watch-0Qr2UfCQ.mjs → watch-BAkSGaOp.mjs} +7 -7
  33. package/dist/workflows-vsEohqa7.mjs +88 -0
  34. package/dist/{worktrees-DHl80k96.mjs → worktrees-Ahj9rdWK.mjs} +5 -5
  35. package/package.json +3 -3
  36. package/dist/ui/assets/app-BgVDC_5l.js +0 -67
  37. package/dist/ui/assets/app-DVf68fj5.css +0 -1
@@ -1,15 +1,17 @@
1
- import { lt as liveStatus } from "./src-CGqMati3.mjs";
2
- import { i as style, n as cost } from "./output-CXSJO00X.mjs";
1
+ import { ht as liveStatus } from "./src-hrj2h0IZ.mjs";
2
+ import { i as style, n as cost } from "./output-TV_j7Ni0.mjs";
3
3
  import { createInterface } from "node:readline/promises";
4
4
  // src/human.ts
5
5
  /**
6
6
  * @file The terminal `HumanChannel`: answers `ask_user` questions, gates and
7
7
  * permission prompts on the terminal, one at a time, and denies or defers
8
- * when no one is there. In a run it is the decision queue's local answerer,
9
- * racing `ork decide`.
8
+ * when no one is there. After a choice it offers one optional line for a
9
+ * note, which Enter skips. In a run it is the decision queue's local
10
+ * answerer, racing `ork decide`.
10
11
  */
11
12
  const PREVIEW = 160;
12
13
  const NO_ONE_TO_PERMIT = "No one is at the terminal to approve this tool, so it is not allowed in this run.";
14
+ const NOTE_PROMPT = " Note for the run (optional, Enter to skip): ";
13
15
  const NO_ONE_TO_ASK = "no one (nobody is at the terminal; use your best judgment and say so in your report)";
14
16
  /** One line that shows what a tool is about to do. */
15
17
  const previewInput = (toolName, input) => {
@@ -55,6 +57,12 @@ const createTerminalHuman = (io) => {
55
57
  });
56
58
  return signal === void 0 ? rl.question(text) : rl.question(text, { signal });
57
59
  };
60
+ /** The optional line after a choice, when the asker takes a note. */
61
+ const takeNote = async (onNote, signal) => {
62
+ if (onNote === void 0) return;
63
+ const note = (await prompt(NOTE_PROMPT, signal)).trim();
64
+ if (note !== "") onNote(note);
65
+ };
58
66
  const choose = async (hint, options, recommended, signal) => {
59
67
  const picked = pickOption(await prompt(` Choose${hint}: `, signal), options, recommended);
60
68
  if (picked !== void 0) return picked;
@@ -67,7 +75,7 @@ const createTerminalHuman = (io) => {
67
75
  return result;
68
76
  };
69
77
  return {
70
- permit: ({ nodeId, toolName, input, signal }) => oneAtATime(async () => {
78
+ permit: ({ nodeId, toolName, input, signal, onNote }) => oneAtATime(async () => {
71
79
  if (signal?.aborted === true) throw new Error("the prompt was withdrawn");
72
80
  say(`${style(["yellow", "bold"], "?")} ${style("bold", nodeId)} wants to run ${previewInput(toolName, input)}`);
73
81
  if (!io.interactive) {
@@ -78,24 +86,28 @@ const createTerminalHuman = (io) => {
78
86
  };
79
87
  }
80
88
  const answer = (await prompt(" Allow? [y/N] ", signal)).trim().toLowerCase();
81
- return answer === "y" || answer === "yes" ? { allow: true } : {
89
+ if (answer === "y" || answer === "yes") return { allow: true };
90
+ await takeNote(onNote, signal);
91
+ return {
82
92
  allow: false,
83
93
  message: "The user did not allow this tool."
84
94
  };
85
95
  }),
86
- ask: ({ nodeId, question, options, recommended, context, signal }) => oneAtATime(() => {
87
- if (signal?.aborted === true) return Promise.reject(/* @__PURE__ */ new Error("the question was withdrawn"));
96
+ ask: ({ nodeId, question, options, recommended, context, signal, onNote }) => oneAtATime(async () => {
97
+ if (signal?.aborted === true) throw new Error("the question was withdrawn");
88
98
  say(`${style(["yellow", "bold"], "?")} ${style("bold", nodeId)} asks: ${question}`);
89
99
  if (context !== void 0) say(style("dim", ` ${context}`));
90
100
  for (const [index, option] of options.entries()) say(optionLine(option, index, recommended));
91
101
  if (!io.interactive) {
92
102
  const answer = recommended ?? NO_ONE_TO_ASK;
93
103
  say(style("dim", ` no terminal; answered ${answer}`));
94
- return Promise.resolve(answer);
104
+ return answer;
95
105
  }
96
106
  const fallback = options.findIndex((o) => o.id === recommended);
97
107
  const hint = fallback === -1 ? "" : ` [${String(fallback + 1)}]`;
98
- return choose(hint, options, recommended, signal);
108
+ const picked = await choose(hint, options, recommended, signal);
109
+ await takeNote(onNote, signal);
110
+ return picked;
99
111
  }),
100
112
  close: () => {
101
113
  rl?.close();
@@ -131,12 +143,38 @@ const formatBusEvent = (event) => {
131
143
  if (event.type === "owner.answered") return [style("dim", `→ ${event.answer} (owner)`)];
132
144
  return [style("dim", "→ its session ended first; asking you")];
133
145
  };
146
+ // src/run/format-result.ts
147
+ const RUN_MARK = {
148
+ succeeded: style("green", "✓"),
149
+ failed: style("red", "✗"),
150
+ budget_exhausted: style("yellow", "!"),
151
+ quota_exhausted: style("yellow", "!"),
152
+ timed_out: style("yellow", "!"),
153
+ interrupted: style("yellow", "!")
154
+ };
155
+ /** `1 failure handled`, `2 failures handled`. */
156
+ const failuresHandled = (count) => `${String(count)} ${count === 1 ? "failure" : "failures"} handled`;
157
+ /**
158
+ * The run summary: status, cost, what ran, what was skipped and which failures
159
+ * an edge handled, branches to look at, why, and mail to nodes that never ran.
160
+ */
161
+ const formatRunResult = (result) => {
162
+ const ran = result.nodes.filter((n) => n.runs > 0).length;
163
+ const skipped = result.skipped ?? result.nodes.length - ran;
164
+ const handled = result.failuresHandled ?? 0;
165
+ const lines = ["", `${RUN_MARK[result.status]} run ${result.runId} ${result.status.replace("_", " ")} ${style("dim", `· ${cost(result.costUsd)} · ${String(ran)} ran, ${String(skipped)} skipped${handled === 0 ? "" : `, ${failuresHandled(handled)}`}`)}`];
166
+ for (const kept of result.worktrees) lines.push(` ${kept.id}: ${kept.branch ?? "detached"} ${style("dim", `at ${kept.path} (${kept.reason})`)}`);
167
+ for (const reason of result.reasons) lines.push(` ${style("red", reason)}`);
168
+ for (const { node, messages } of result.unread ?? []) lines.push(` ${style(["yellow", "bold"], "warning")} ${node} never ran, so ${messages === 1 ? "1 message" : `${String(messages)} messages`} to it went unread`);
169
+ return lines;
170
+ };
134
171
  // src/run/format.ts
135
172
  /** Who answered a decision, as the log says it. */
136
173
  const ANSWERED_BY = {
137
174
  terminal: "terminal",
138
175
  decide: "ork decide",
139
- ui: "ork ui"
176
+ ui: "ork ui",
177
+ owner: "its owner"
140
178
  };
141
179
  const STATUS_MARK = {
142
180
  pass: style("green", "✓"),
@@ -170,7 +208,10 @@ const formatOutcome = (nodeId, outcome) => {
170
208
  };
171
209
  /** The transcript lines for one event; tool names come from `tools`. */
172
210
  const formatEvent = (nodeId, event, tools) => {
173
- if (event.type === "session.started") return [style("dim", `▸ ${nodeId} (${event.model}, session ${event.sessionId})`)];
211
+ if (event.type === "session.started") {
212
+ const fork = event.fork === true ? ", a fork to answer a question" : "";
213
+ return [style("dim", `▸ ${nodeId} (${event.model}, session ${event.sessionId}${fork})`)];
214
+ }
174
215
  if (event.type === "assistant.text") return indent(event.text);
175
216
  if (event.type === "tool.called") {
176
217
  tools.set(event.id, event.name);
@@ -201,8 +242,12 @@ const formatRunEvent = (event, width, tools) => {
201
242
  if (event.type === "node.event") {
202
243
  const inner = event.event;
203
244
  if (inner.type === "session.finished") return [];
204
- if (inner.type === "session.started") return [`${tag(event.id)} ${style("dim", `${inner.model}, session ${inner.sessionId}`)}`];
205
- return formatEvent(event.id, event.event, tools).map((line) => `${tag(event.id)} ${line.replace(/^ {2}/u, "")}`);
245
+ const fork = inner.fork === true ? style("dim", "fork · ") : "";
246
+ if (inner.type === "session.started") {
247
+ const why = inner.fork === true ? ", to answer a question" : "";
248
+ return [`${tag(event.id)} ${fork}${style("dim", `${inner.model}, session ${inner.sessionId}${why}`)}`];
249
+ }
250
+ return formatEvent(event.id, event.event, tools).map((line) => `${tag(event.id)} ${fork}${line.replace(/^ {2}/u, "")}`);
206
251
  }
207
252
  if (event.type === "node.finished") {
208
253
  const mark = event.ok ? style("green", "✓") : style("red", "✗");
@@ -223,11 +268,13 @@ const formatRunEvent = (event, width, tools) => {
223
268
  if (event.type === "decision.opened") {
224
269
  const what = event.kind === "permission" ? "asks to" : "asks:";
225
270
  const question = event.kind === "permission" ? event.question.replace(/^Allow /u, "run ").replace(/\?$/u, "") : event.question;
226
- return [`${tag(event.node)} ${style(["yellow", "bold"], "?")} ${what} ${question} ${style("dim", `(decision ${event.decision}; ork decide)`)}`];
271
+ const where = event.owner === void 0 ? `decision ${event.decision}; ork decide` : `decision ${event.decision}, via ${event.owner}`;
272
+ return [`${tag(event.node)} ${style(["yellow", "bold"], "?")} ${what} ${question} ${style("dim", `(${where})`)}`];
227
273
  }
228
274
  if (event.type === "decision.answered") {
229
- const by = ANSWERED_BY[event.by];
230
- return [`${tag(event.node)} ${style("dim", `→ ${event.answer} (${by})`)}`];
275
+ const by = event.by === "owner" && event.owner !== void 0 ? event.owner : ANSWERED_BY[event.by];
276
+ const said = event.note === void 0 ? "" : ` · said: ${event.note.replaceAll(/\s+/gu, " ")}`;
277
+ return [`${tag(event.node)} ${style("dim", `→ ${event.answer} (${by})${said}`)}`];
231
278
  }
232
279
  if (event.type === "decision.withdrawn") return [`${tag(event.node)} ${style("dim", "→ question withdrawn")}`];
233
280
  if (event.type === "run.command") {
@@ -256,23 +303,6 @@ const formatRunEvent = (event, width, tools) => {
256
303
  }
257
304
  return formatBusEvent(event).map((line) => `${tag(event.node)} ${line}`);
258
305
  };
259
- const RUN_MARK = {
260
- succeeded: style("green", "✓"),
261
- failed: style("red", "✗"),
262
- budget_exhausted: style("yellow", "!"),
263
- quota_exhausted: style("yellow", "!"),
264
- timed_out: style("yellow", "!"),
265
- interrupted: style("yellow", "!")
266
- };
267
- /** The run summary: status, cost, what ran, branches to look at, and why. */
268
- const formatRunResult = (result) => {
269
- const ran = result.nodes.filter((n) => n.runs > 0).length;
270
- const skipped = result.nodes.length - ran;
271
- const lines = ["", `${RUN_MARK[result.status]} run ${result.runId} ${result.status.replace("_", " ")} ${style("dim", `· ${cost(result.costUsd)} · ${String(ran)} ran, ${String(skipped)} skipped`)}`];
272
- for (const kept of result.worktrees) lines.push(` ${kept.id}: ${kept.branch ?? "detached"} ${style("dim", `at ${kept.path} (${kept.reason})`)}`);
273
- for (const reason of result.reasons) lines.push(` ${style("red", reason)}`);
274
- return lines;
275
- };
276
306
  // src/runs/format.ts
277
307
  /**
278
308
  * @file How stored runs read on the terminal: the `ork ps` table and the `ork
@@ -306,7 +336,10 @@ const duration = (ms) => {
306
336
  if (s < 3600) return `${String(Math.floor(s / 60))}m${String(s % 60).padStart(2, "0")}s`;
307
337
  return `${String(Math.floor(s / 3600))}h${String(Math.floor(s % 3600 / 60)).padStart(2, "0")}m`;
308
338
  };
309
- /** The `ork ps` table; `withProject` adds each run's project. */
339
+ /**
340
+ * The `ork ps` table; `withProject` adds each run's project. A called run's
341
+ * cost is dimmed and says its caller's, whose cost already counts it.
342
+ */
310
343
  const formatRuns = (runs, withProject, now = Date.now(), statusOf = liveStatus) => {
311
344
  if (runs.length === 0) return ["No runs."];
312
345
  const rows = runs.map((run) => {
@@ -317,7 +350,8 @@ const formatRuns = (runs, withProject, now = Date.now(), statusOf = liveStatus)
317
350
  workflow: run.workflow,
318
351
  status,
319
352
  when: `${ago(run.startedAt, now)} · ${duration(end - Date.parse(run.startedAt))}`,
320
- cost: cost(run.costUsd),
353
+ cost: run.parent === void 0 ? cost(run.costUsd) : `${cost(run.costUsd)} in ${run.parent}`,
354
+ called: run.parent !== void 0,
321
355
  project: run.project
322
356
  };
323
357
  });
@@ -342,7 +376,7 @@ const formatRuns = (runs, withProject, now = Date.now(), statusOf = liveStatus)
342
376
  r.workflow.padEnd(w.workflow),
343
377
  style(STATUS_STYLE[r.status] ?? "reset", r.status.padEnd(w.status)),
344
378
  style("dim", r.when.padEnd(w.when)),
345
- r.cost.padStart(w.cost),
379
+ r.called ? style("dim", r.cost.padStart(w.cost)) : r.cost.padStart(w.cost),
346
380
  ...withProject ? [style("dim", r.project)] : []
347
381
  ].join(" "))];
348
382
  };
@@ -0,0 +1,354 @@
1
+ // src/completion/scripts.ts
2
+ /**
3
+ * @file The tab-completion scripts `ork completion <shell>` prints, one per
4
+ * shell. Each is thin: it runs `ork __complete -- <words…>` with the word
5
+ * under the cursor last, and offers the lines it prints, `value<TAB>
6
+ * description`. A line `:group <title>` starts a group (zsh labels the menu
7
+ * with it); `:files` asks for file names too. A new shell is one more entry.
8
+ */
9
+ /** The hidden command the scripts run. */
10
+ const COMPLETE_COMMAND = "__complete";
11
+ /** Each shell's script, by the name `ork completion` takes. */
12
+ const SHELL_SCRIPTS = {
13
+ zsh: `#compdef ork
14
+ # Tab completion for ork, in zsh. Save it as _ork in a folder on your fpath
15
+ # (ork completion zsh > ~/.zsh/completions/_ork), or source it.
16
+
17
+ _ork() {
18
+ local -a lines cands
19
+ local line group=commands files=0 n=0 ret=1
20
+ lines=("\${(@f)$("\${words[1]}" ${COMPLETE_COMMAND} -- "\${(@)words[2,CURRENT]}" 2>/dev/null)}")
21
+ for line in "\${lines[@]}"; do
22
+ case $line in
23
+ (:files) files=1 ;;
24
+ (:group\\ *)
25
+ if (( \${#cands} )); then
26
+ _describe -V -t "ork-$(( ++n ))" "$group" cands && ret=0
27
+ fi
28
+ cands=()
29
+ group=\${line#:group }
30
+ ;;
31
+ (?*) cands+=("\${\${line%%$'\\t'*}//:/\\\\:}:\${line#*$'\\t'}") ;;
32
+ esac
33
+ done
34
+ if (( \${#cands} )); then
35
+ _describe -V -t "ork-$(( ++n ))" "$group" cands && ret=0
36
+ fi
37
+ if (( files )); then
38
+ _files && ret=0
39
+ fi
40
+ return ret
41
+ }
42
+
43
+ if [[ "\${funcstack[1]}" == _ork ]]; then
44
+ _ork "$@"
45
+ else
46
+ compdef _ork ork
47
+ fi
48
+ `,
49
+ bash: `# Tab completion for ork, in bash. Add to your ~/.bashrc:
50
+ # eval "$(ork completion bash)"
51
+ # (macOS's bash 3.2 can't source <(…); saving it to a file and sourcing that
52
+ # works too.) Every expansion is quoted and lines are read with read -r, so a
53
+ # candidate is never split or glob-expanded.
54
+
55
+ _ork() {
56
+ local line="\${COMP_LINE:0:COMP_POINT}" cur files=0 strip="" out
57
+ local -a words
58
+ read -r -a words <<< "$line"
59
+ if [[ "$line" == *[[:space:]] ]]; then words+=(""); fi
60
+ cur="\${words[\${#words[@]}-1]}"
61
+ COMPREPLY=()
62
+ # bash splits a word at ":" (ork:feature); offer only what follows it.
63
+ if [[ "$cur" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
64
+ strip="\${cur%"\${cur##*:}"}"
65
+ fi
66
+ out="$("\${words[0]}" ${COMPLETE_COMMAND} -- "\${words[@]:1}" 2>/dev/null)"
67
+ while IFS= read -r line; do
68
+ case "$line" in
69
+ :files) files=1 ;;
70
+ :* | "") ;;
71
+ *)
72
+ line="\${line%%$'\\t'*}"
73
+ COMPREPLY+=("\${line#"$strip"}")
74
+ ;;
75
+ esac
76
+ done <<< "$out"
77
+ if (( files )); then
78
+ out="$(compgen -f -- "$cur")"
79
+ while IFS= read -r line; do
80
+ if [[ -n "$line" ]]; then COMPREPLY+=("\${line#"$strip"}"); fi
81
+ done <<< "$out"
82
+ fi
83
+ }
84
+
85
+ complete -F _ork ork
86
+ `,
87
+ fish: `# Tab completion for ork, in fish. Save it as
88
+ # ~/.config/fish/completions/ork.fish (ork completion fish > …), or source it.
89
+
90
+ function __ork_complete
91
+ # -x is fish 4's name for -o, which it deprecates; fish 3 has only -o.
92
+ set -l tokens (commandline -xpc 2>/dev/null; or commandline -opc)
93
+ set -l current (commandline -ct)
94
+ set -l out ($tokens[1] ${COMPLETE_COMMAND} -- $tokens[2..-1] "$current" 2>/dev/null)
95
+ string match -v -r '^:' -- $out
96
+ if contains -- :files $out
97
+ __fish_complete_path "$current"
98
+ end
99
+ end
100
+
101
+ complete -c ork -f -a '(__ork_complete)'
102
+ `
103
+ };
104
+ /** The shells there is a script for. */
105
+ const SHELLS = Object.keys(SHELL_SCRIPTS);
106
+ // src/command-tree.ts
107
+ /** How a flag that only Ork passes to itself starts its description. */
108
+ const INTERNAL = "Internal:";
109
+ /** Whether citty was given a function that gives the value. */
110
+ const isThunk = (value) => typeof value === "function";
111
+ /** A value citty lets be given as is, as a promise, or as a function. */
112
+ const resolved = (value) => Promise.resolve(isThunk(value) ? value() : value);
113
+ /** `args` without the flags only Ork passes to itself. */
114
+ const publicArgs = (args) => Object.fromEntries(Object.entries(args).filter(([, def]) => !(def.description ?? "").startsWith(INTERNAL)));
115
+ /** A command's flags and positionals, which citty lets be given lazily. */
116
+ const argsOf = (cmd) => cmd.args === void 0 ? Promise.resolve({}) : resolved(cmd.args);
117
+ const listOf = (value) => value === void 0 ? [] : [value].flat();
118
+ const flagOf = (name, def) => ({
119
+ name,
120
+ description: def.description ?? "",
121
+ takesValue: def.type === "string" || def.type === "enum",
122
+ aliases: "alias" in def ? listOf(def.alias) : [],
123
+ options: def.type === "enum" ? def.options ?? [] : [],
124
+ negatable: def.type === "boolean" && def.default === true
125
+ });
126
+ /** A command's public flags and positionals, in the order it defines them. */
127
+ const argInfoOf = async (cmd) => {
128
+ const entries = Object.entries(publicArgs(await argsOf(cmd)));
129
+ return {
130
+ flags: entries.filter(([, def]) => def.type !== "positional").map(([name, def]) => flagOf(name, def)),
131
+ positionals: entries.filter(([, def]) => def.type === "positional").map(([name, def]) => ({
132
+ name,
133
+ description: def.description ?? ""
134
+ }))
135
+ };
136
+ };
137
+ /**
138
+ * A command's subcommands, in order, loaded ones resolved. A name whose
139
+ * definition is one listed before it (`wt`, `worktrees`'s) is an alias of that
140
+ * one, not an entry of its own.
141
+ */
142
+ const subcommandsOf = async (cmd) => {
143
+ const subs = cmd.subCommands === void 0 ? {} : await resolved(cmd.subCommands);
144
+ const loaded = await Promise.all(Object.entries(subs).map(async ([name, sub]) => {
145
+ const def = await resolved(sub);
146
+ return {
147
+ name,
148
+ def,
149
+ meta: def.meta === void 0 ? {} : await resolved(def.meta)
150
+ };
151
+ }));
152
+ const entries = [];
153
+ for (const { name, def, meta } of loaded) {
154
+ const same = entries.find((entry) => entry.def === def);
155
+ if (same === void 0) entries.push({
156
+ name,
157
+ aliases: listOf(meta.alias),
158
+ description: meta.description ?? "",
159
+ hidden: meta.hidden === true,
160
+ def
161
+ });
162
+ else same.aliases.push(name);
163
+ }
164
+ return entries;
165
+ };
166
+ /** The subcommand `name` (or one of its aliases) of `entries`. */
167
+ const findEntry = (entries, name) => entries.find((entry) => entry.name === name || entry.aliases.includes(name));
168
+ // src/help-groups.ts
169
+ const HELP_GROUPS = [
170
+ {
171
+ title: "Get started",
172
+ commands: [
173
+ {
174
+ name: "init",
175
+ short: "Set up Ork in this repository, in .ork/"
176
+ },
177
+ {
178
+ name: "doctor",
179
+ short: "Check Node, git and your own logins"
180
+ },
181
+ {
182
+ name: "ui",
183
+ short: "Open Ork's local UI in the browser"
184
+ }
185
+ ]
186
+ },
187
+ {
188
+ title: "Workflows",
189
+ commands: [
190
+ {
191
+ name: "validate",
192
+ short: "Check workflow files"
193
+ },
194
+ {
195
+ name: "run",
196
+ short: "Run a workflow, or one node of it"
197
+ },
198
+ {
199
+ name: "agents",
200
+ short: "List, show, add and remove agents"
201
+ },
202
+ {
203
+ name: "context",
204
+ short: "Show what a node's session loads"
205
+ },
206
+ {
207
+ name: "watch",
208
+ short: "Start runs from each project's triggers"
209
+ }
210
+ ]
211
+ },
212
+ {
213
+ title: "Runs",
214
+ commands: [
215
+ {
216
+ name: "ps",
217
+ short: "List this project's runs"
218
+ },
219
+ {
220
+ name: "logs",
221
+ short: "Print a run's transcript"
222
+ },
223
+ {
224
+ name: "tree",
225
+ short: "Show a run's tree, tokens and cost"
226
+ },
227
+ {
228
+ name: "usage",
229
+ short: "Show what runs spent over time"
230
+ },
231
+ {
232
+ name: "resume",
233
+ short: "Continue a run that didn't succeed"
234
+ }
235
+ ]
236
+ },
237
+ {
238
+ title: "Steer a running run",
239
+ commands: [
240
+ {
241
+ name: "decide",
242
+ short: "Answer a run's open decisions"
243
+ },
244
+ {
245
+ name: "chat",
246
+ short: "Talk to a node of a run"
247
+ },
248
+ {
249
+ name: "pause",
250
+ short: "Pause a run, or one node"
251
+ },
252
+ {
253
+ name: "continue",
254
+ short: "Let a paused run or node go on"
255
+ },
256
+ {
257
+ name: "stop",
258
+ short: "Stop a run, or one node"
259
+ },
260
+ {
261
+ name: "relay",
262
+ short: "Let nodes of two runs message each other"
263
+ }
264
+ ]
265
+ },
266
+ {
267
+ title: "After runs",
268
+ commands: [
269
+ {
270
+ name: "feedback",
271
+ short: "What got in the way in your runs"
272
+ },
273
+ {
274
+ name: "worktrees",
275
+ short: "Track and clean up Ork's worktrees"
276
+ },
277
+ {
278
+ name: "prune",
279
+ short: "Forget old runs' transcripts"
280
+ }
281
+ ]
282
+ },
283
+ {
284
+ title: "Projects",
285
+ commands: [
286
+ {
287
+ name: "projects",
288
+ short: "List, forget or move registered projects"
289
+ },
290
+ {
291
+ name: "register",
292
+ short: "Attach a project from outside it"
293
+ },
294
+ {
295
+ name: "migrate",
296
+ short: "Move an attached project into its .ork/"
297
+ },
298
+ {
299
+ name: "link",
300
+ short: "Point tools' agent folders at .ork/"
301
+ }
302
+ ]
303
+ },
304
+ {
305
+ title: "Ork itself",
306
+ commands: [
307
+ {
308
+ name: "upgrade",
309
+ short: "Upgrade Ork, the way it was installed"
310
+ },
311
+ {
312
+ name: "mcp",
313
+ short: "Serve Ork's tools to Claude Code (MCP)"
314
+ },
315
+ {
316
+ name: "send",
317
+ short: "Write to a session from a tool node's program"
318
+ },
319
+ {
320
+ name: "completion",
321
+ short: "Print a tab-completion script"
322
+ }
323
+ ]
324
+ }
325
+ ];
326
+ /** What a command not in any group is listed under. */
327
+ const OTHER_GROUP = "Other";
328
+ /** The group a root command is in, and its short line, if it has them. */
329
+ const groupOf = (name) => {
330
+ for (const group of HELP_GROUPS) {
331
+ const found = group.commands.find((command) => command.name === name);
332
+ if (found !== void 0) return {
333
+ title: group.title,
334
+ short: found.short
335
+ };
336
+ }
337
+ };
338
+ /**
339
+ * The shown commands in `HELP_GROUPS`'s order, then any in no group under
340
+ * "Other", in the order the root command lists them.
341
+ */
342
+ const groupedEntries = (entries) => {
343
+ const shown = entries.filter((entry) => !entry.hidden);
344
+ const grouped = HELP_GROUPS.map((group) => ({
345
+ title: group.title,
346
+ entries: group.commands.flatMap((command) => shown.find((entry) => entry.name === command.name) ?? [])
347
+ }));
348
+ const other = shown.filter((entry) => groupOf(entry.name) === void 0);
349
+ return [...grouped, {
350
+ title: OTHER_GROUP,
351
+ entries: other
352
+ }].filter((group) => group.entries.length > 0);
353
+ };
354
+ export { findEntry as a, subcommandsOf as c, SHELL_SCRIPTS as d, argsOf as i, COMPLETE_COMMAND as l, groupedEntries as n, publicArgs as o, argInfoOf as r, resolved as s, groupOf as t, SHELLS as u };
@@ -1,4 +1,4 @@
1
- import { p as runMarkerIn } from "./src-UWkk1sl9.mjs";
1
+ import { p as runMarkerIn } from "./src-BxUJPGa5.mjs";
2
2
  // src/commands/in-a-run.ts
3
3
  /**
4
4
  * @file The commands that act as the user refuse a program or an agent in a run
@@ -1,4 +1,4 @@
1
- import { a as limitsNow, i as NOT_STARTED, o as limitsReport, r as capsHere } from "./src-CGqMati3.mjs";
1
+ import { a as limitsNow, i as NOT_STARTED, o as limitsReport, r as capsHere } from "./src-hrj2h0IZ.mjs";
2
2
  // src/run/limits.ts
3
3
  /**
4
4
  * @file The limits across runs that apply here (RFC-007): the user's settings
@@ -1,4 +1,4 @@
1
- import { D as git, O as gitOk, k as lines } from "./src-CGqMati3.mjs";
1
+ import { A as gitOk, j as lines, k as git } from "./src-hrj2h0IZ.mjs";
2
2
  import { existsSync } from "node:fs";
3
3
  // ../core/src/worktree/list.ts
4
4
  /**
@@ -1,10 +1,10 @@
1
- import { m as isNewer } from "./src-CGqMati3.mjs";
2
- import { n as ORK_VERSION } from "./__build_info__-CtSVVuZE.mjs";
3
- import { a as openMcp, c as refused, o as tool, s as answer } from "./src-CpDKIulI.mjs";
4
- import { n as explainNode } from "./context-C_bksb0C.mjs";
5
- import { p as runMarkerIn } from "./src-UWkk1sl9.mjs";
6
- import { defineCommand } from "citty";
1
+ import { h as isNewer } from "./src-hrj2h0IZ.mjs";
2
+ import { n as ORK_VERSION } from "./__build_info__-D9lSlMFy.mjs";
3
+ import { a as openMcp, c as refused, o as tool, s as answer } from "./src-B0TT7DBi.mjs";
4
+ import { n as explainNode } from "./context-6x3kXREO.mjs";
5
+ import { p as runMarkerIn } from "./src-BxUJPGa5.mjs";
7
6
  import { z } from "zod";
7
+ import { defineCommand } from "citty";
8
8
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
9
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
10
10
  // src/commands/mcp.ts
@@ -43,7 +43,7 @@ const orkMcpServer = (options = {}) => {
43
43
  ...options.script === void 0 ? {} : { script: options.script }
44
44
  });
45
45
  const all = [...opened.tools, explainTool(opened, options.home)];
46
- const tools = options.expects !== void 0 && isNewer(options.expects, "0.4.0") ? tooOld(all, options.expects) : all;
46
+ const tools = options.expects !== void 0 && isNewer(options.expects, "1.0.0-alpha.1") ? tooOld(all, options.expects) : all;
47
47
  const server = new McpServer({
48
48
  name: "ork",
49
49
  version: ORK_VERSION