@krapht/ork 1.0.0-alpha.2 → 1.0.0-alpha.21
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 +12 -7
- package/dist/__build_info__-0wqQ-rtf.mjs +180 -0
- package/dist/apply-Mb0fNpCX.mjs +17 -0
- package/dist/argument-error-CihH46Le.mjs +27 -0
- package/dist/attach-C0-YyNoY.mjs +92 -0
- package/dist/attach-CAKNqnUK.mjs +1259 -0
- package/dist/bin.mjs +1885 -1609
- package/dist/brief-C2hew8cS.mjs +116 -0
- package/dist/{decide-CGyx9rv7.mjs → builtin-files-YYAnbZGD.mjs} +3 -51
- package/dist/chips-CP_pgKlj.mjs +48 -0
- package/dist/command-tree-CEkzWNy6.mjs +63 -0
- package/dist/{completion-CSU5vkEt.mjs → completion-K4rAVsrf.mjs} +200 -30
- package/dist/context-B-fjPJky.mjs +106 -0
- package/dist/context-DMK0U1w0.mjs +160 -0
- package/dist/detached-q0AYG0Bs.mjs +39 -0
- package/dist/extra-arguments-BxQMXARO.mjs +13 -0
- package/dist/{find-D6Gj1sxg.mjs → find-Ct5CNl1J.mjs} +2 -2
- package/dist/flags-C90N4J1i.mjs +145 -0
- package/dist/follow-ups-D3HPyNYM.mjs +568 -0
- package/dist/format-CiPhQqbX.mjs +260 -0
- package/dist/{help-groups-BwYoAWsh.mjs → help-groups-BQvcKQ9H.mjs} +49 -78
- package/dist/in-a-run-D_wg4iJi.mjs +27 -0
- package/dist/install-Hji6LIei.mjs +259 -0
- package/dist/{limits-B1-DYc3d.mjs → limits-BD6RZRCS.mjs} +2 -2
- package/dist/{mcp-DE_EmyVm.mjs → mcp-CpbDmBLG.mjs} +6 -6
- package/dist/once-J8Qn5fTG.mjs +112 -0
- package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
- package/dist/output-DXZbnFt_.mjs +458 -0
- package/dist/outputs-text-CNTJ7Qq_.mjs +19 -0
- package/dist/{pause-CL6N_Xrj.mjs → pause-Cl4ZYA35.mjs} +7 -4
- package/dist/print-DzugGZQB.mjs +19 -0
- package/dist/project-records-CtkmlsHN.mjs +233 -0
- package/dist/refusals-DQQX3AIb.mjs +116 -0
- package/dist/{relay-0SD0erwf.mjs → relay-CiP77vga.mjs} +2 -2
- package/dist/run-CtuNY3mE.mjs +234 -0
- package/dist/send-DRYGks8K.mjs +104 -0
- package/dist/short-flags-DkJLWVqh.mjs +1 -0
- package/dist/short-flags-Qf21as0E.mjs +83 -0
- package/dist/src-BMFQKHz4.mjs +3122 -0
- package/dist/{src-BbyEoMn4.mjs → src-BrFfDKux.mjs} +800 -235
- package/dist/{src-Dujmvv4s.mjs → src-BuF9Lbow.mjs} +12580 -5505
- package/dist/{src-DOcK8Jfp.mjs → src-k0A_QpST.mjs} +1024 -590
- package/dist/store-rTgRu8xy.mjs +25 -0
- package/dist/ui/THIRD_PARTY_LICENSES.txt +612 -0
- package/dist/ui/assets/app-BYxpKJRh.css +1 -0
- package/dist/ui/assets/app-SEFEJLkF.js +112 -0
- package/dist/ui/assets/{index-9dFha9Qn.js → index-Cb6Voq_W.js} +3 -3
- package/dist/ui/assets/theme-CdXvqTQ6.js +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/{ui-CTmO2Xzu.mjs → ui-C2f37TSb.mjs} +57 -25
- package/dist/usage-BuvGcQSk.mjs +256 -0
- package/dist/validate-ObP5lt29.mjs +1 -0
- package/dist/validate-_HjACtk9.mjs +632 -0
- package/dist/verdict-B592j1W3.mjs +84 -0
- package/dist/{watch-CtbgDAcz.mjs → watch-DV3ixCYj.mjs} +45 -17
- package/dist/{workflows-D1_WVukk.mjs → workflows-QLjBhGNS.mjs} +30 -5
- package/dist/worktrees-D3ubG_Qw.mjs +302 -0
- package/dist/yolo-BSH1E_kE.mjs +33 -0
- package/package.json +10 -8
- package/dist/__build_info__-fy6L1Mrd.mjs +0 -615
- package/dist/clean-DaHjs__2.mjs +0 -88
- package/dist/context-DT8MfEIR.mjs +0 -274
- package/dist/format-Dnfv8zMT.mjs +0 -427
- package/dist/in-a-run-pLAZeNys.mjs +0 -22
- package/dist/list-BrH8KRSJ.mjs +0 -110
- package/dist/output-BLQWD6MJ.mjs +0 -950
- package/dist/remove-BeGdqroy.mjs +0 -194
- package/dist/report-DK4LGqwF.mjs +0 -129
- package/dist/send-C8tunWhv.mjs +0 -163
- package/dist/src-CCUKRX51.mjs +0 -1204
- package/dist/ui/assets/app-BNT0SuIz.css +0 -1
- package/dist/ui/assets/app-T7wVAhgx.js +0 -67
- package/dist/ui/assets/theme-3xH5jxKs.js +0 -1
- package/dist/usage-DYUmP0i4.mjs +0 -419
- package/dist/worktrees-DdMYoNOC.mjs +0 -212
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { Fn as lockWaitsOf, Ln as runStatus, Nt as lockWaitLine, Pn as liveStatus, Rn as slotWaitingOf, Vn as yoloOf, Wn as isItem, jr as maskSecrets, zn as slotWaitsOf, zt as slotWaitLine } from "./src-BuF9Lbow.mjs";
|
|
2
|
+
import { n as cost, o as shortCount, r as costOf, s as style, v as termOf, x as widthOf, y as cut } from "./output-DXZbnFt_.mjs";
|
|
3
|
+
import { a as statusChip, r as chip } from "./chips-CP_pgKlj.mjs";
|
|
4
|
+
import { n as nodeVerdict } from "./verdict-B592j1W3.mjs";
|
|
5
|
+
// src/term/table.ts
|
|
6
|
+
/**
|
|
7
|
+
* @file Aligned columns for the listing commands: each column as wide as its
|
|
8
|
+
* widest cell, a two-space gap, the last column left unpadded. Rich, a cell
|
|
9
|
+
* may be a chip and a table wider than the screen cuts its flexible columns
|
|
10
|
+
* with `…`; plain, nothing is cut or styled, as the listings always printed.
|
|
11
|
+
*/
|
|
12
|
+
/** A dimmed header cell. */
|
|
13
|
+
const header = (text) => ({
|
|
14
|
+
text,
|
|
15
|
+
look: (s) => style("dim", s)
|
|
16
|
+
});
|
|
17
|
+
const GAP = " ";
|
|
18
|
+
/** A flexible column is never cut under this many columns. */
|
|
19
|
+
const MIN_FLEX = 8;
|
|
20
|
+
/** The text a cell shows, and its columns. */
|
|
21
|
+
const shown = (term, cell) => term.rich && cell.chip !== void 0 ? cell.chip.label : cell.text;
|
|
22
|
+
const widthOfCell = (term, cell) => widthOf(shown(term, cell)) + (term.rich && cell.chip !== void 0 ? 2 : 0);
|
|
23
|
+
/** The lines of a table, one per row (a header is a row the caller passes). */
|
|
24
|
+
const table = (term, columns, rows, indent = "") => {
|
|
25
|
+
const widths = columns.map((_, c) => Math.max(0, ...rows.map((row) => widthOfCell(term, row[c] ?? { text: "" }))));
|
|
26
|
+
if (term.rich) {
|
|
27
|
+
const gaps = 2 * (columns.length - 1);
|
|
28
|
+
let over = indent.length + widths.reduce((a, b) => a + b, 0) + gaps - term.width;
|
|
29
|
+
columns.forEach((column, c) => {
|
|
30
|
+
const width = widths[c] ?? 0;
|
|
31
|
+
if (over <= 0 || column.flex !== true || width <= MIN_FLEX) return;
|
|
32
|
+
const next = Math.max(MIN_FLEX, width - over);
|
|
33
|
+
over -= width - next;
|
|
34
|
+
widths[c] = next;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const last = columns.length - 1;
|
|
38
|
+
return rows.map((row) => indent + columns.map((column, c) => {
|
|
39
|
+
const cell = row[c] ?? { text: "" };
|
|
40
|
+
const width = widths[c] ?? 0;
|
|
41
|
+
const look = cell.look ?? ((s) => s);
|
|
42
|
+
if (term.rich && cell.chip !== void 0) {
|
|
43
|
+
const used = widthOfCell(term, cell);
|
|
44
|
+
const pad = " ".repeat(Math.max(0, width - used));
|
|
45
|
+
return column.align === "right" ? pad + chip(term, cell.chip.label, cell.chip.role) : chip(term, cell.chip.label, cell.chip.role) + (c === last ? "" : pad);
|
|
46
|
+
}
|
|
47
|
+
const text = cut(shown(term, cell), width);
|
|
48
|
+
const pad = " ".repeat(Math.max(0, width - widthOf(text)));
|
|
49
|
+
if (column.align === "right") return look(pad + text);
|
|
50
|
+
return c === last ? look(text) : look(text + pad);
|
|
51
|
+
}).join(GAP));
|
|
52
|
+
};
|
|
53
|
+
// src/runs/format.ts
|
|
54
|
+
/**
|
|
55
|
+
* @file How stored runs read on the terminal: the `ork ps` table and the `ork
|
|
56
|
+
* tree` ownership tree with each node's state, tokens and cost, and what is
|
|
57
|
+
* paused (ADR-041).
|
|
58
|
+
*/
|
|
59
|
+
const STATUS_STYLE = {
|
|
60
|
+
running: "cyan",
|
|
61
|
+
paused: "yellow",
|
|
62
|
+
succeeded: "green",
|
|
63
|
+
failed: "red",
|
|
64
|
+
crashed: "red",
|
|
65
|
+
budget_exhausted: "yellow",
|
|
66
|
+
quota_exhausted: "yellow",
|
|
67
|
+
timed_out: "yellow",
|
|
68
|
+
interrupted: "yellow"
|
|
69
|
+
};
|
|
70
|
+
/** `just now`, `5m ago`, `3h ago`, `2d ago`, then the date. */
|
|
71
|
+
const ago = (iso, now = Date.now()) => {
|
|
72
|
+
const minutes = Math.floor((now - Date.parse(iso)) / 6e4);
|
|
73
|
+
if (minutes < 1) return "just now";
|
|
74
|
+
if (minutes < 60) return `${String(minutes)}m ago`;
|
|
75
|
+
if (minutes < 1440) return `${String(Math.floor(minutes / 60))}h ago`;
|
|
76
|
+
if (minutes < 10080) return `${String(Math.floor(minutes / 1440))}d ago`;
|
|
77
|
+
return iso.slice(0, 10);
|
|
78
|
+
};
|
|
79
|
+
/** `42s`, `3m12s`, `1h05m`. */
|
|
80
|
+
const duration = (ms) => {
|
|
81
|
+
const s = Math.max(0, Math.round(ms / 1e3));
|
|
82
|
+
if (s < 60) return `${String(s)}s`;
|
|
83
|
+
if (s < 3600) return `${String(Math.floor(s / 60))}m${String(s % 60).padStart(2, "0")}s`;
|
|
84
|
+
return `${String(Math.floor(s / 3600))}h${String(Math.floor(s % 3600 / 60)).padStart(2, "0")}m`;
|
|
85
|
+
};
|
|
86
|
+
const dim = (s) => style("dim", s);
|
|
87
|
+
/**
|
|
88
|
+
* A run's status as `ork ps` reads it: for a running run, `waiting for <lock>,
|
|
89
|
+
* held by <run>/<node>` with a node waiting for a lock, or `waiting for slot`
|
|
90
|
+
* with a command waiting for a machine-wide slot; `events` are a running
|
|
91
|
+
* run's.
|
|
92
|
+
*/
|
|
93
|
+
const listedStatus = (run, events) => {
|
|
94
|
+
const status = runStatus(run, events);
|
|
95
|
+
if (status !== "running") return status;
|
|
96
|
+
const [lock] = lockWaitsOf(events);
|
|
97
|
+
if (lock !== void 0) return lockWaitLine(lock.lock, lock.holders);
|
|
98
|
+
return slotWaitsOf(events).length > 0 ? "waiting for slot" : status;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* The `ork ps` table; `withProject` adds each run's project. A called run's
|
|
102
|
+
* cost is dimmed and says its caller's, whose cost already counts it.
|
|
103
|
+
*/
|
|
104
|
+
const formatRuns = (runs, withProject, now = Date.now(), statusOf = liveStatus, term = termOf(process.stdout), yolo = () => false) => {
|
|
105
|
+
if (runs.length === 0) return ["No runs."];
|
|
106
|
+
const rows = runs.map((run) => {
|
|
107
|
+
const status = statusOf(run);
|
|
108
|
+
const end = run.finishedAt === void 0 ? now : Date.parse(run.finishedAt);
|
|
109
|
+
return {
|
|
110
|
+
id: run.id,
|
|
111
|
+
workflow: yolo(run) ? `${run.workflow} · yolo` : run.workflow,
|
|
112
|
+
yolo: yolo(run),
|
|
113
|
+
status,
|
|
114
|
+
when: `${ago(run.startedAt, now)} · ${duration(end - Date.parse(run.startedAt))}`,
|
|
115
|
+
cost: run.parent === void 0 ? costOf(run.costUsd, run.partial, run.tokens) : `${costOf(run.costUsd, run.partial, run.tokens)} in ${run.parent}`,
|
|
116
|
+
called: run.parent !== void 0,
|
|
117
|
+
project: run.project
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
const rowOf = (r) => {
|
|
121
|
+
const cells = [
|
|
122
|
+
{
|
|
123
|
+
text: r.id,
|
|
124
|
+
look: (s) => style("bold", s)
|
|
125
|
+
},
|
|
126
|
+
r.yolo ? {
|
|
127
|
+
text: r.workflow,
|
|
128
|
+
look: (s) => style("yellow", s)
|
|
129
|
+
} : { text: r.workflow },
|
|
130
|
+
{
|
|
131
|
+
text: r.status,
|
|
132
|
+
chip: statusChip(r.status),
|
|
133
|
+
look: (s) => style(STATUS_STYLE[r.status] ?? "reset", s)
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
text: r.when,
|
|
137
|
+
look: dim
|
|
138
|
+
},
|
|
139
|
+
r.called ? {
|
|
140
|
+
text: r.cost,
|
|
141
|
+
look: dim
|
|
142
|
+
} : { text: r.cost }
|
|
143
|
+
];
|
|
144
|
+
if (withProject) cells.push({
|
|
145
|
+
text: r.project,
|
|
146
|
+
look: dim
|
|
147
|
+
});
|
|
148
|
+
return cells;
|
|
149
|
+
};
|
|
150
|
+
const heads = [
|
|
151
|
+
"RUN",
|
|
152
|
+
"WORKFLOW",
|
|
153
|
+
"STATUS",
|
|
154
|
+
"STARTED",
|
|
155
|
+
"COST"
|
|
156
|
+
].map((text) => header(text));
|
|
157
|
+
if (withProject) heads.push(header("PROJECT"));
|
|
158
|
+
const columns = [
|
|
159
|
+
{},
|
|
160
|
+
{ flex: true },
|
|
161
|
+
{},
|
|
162
|
+
{},
|
|
163
|
+
{ align: "right" }
|
|
164
|
+
];
|
|
165
|
+
if (withProject) columns.push({ flex: true });
|
|
166
|
+
return table(term, columns, [heads, ...rows.map((row) => rowOf(row))]);
|
|
167
|
+
};
|
|
168
|
+
/** Marks are made when used, so `--plain` (set after load) is read. */
|
|
169
|
+
const STATE_MARK = {
|
|
170
|
+
waiting: () => style("dim", "·"),
|
|
171
|
+
running: () => style("cyan", "●"),
|
|
172
|
+
ok: () => style("green", "✓"),
|
|
173
|
+
failed: () => style("red", "✗"),
|
|
174
|
+
skipped: () => style("dim", "-")
|
|
175
|
+
};
|
|
176
|
+
/** `950`, `12.3k`, `1.2M`. */
|
|
177
|
+
const count = shortCount;
|
|
178
|
+
const stateText = (node) => {
|
|
179
|
+
if (node.state === "running") return node.runs > 1 ? `running (round ${String(node.runs)})` : "running";
|
|
180
|
+
if (node.state === "ok" || node.state === "failed") {
|
|
181
|
+
const verdict = nodeVerdict(node.output ?? {}, node.state === "ok");
|
|
182
|
+
const line = verdict.length > 60 ? `${verdict.slice(0, 59)}…` : verdict;
|
|
183
|
+
return node.runs > 1 ? `${line} (${String(node.runs)} runs)` : line;
|
|
184
|
+
}
|
|
185
|
+
return node.state;
|
|
186
|
+
};
|
|
187
|
+
/** A node's counted tokens: input, output and cache write. */
|
|
188
|
+
const countedOf = (node) => node.inputTokens + node.outputTokens + (node.cacheWriteTokens ?? 0);
|
|
189
|
+
/**
|
|
190
|
+
* What a node spent: its counted tokens, then dollars unless its driver reports
|
|
191
|
+
* none, where `$0.00` would be false. Blank with no tokens.
|
|
192
|
+
*/
|
|
193
|
+
const worth = (node) => {
|
|
194
|
+
const counted = countedOf(node);
|
|
195
|
+
if (counted === 0) return "";
|
|
196
|
+
const tok = `${count(counted)} tok`;
|
|
197
|
+
return style("dim", ` ${node.unpriced === true ? tok : `${tok} · ${cost(node.costUsd)}`}`);
|
|
198
|
+
};
|
|
199
|
+
/**
|
|
200
|
+
* A crashed run's node that never ended: cut off when it had started, not run
|
|
201
|
+
* when it had not; `null` for one that ended.
|
|
202
|
+
*/
|
|
203
|
+
const crashedState = (node) => {
|
|
204
|
+
if (node.state === "running") return `${STATE_MARK.failed()} cut off`;
|
|
205
|
+
if (node.state === "waiting") return `${STATE_MARK.skipped()} not run`;
|
|
206
|
+
return null;
|
|
207
|
+
};
|
|
208
|
+
/**
|
|
209
|
+
* The ownership tree, one line per node, children under their owner. A node in
|
|
210
|
+
* `waits` (a live run's lock and slot waits) reads its line, an item too. In a
|
|
211
|
+
* `crashed` run, a node started but not ended reads `cut off` and one not
|
|
212
|
+
* started `not run`: nothing will end them.
|
|
213
|
+
*/
|
|
214
|
+
const formatTree = (roots, now = Date.now(), waits = [], crashed = false) => {
|
|
215
|
+
const lines = [];
|
|
216
|
+
const walk = (raw, lead, branch) => {
|
|
217
|
+
const node = raw.description === void 0 ? raw : {
|
|
218
|
+
...raw,
|
|
219
|
+
description: maskSecrets(raw.description)
|
|
220
|
+
};
|
|
221
|
+
const named = node.description === void 0 ? "" : ` ${node.description}`;
|
|
222
|
+
lines.push({
|
|
223
|
+
label: `${lead}${branch}${node.id}${named}`,
|
|
224
|
+
node
|
|
225
|
+
});
|
|
226
|
+
const next = lead + (branch === "├─ " ? "│ " : branch === "" ? "" : " ");
|
|
227
|
+
node.children.forEach((child, index) => {
|
|
228
|
+
walk(child, next, index === node.children.length - 1 ? "└─ " : "├─ ");
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
for (const root of roots) walk(root, "", "");
|
|
232
|
+
const label = Math.max(...lines.map((l) => widthOf(l.label)));
|
|
233
|
+
const type = Math.max(...lines.map((l) => l.node.type.length));
|
|
234
|
+
return lines.map(({ label: text, node }) => {
|
|
235
|
+
const usage = worth(node);
|
|
236
|
+
const wait = waits.find((each) => each.id === node.id)?.line;
|
|
237
|
+
const cut = crashed ? crashedState(node) : null;
|
|
238
|
+
const state = cut ?? (node.held === true ? `${style("yellow", "‖")} paused` : wait === void 0 ? `${STATE_MARK[node.state]()} ${stateText(node)}` : `${STATE_MARK.waiting()} ${wait}`);
|
|
239
|
+
const spent = isItem(node) && node.startedAt !== void 0 && cut === null ? style("dim", ` ${duration((node.finishedAt === void 0 ? now : Date.parse(node.finishedAt)) - Date.parse(node.startedAt))}`) : "";
|
|
240
|
+
return `${node.description === void 0 ? text : `${text.slice(0, text.length - node.description.length)}${dim(node.description)}`}${" ".repeat(label - widthOf(text))} ${style("dim", node.type.padEnd(type))} ${state}${spent}${usage}`;
|
|
241
|
+
});
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* What `ork tree` prints: the run's header (its status as `ork ps` reads it)
|
|
245
|
+
* and the tree. A finished run's lock waits are stale, so only a live run's
|
|
246
|
+
* show; `events` are the run's, `roots` its nested tree.
|
|
247
|
+
*/
|
|
248
|
+
const formatTreeView = (run, events, roots, now = Date.now()) => {
|
|
249
|
+
const status = runStatus(run, events);
|
|
250
|
+
const waits = status === "running" ? [...lockWaitsOf(events).map(({ id, lock }) => ({
|
|
251
|
+
id,
|
|
252
|
+
line: `waiting for ${lock}`
|
|
253
|
+
})), ...slotWaitingOf(events).map(({ id, holders }) => ({
|
|
254
|
+
id,
|
|
255
|
+
line: slotWaitLine(holders)
|
|
256
|
+
}))] : [];
|
|
257
|
+
const yolo = status === "running" && yoloOf(events) ? style("yellow", " · yolo") : "";
|
|
258
|
+
return [`${style("bold", `run ${run.id}`)} ${dim(`of ${run.workflow} · ${listedStatus(run, events)} · ${cost(run.costUsd)}`)}${yolo}`, ...formatTree(roots, now, waits, status === "crashed")];
|
|
259
|
+
};
|
|
260
|
+
export { header as a, listedStatus as i, formatRuns as n, table as o, formatTreeView as r, ago as t };
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* shell. Each is thin: it runs `ork __complete -- <words…>` with the word
|
|
5
5
|
* under the cursor last, and offers the lines it prints, `value<TAB>
|
|
6
6
|
* description`. A line `:group <title>` starts a group (zsh labels the menu
|
|
7
|
-
* with it); `:files` asks for file names too. A
|
|
7
|
+
* with it); `:files` asks for file names too. A value ending in `=` (an
|
|
8
|
+
* input's `name=`) takes no space after it, so its value is typed next. A new
|
|
9
|
+
* shell is one more entry.
|
|
8
10
|
*/
|
|
9
11
|
/** The hidden command the scripts run. */
|
|
10
12
|
const COMPLETE_COMMAND = "__complete";
|
|
@@ -15,9 +17,11 @@ const SHELL_SCRIPTS = {
|
|
|
15
17
|
# (ork completion zsh > ~/.zsh/completions/_ork), or source it.
|
|
16
18
|
|
|
17
19
|
_ork() {
|
|
18
|
-
local -a lines cands
|
|
19
|
-
local line group=commands files=0 n=0 ret=1
|
|
20
|
+
local -a lines cands opens
|
|
21
|
+
local line value said group=commands files=0 n=0 ret=1
|
|
20
22
|
lines=("\${(@f)$("\${words[1]}" ${COMPLETE_COMMAND} -- "\${(@)words[2,CURRENT]}" 2>/dev/null)}")
|
|
23
|
+
# A last group line offers what the last group gathered.
|
|
24
|
+
lines+=(":group end")
|
|
21
25
|
for line in "\${lines[@]}"; do
|
|
22
26
|
case $line in
|
|
23
27
|
(:files) files=1 ;;
|
|
@@ -25,15 +29,22 @@ _ork() {
|
|
|
25
29
|
if (( \${#cands} )); then
|
|
26
30
|
_describe -V -t "ork-$(( ++n ))" "$group" cands && ret=0
|
|
27
31
|
fi
|
|
28
|
-
|
|
32
|
+
# name= is followed by its value, not a space.
|
|
33
|
+
if (( \${#opens} )); then
|
|
34
|
+
_describe -V -t "ork-$(( ++n ))" "$group" opens -S '' && ret=0
|
|
35
|
+
fi
|
|
36
|
+
cands=() opens=()
|
|
29
37
|
group=\${line#:group }
|
|
30
38
|
;;
|
|
31
|
-
(?*)
|
|
39
|
+
(?*)
|
|
40
|
+
value=\${\${line%%$'\\t'*}//:/\\\\:}
|
|
41
|
+
said=\${line#*$'\\t'}
|
|
42
|
+
# No description: the value alone, or zsh shows an empty one as --.
|
|
43
|
+
[[ $said == "$line" || -z $said ]] || value+=":$said"
|
|
44
|
+
if [[ $line == *=$'\\t'* ]]; then opens+=("$value"); else cands+=("$value"); fi
|
|
45
|
+
;;
|
|
32
46
|
esac
|
|
33
47
|
done
|
|
34
|
-
if (( \${#cands} )); then
|
|
35
|
-
_describe -V -t "ork-$(( ++n ))" "$group" cands && ret=0
|
|
36
|
-
fi
|
|
37
48
|
if (( files )); then
|
|
38
49
|
_files && ret=0
|
|
39
50
|
fi
|
|
@@ -48,9 +59,10 @@ fi
|
|
|
48
59
|
`,
|
|
49
60
|
bash: `# Tab completion for ork, in bash. Add to your ~/.bashrc:
|
|
50
61
|
# eval "$(ork completion bash)"
|
|
51
|
-
# (macOS's bash 3.2
|
|
52
|
-
#
|
|
53
|
-
#
|
|
62
|
+
# (macOS's bash 3.2 loads nothing from source <(…), and prints no error;
|
|
63
|
+
# saving it to a file and sourcing that works in every version.) Every
|
|
64
|
+
# expansion is quoted and lines are read with read -r, so a candidate is never
|
|
65
|
+
# split or glob-expanded.
|
|
54
66
|
|
|
55
67
|
_ork() {
|
|
56
68
|
local line="\${COMP_LINE:0:COMP_POINT}" cur files=0 strip="" out
|
|
@@ -59,10 +71,15 @@ _ork() {
|
|
|
59
71
|
if [[ "$line" == *[[:space:]] ]]; then words+=(""); fi
|
|
60
72
|
cur="\${words[\${#words[@]}-1]}"
|
|
61
73
|
COMPREPLY=()
|
|
62
|
-
# bash splits a word at ":" (ork:feature); offer only
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
# bash splits a word at ":" and "=" (ork:feature, --input=plan=); offer only
|
|
75
|
+
# what follows the last of them that is a word break.
|
|
76
|
+
local brk pre
|
|
77
|
+
for brk in : =; do
|
|
78
|
+
if [[ "$cur" == *"$brk"* && "$COMP_WORDBREAKS" == *"$brk"* ]]; then
|
|
79
|
+
pre="\${cur%"\${cur##*"$brk"}"}"
|
|
80
|
+
if (( \${#pre} > \${#strip} )); then strip="$pre"; fi
|
|
81
|
+
fi
|
|
82
|
+
done
|
|
66
83
|
out="$("\${words[0]}" ${COMPLETE_COMMAND} -- "\${words[@]:1}" 2>/dev/null)"
|
|
67
84
|
while IFS= read -r line; do
|
|
68
85
|
case "$line" in
|
|
@@ -71,6 +88,10 @@ _ork() {
|
|
|
71
88
|
*)
|
|
72
89
|
line="\${line%%$'\\t'*}"
|
|
73
90
|
COMPREPLY+=("\${line#"$strip"}")
|
|
91
|
+
# name= is followed by its value, not a space (bash 4 and later).
|
|
92
|
+
if [[ "$line" == *= ]] && type compopt &>/dev/null; then
|
|
93
|
+
compopt -o nospace
|
|
94
|
+
fi
|
|
74
95
|
;;
|
|
75
96
|
esac
|
|
76
97
|
done <<< "$out"
|
|
@@ -103,68 +124,6 @@ complete -c ork -f -a '(__ork_complete)'
|
|
|
103
124
|
};
|
|
104
125
|
/** The shells there is a script for. */
|
|
105
126
|
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
127
|
// src/help-groups.ts
|
|
169
128
|
const HELP_GROUPS = [
|
|
170
129
|
{
|
|
@@ -220,6 +179,10 @@ const HELP_GROUPS = [
|
|
|
220
179
|
name: "logs",
|
|
221
180
|
short: "Print a run's transcript"
|
|
222
181
|
},
|
|
182
|
+
{
|
|
183
|
+
name: "attach",
|
|
184
|
+
short: "Follow a run here and answer its questions"
|
|
185
|
+
},
|
|
223
186
|
{
|
|
224
187
|
name: "tree",
|
|
225
188
|
short: "Show a run's tree, tokens and cost"
|
|
@@ -270,6 +233,10 @@ const HELP_GROUPS = [
|
|
|
270
233
|
name: "feedback",
|
|
271
234
|
short: "What got in the way in your runs"
|
|
272
235
|
},
|
|
236
|
+
{
|
|
237
|
+
name: "follow-ups",
|
|
238
|
+
short: "What shipped changes left for later"
|
|
239
|
+
},
|
|
273
240
|
{
|
|
274
241
|
name: "worktrees",
|
|
275
242
|
short: "Track and clean up Ork's worktrees"
|
|
@@ -316,6 +283,10 @@ const HELP_GROUPS = [
|
|
|
316
283
|
name: "send",
|
|
317
284
|
short: "Write to a session from a tool node's program"
|
|
318
285
|
},
|
|
286
|
+
{
|
|
287
|
+
name: "once",
|
|
288
|
+
short: "Run a check unless it passed on these files"
|
|
289
|
+
},
|
|
319
290
|
{
|
|
320
291
|
name: "completion",
|
|
321
292
|
short: "Print a tab-completion script"
|
|
@@ -351,4 +322,4 @@ const groupedEntries = (entries) => {
|
|
|
351
322
|
entries: other
|
|
352
323
|
}].filter((group) => group.entries.length > 0);
|
|
353
324
|
};
|
|
354
|
-
export {
|
|
325
|
+
export { SHELL_SCRIPTS as a, SHELLS as i, groupedEntries as n, COMPLETE_COMMAND as r, groupOf as t };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z as storePath } from "./src-BuF9Lbow.mjs";
|
|
2
|
+
import { g as storeGuarded, h as runMarkerIn } from "./src-k0A_QpST.mjs";
|
|
3
|
+
// src/commands/in-a-run.ts
|
|
4
|
+
/**
|
|
5
|
+
* @file The commands that act as the user refuse a program or an agent in a run
|
|
6
|
+
* (ADR-047), in the stores that run guards: the store of a nested run under a
|
|
7
|
+
* temp HOME is not guarded unless an enclosing run lists it. The refusal
|
|
8
|
+
* names the variable it saw, for someone at their own terminal who inherited
|
|
9
|
+
* it. It stops accidents; it is not a boundary.
|
|
10
|
+
*/
|
|
11
|
+
const refusal = (why, env, store) => {
|
|
12
|
+
const marker = runMarkerIn(env);
|
|
13
|
+
return marker === null || !storeGuarded(env, store) ? null : `${why} ${marker} is set here. If this is your own terminal, it inherited that from a program in a run (a tmux server started by one, say); open a shell without it.`;
|
|
14
|
+
};
|
|
15
|
+
/** Why `ork watch --accept` refuses here, or `null` when it may run. */
|
|
16
|
+
const acceptRefusal = (env = process.env, store = storePath()) => refusal("`ork watch --accept` approves code to run with nobody there, so a program or an agent in a run cannot use it.", env, store);
|
|
17
|
+
/** Why `ork chat` refuses here, or `null` when it may run. */
|
|
18
|
+
const chatRefusal = (env = process.env, store = storePath()) => refusal("`ork chat` speaks as you, so a program or an agent in a run cannot use it. A program writes to a session with `ork send`.", env, store);
|
|
19
|
+
/** Why `ork decide` will not answer here, or `null` when it may. */
|
|
20
|
+
const decideRefusal = (env = process.env, store = storePath()) => refusal("`ork decide` answers as you, so a program or an agent in a run cannot answer with it; `ork decide --list` still shows what is open.", env, store);
|
|
21
|
+
/** Why `ork continue` refuses here, or `null` when it may run. */
|
|
22
|
+
const continueRefusal = (env = process.env, store = storePath()) => refusal("`ork continue` undoes a pause you set, so a program or an agent in a run cannot use it.", env, store);
|
|
23
|
+
/** Whether `ork ui` may print its sign-in link, which acts as the user. */
|
|
24
|
+
const mayShowSignIn = (env = process.env, store = storePath()) => !storeGuarded(env, store);
|
|
25
|
+
/** Why `ork attach` will not answer here, or `null` when it may. */
|
|
26
|
+
const attachRefusal = (env = process.env, store = storePath()) => refusal("`ork attach` answers as you, so a program or an agent in a run cannot use it; `ork logs` still shows the run.", env, store);
|
|
27
|
+
export { decideRefusal as a, continueRefusal as i, attachRefusal as n, mayShowSignIn as o, chatRefusal as r, acceptRefusal as t };
|