@krapht/ork 0.4.0 → 1.0.0-alpha.0
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.
- package/README.md +16 -12
- package/dist/{__build_info__-CtSVVuZE.mjs → __build_info__-DdTVd2p5.mjs} +36 -112
- package/dist/bin.mjs +663 -300
- package/dist/{clean-Dcr807be.mjs → clean-NnboOuz6.mjs} +1 -1
- package/dist/completion-snnYGjgK.mjs +405 -0
- package/dist/{context-C_bksb0C.mjs → context-DO3sBy9v.mjs} +11 -88
- package/dist/decide-B81wCZQg.mjs +113 -0
- package/dist/{find-DptmbhGd.mjs → find-BKW-uGey.mjs} +1 -1
- package/dist/{format-J-a21Yvn.mjs → format-CmTlTOjT.mjs} +71 -37
- package/dist/help-groups-BwYoAWsh.mjs +354 -0
- package/dist/{in-a-run-D1Qc5Mit.mjs → in-a-run-BPXX09Bs.mjs} +1 -1
- package/dist/{limits-BDZCmGEu.mjs → limits-JCve2QpI.mjs} +1 -1
- package/dist/{list-ZvY2OcLq.mjs → list-C7UEgmtd.mjs} +1 -1
- package/dist/{mcp-CHVKKg3B.mjs → mcp-CKPyNaKD.mjs} +7 -7
- package/dist/{output-CXSJO00X.mjs → output-B1V6ZJSx.mjs} +67 -17
- package/dist/{pause-BPSmg4yz.mjs → pause-B_SL_yDz.mjs} +4 -4
- package/dist/{relay-Do-aNbSD.mjs → relay-o-Luee1c.mjs} +2 -2
- package/dist/{remove-AwaMMaHf.mjs → remove-DAZ7ZCzt.mjs} +3 -3
- package/dist/{send-DhGBkowy.mjs → send-3B64fBNB.mjs} +3 -3
- package/dist/{src-CpDKIulI.mjs → src-BJTegEzY.mjs} +23 -13
- package/dist/{src-UWkk1sl9.mjs → src-C38BPzit.mjs} +251 -18
- package/dist/{src-CGqMati3.mjs → src-DHvWU4tX.mjs} +636 -217
- package/dist/{src-BHnxpJyH.mjs → src-Dyz_Gy1q.mjs} +35 -8
- package/dist/ui/THIRD_PARTY_LICENSES.txt +2 -2
- package/dist/ui/assets/app-C8nJ6NpT.js +67 -0
- package/dist/ui/assets/app-CH7YC3in.css +1 -0
- package/dist/ui/assets/{index-BiO54L90.css → index-BPIbqRRb.css} +1 -1
- package/dist/ui/assets/{index-MQNNgsyT.js → index-D83F2OKZ.js} +2 -2
- package/dist/ui/index.html +2 -3
- package/dist/{ui-DhutUMgQ.mjs → ui-CY4xxJUu.mjs} +5 -5
- package/dist/{usage-CT7Z-yjr.mjs → usage-CJnytoe4.mjs} +6 -6
- package/dist/{watch-0Qr2UfCQ.mjs → watch-BJB82ZJ7.mjs} +7 -7
- package/dist/workflows-CiM7S3l0.mjs +88 -0
- package/dist/{worktrees-DHl80k96.mjs → worktrees-86ySXxR6.mjs} +5 -5
- package/package.json +3 -3
- package/dist/ui/assets/app-BgVDC_5l.js +0 -67
- package/dist/ui/assets/app-DVf68fj5.css +0 -1
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { i as style, n as cost } from "./output-
|
|
1
|
+
import { ht as liveStatus } from "./src-DHvWU4tX.mjs";
|
|
2
|
+
import { i as style, n as cost } from "./output-B1V6ZJSx.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.
|
|
9
|
-
*
|
|
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
|
-
|
|
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)
|
|
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
|
|
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
|
-
|
|
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")
|
|
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
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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 { a as limitsNow, i as NOT_STARTED, o as limitsReport, r as capsHere } from "./src-
|
|
1
|
+
import { a as limitsNow, i as NOT_STARTED, o as limitsReport, r as capsHere } from "./src-DHvWU4tX.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,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as ORK_VERSION } from "./__build_info__-
|
|
3
|
-
import { a as openMcp, c as refused, o as tool, s as answer } from "./src-
|
|
4
|
-
import { n as explainNode } from "./context-
|
|
5
|
-
import { p as runMarkerIn } from "./src-
|
|
6
|
-
import { defineCommand } from "citty";
|
|
1
|
+
import { h as isNewer } from "./src-DHvWU4tX.mjs";
|
|
2
|
+
import { n as ORK_VERSION } from "./__build_info__-DdTVd2p5.mjs";
|
|
3
|
+
import { a as openMcp, c as refused, o as tool, s as answer } from "./src-BJTegEzY.mjs";
|
|
4
|
+
import { n as explainNode } from "./context-DO3sBy9v.mjs";
|
|
5
|
+
import { p as runMarkerIn } from "./src-C38BPzit.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.
|
|
46
|
+
const tools = options.expects !== void 0 && isNewer(options.expects, "1.0.0-alpha.0") ? tooOld(all, options.expects) : all;
|
|
47
47
|
const server = new McpServer({
|
|
48
48
|
name: "ork",
|
|
49
49
|
version: ORK_VERSION
|