@krapht/ork 0.3.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__-DdTVd2p5.mjs +615 -0
- package/dist/bin.mjs +1572 -909
- package/dist/clean-NnboOuz6.mjs +88 -0
- package/dist/completion-snnYGjgK.mjs +405 -0
- package/dist/{context-BFGC_C02.mjs → context-DO3sBy9v.mjs} +30 -191
- package/dist/decide-B81wCZQg.mjs +113 -0
- package/dist/{find-BcvhT6UN.mjs → find-BKW-uGey.mjs} +1 -1
- package/dist/{format-DqpObyMl.mjs → format-CmTlTOjT.mjs} +72 -198
- package/dist/help-groups-BwYoAWsh.mjs +354 -0
- package/dist/{in-a-run-BbLhZOFW.mjs → in-a-run-BPXX09Bs.mjs} +1 -1
- package/dist/{limits-BKYAmqCR.mjs → limits-JCve2QpI.mjs} +1 -1
- package/dist/list-C7UEgmtd.mjs +110 -0
- package/dist/{mcp-BWsdQyri.mjs → mcp-CKPyNaKD.mjs} +7 -7
- package/dist/{output-Q2EdeVUt.mjs → output-B1V6ZJSx.mjs} +130 -36
- package/dist/{pause-DKACDfV1.mjs → pause-B_SL_yDz.mjs} +4 -4
- package/dist/{relay-CbK-Wme9.mjs → relay-o-Luee1c.mjs} +2 -2
- package/dist/remove-DAZ7ZCzt.mjs +194 -0
- package/dist/{send-C6yr04Fe.mjs → send-3B64fBNB.mjs} +3 -3
- package/dist/{src-D5ruZFgD.mjs → src-BJTegEzY.mjs} +1212 -57
- package/dist/{src-DtN1k-B3.mjs → src-C38BPzit.mjs} +251 -18
- package/dist/{src-Cz1yOUTg.mjs → src-DHvWU4tX.mjs} +2742 -1659
- package/dist/{src-CZOVF2yU.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-BoG6O-Lp.js → index-D83F2OKZ.js} +2 -2
- package/dist/ui/index.html +2 -3
- package/dist/ui-CY4xxJUu.mjs +472 -0
- package/dist/{usage-BIjf9Xdt.mjs → usage-CJnytoe4.mjs} +6 -5
- package/dist/{watch-B8o4Szd_.mjs → watch-BJB82ZJ7.mjs} +7 -7
- package/dist/workflows-CiM7S3l0.mjs +88 -0
- package/dist/worktrees-86ySXxR6.mjs +212 -0
- package/package.json +6 -6
- package/dist/__build_info__-CXXMLvjt.mjs +0 -104
- package/dist/ui/assets/app-BlDdP396.js +0 -61
- package/dist/ui/assets/app-C722xNZ3.css +0 -1
- package/dist/ui-CZS1XKla.mjs +0 -160
|
@@ -1,175 +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
|
-
import { existsSync } from "node:fs";
|
|
5
|
-
// ../core/src/worktree/list.ts
|
|
6
|
-
/**
|
|
7
|
-
* @file What `ork worktrees` shows and cleans: every worktree and branch Ork's
|
|
8
|
-
* runs recorded creating, whatever the project's git rules named them, and
|
|
9
|
-
* any `ork/*` branch besides, with their state read from git itself.
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* The worktrees the project's runs recorded creating, oldest run first; one
|
|
13
|
-
* entry per branch or folder, from the latest run that created it.
|
|
14
|
-
*/
|
|
15
|
-
const createdWorktrees = (store, project) => {
|
|
16
|
-
const byName = /* @__PURE__ */ new Map();
|
|
17
|
-
for (const run of store.runs({ project }).toReversed()) for (const { event } of store.events(run.id)) {
|
|
18
|
-
if (event.type !== "worktree" || event.action !== "created") continue;
|
|
19
|
-
byName.set(event.branch ?? event.path, {
|
|
20
|
-
runId: run.id,
|
|
21
|
-
nodeId: event.id,
|
|
22
|
-
path: event.path,
|
|
23
|
-
...event.branch === void 0 ? {} : { branch: event.branch }
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
return [...byName.values()];
|
|
27
|
-
};
|
|
28
|
-
const parsePorcelain = (text) => text.split("\n\n").map((block) => lines(block)).map((fields) => {
|
|
29
|
-
const value = (key) => fields.find((f) => f.startsWith(`${key} `))?.slice(key.length + 1);
|
|
30
|
-
return {
|
|
31
|
-
path: value("worktree") ?? "",
|
|
32
|
-
branch: (value("branch") ?? "").replace(/^refs\/heads\//u, ""),
|
|
33
|
-
prunable: fields.some((f) => f.startsWith("prunable"))
|
|
34
|
-
};
|
|
35
|
-
}).filter((entry) => entry.path !== "");
|
|
36
|
-
/** The `ork/<run>/<node>` branches of runs that recorded nothing. */
|
|
37
|
-
const prefixed = (branch) => {
|
|
38
|
-
const [, runId = "", ...rest] = branch.split("/");
|
|
39
|
-
return {
|
|
40
|
-
runId,
|
|
41
|
-
nodeId: rest.join("/"),
|
|
42
|
-
path: "",
|
|
43
|
-
branch
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
/** How many commits a detached worktree holds that no branch, tag or remote has. */
|
|
47
|
-
const onNoBranch = async (entry) => entry === void 0 ? "0" : (await git(entry.path, [
|
|
48
|
-
"rev-list",
|
|
49
|
-
"--count",
|
|
50
|
-
"HEAD",
|
|
51
|
-
"--not",
|
|
52
|
-
"--branches",
|
|
53
|
-
"--tags",
|
|
54
|
-
"--remotes"
|
|
55
|
-
])).stdout;
|
|
56
|
-
/** One created worktree or branch, with its state read from git. */
|
|
57
|
-
const stateOf = async (root, entries, item) => {
|
|
58
|
-
const { branch } = item;
|
|
59
|
-
const entry = branch === void 0 ? entries.find((e) => e.path === item.path) : entries.find((e) => e.branch === branch);
|
|
60
|
-
const missing = entry !== void 0 && (entry.prunable || !existsSync(entry.path));
|
|
61
|
-
const status = entry === void 0 || missing ? "" : (await git(entry.path, ["status", "--porcelain"])).stdout;
|
|
62
|
-
const checkedOut = branch === void 0 && entry !== void 0 && entry.branch !== "" ? entry.branch : void 0;
|
|
63
|
-
const on = branch ?? checkedOut;
|
|
64
|
-
const counted = on === void 0 ? await onNoBranch(entry === void 0 || missing ? void 0 : entry) : (await git(root, [
|
|
65
|
-
"rev-list",
|
|
66
|
-
"--count",
|
|
67
|
-
`HEAD..${on}`
|
|
68
|
-
])).stdout;
|
|
69
|
-
const ahead = Number(counted);
|
|
70
|
-
return {
|
|
71
|
-
...branch === void 0 ? {} : { branch },
|
|
72
|
-
...checkedOut === void 0 ? {} : { checkedOut },
|
|
73
|
-
runId: item.runId,
|
|
74
|
-
nodeId: item.nodeId,
|
|
75
|
-
path: entry?.path ?? null,
|
|
76
|
-
missing,
|
|
77
|
-
dirty: lines(status).length > 0,
|
|
78
|
-
ahead: Number.isNaN(ahead) ? 0 : ahead,
|
|
79
|
-
merged: on !== void 0 && ahead === 0,
|
|
80
|
-
inMainCheckout: entry?.path === root
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
|
-
/**
|
|
84
|
-
* Every worktree and branch Ork's runs created in the repository at `root`,
|
|
85
|
-
* with its state: those the runs recorded (`created`, whatever the project's
|
|
86
|
-
* git rules named them), and any `ork/` branch besides. A recorded one that git
|
|
87
|
-
* no longer has is left out.
|
|
88
|
-
*/
|
|
89
|
-
const listOrkWorktrees = async (root, created = []) => {
|
|
90
|
-
const entries = parsePorcelain(await gitOk(root, [
|
|
91
|
-
"worktree",
|
|
92
|
-
"list",
|
|
93
|
-
"--porcelain"
|
|
94
|
-
]));
|
|
95
|
-
const branches = lines(await gitOk(root, [
|
|
96
|
-
"for-each-ref",
|
|
97
|
-
"--format=%(refname:short)",
|
|
98
|
-
"refs/heads/"
|
|
99
|
-
]));
|
|
100
|
-
const recordedBranches = new Set(created.flatMap((c) => c.branch ?? []));
|
|
101
|
-
const items = [...created.filter((c) => c.branch === void 0 ? entries.some((e) => e.path === c.path) : branches.includes(c.branch)), ...branches.filter((b) => b.startsWith("ork/") && !recordedBranches.has(b)).map((b) => prefixed(b))];
|
|
102
|
-
return Promise.all(items.map((item) => stateOf(root, entries, item)));
|
|
103
|
-
};
|
|
104
|
-
const cleanStep = (item, options) => {
|
|
105
|
-
if (item.inMainCheckout) return {
|
|
106
|
-
item,
|
|
107
|
-
removeWorktree: false,
|
|
108
|
-
deleteBranch: false,
|
|
109
|
-
skip: "checked out in the main checkout"
|
|
110
|
-
};
|
|
111
|
-
if (item.dirty && !options.force) return {
|
|
112
|
-
item,
|
|
113
|
-
removeWorktree: false,
|
|
114
|
-
deleteBranch: false,
|
|
115
|
-
skip: "uncommitted changes"
|
|
116
|
-
};
|
|
117
|
-
if (item.branch === void 0 && item.checkedOut === void 0 && item.ahead > 0 && !options.force) return {
|
|
118
|
-
item,
|
|
119
|
-
removeWorktree: false,
|
|
120
|
-
deleteBranch: false,
|
|
121
|
-
skip: "commits on no branch"
|
|
122
|
-
};
|
|
123
|
-
const removable = item.path !== null && !item.missing;
|
|
124
|
-
if (options.branches && item.branch !== void 0 && !item.merged && !options.force) return {
|
|
125
|
-
item,
|
|
126
|
-
removeWorktree: removable,
|
|
127
|
-
deleteBranch: false,
|
|
128
|
-
skip: "branch not merged"
|
|
129
|
-
};
|
|
130
|
-
return {
|
|
131
|
-
item,
|
|
132
|
-
removeWorktree: removable,
|
|
133
|
-
deleteBranch: options.branches && item.branch !== void 0
|
|
134
|
-
};
|
|
135
|
-
};
|
|
136
|
-
/**
|
|
137
|
-
* What `ork worktrees clean` would do. A worktree with uncommitted changes, an
|
|
138
|
-
* unmerged branch, or a detached worktree holding commits no branch has, is
|
|
139
|
-
* kept unless `force`.
|
|
140
|
-
*/
|
|
141
|
-
const planClean = (items, filter, options) => items.filter((item) => filter.runId === void 0 || item.runId === filter.runId).filter((item) => !filter.merged || item.merged).map((item) => cleanStep(item, options));
|
|
142
|
-
/** Carries out a clean plan, then prunes stale worktree entries. */
|
|
143
|
-
const applyClean = async (root, steps, force) => {
|
|
144
|
-
const done = [];
|
|
145
|
-
await gitOk(root, ["worktree", "prune"]);
|
|
146
|
-
for (const step of steps) {
|
|
147
|
-
const { item } = step;
|
|
148
|
-
if (step.removeWorktree && item.path !== null) {
|
|
149
|
-
await removeWorktree(root, item.path, force);
|
|
150
|
-
done.push(`removed ${item.path}`);
|
|
151
|
-
}
|
|
152
|
-
if (step.deleteBranch && item.branch !== void 0) {
|
|
153
|
-
await gitOk(root, [
|
|
154
|
-
"branch",
|
|
155
|
-
force ? "-D" : "-d",
|
|
156
|
-
item.branch
|
|
157
|
-
]);
|
|
158
|
-
done.push(`deleted ${item.branch}`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
await gitOk(root, ["worktree", "prune"]);
|
|
162
|
-
return done;
|
|
163
|
-
};
|
|
164
4
|
// src/human.ts
|
|
165
5
|
/**
|
|
166
6
|
* @file The terminal `HumanChannel`: answers `ask_user` questions, gates and
|
|
167
7
|
* permission prompts on the terminal, one at a time, and denies or defers
|
|
168
|
-
* when no one is there.
|
|
169
|
-
*
|
|
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`.
|
|
170
11
|
*/
|
|
171
12
|
const PREVIEW = 160;
|
|
172
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): ";
|
|
173
15
|
const NO_ONE_TO_ASK = "no one (nobody is at the terminal; use your best judgment and say so in your report)";
|
|
174
16
|
/** One line that shows what a tool is about to do. */
|
|
175
17
|
const previewInput = (toolName, input) => {
|
|
@@ -215,6 +57,12 @@ const createTerminalHuman = (io) => {
|
|
|
215
57
|
});
|
|
216
58
|
return signal === void 0 ? rl.question(text) : rl.question(text, { signal });
|
|
217
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
|
+
};
|
|
218
66
|
const choose = async (hint, options, recommended, signal) => {
|
|
219
67
|
const picked = pickOption(await prompt(` Choose${hint}: `, signal), options, recommended);
|
|
220
68
|
if (picked !== void 0) return picked;
|
|
@@ -227,7 +75,7 @@ const createTerminalHuman = (io) => {
|
|
|
227
75
|
return result;
|
|
228
76
|
};
|
|
229
77
|
return {
|
|
230
|
-
permit: ({ nodeId, toolName, input, signal }) => oneAtATime(async () => {
|
|
78
|
+
permit: ({ nodeId, toolName, input, signal, onNote }) => oneAtATime(async () => {
|
|
231
79
|
if (signal?.aborted === true) throw new Error("the prompt was withdrawn");
|
|
232
80
|
say(`${style(["yellow", "bold"], "?")} ${style("bold", nodeId)} wants to run ${previewInput(toolName, input)}`);
|
|
233
81
|
if (!io.interactive) {
|
|
@@ -238,24 +86,28 @@ const createTerminalHuman = (io) => {
|
|
|
238
86
|
};
|
|
239
87
|
}
|
|
240
88
|
const answer = (await prompt(" Allow? [y/N] ", signal)).trim().toLowerCase();
|
|
241
|
-
|
|
89
|
+
if (answer === "y" || answer === "yes") return { allow: true };
|
|
90
|
+
await takeNote(onNote, signal);
|
|
91
|
+
return {
|
|
242
92
|
allow: false,
|
|
243
93
|
message: "The user did not allow this tool."
|
|
244
94
|
};
|
|
245
95
|
}),
|
|
246
|
-
ask: ({ nodeId, question, options, recommended, context, signal }) => oneAtATime(() => {
|
|
247
|
-
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");
|
|
248
98
|
say(`${style(["yellow", "bold"], "?")} ${style("bold", nodeId)} asks: ${question}`);
|
|
249
99
|
if (context !== void 0) say(style("dim", ` ${context}`));
|
|
250
100
|
for (const [index, option] of options.entries()) say(optionLine(option, index, recommended));
|
|
251
101
|
if (!io.interactive) {
|
|
252
102
|
const answer = recommended ?? NO_ONE_TO_ASK;
|
|
253
103
|
say(style("dim", ` no terminal; answered ${answer}`));
|
|
254
|
-
return
|
|
104
|
+
return answer;
|
|
255
105
|
}
|
|
256
106
|
const fallback = options.findIndex((o) => o.id === recommended);
|
|
257
107
|
const hint = fallback === -1 ? "" : ` [${String(fallback + 1)}]`;
|
|
258
|
-
|
|
108
|
+
const picked = await choose(hint, options, recommended, signal);
|
|
109
|
+
await takeNote(onNote, signal);
|
|
110
|
+
return picked;
|
|
259
111
|
}),
|
|
260
112
|
close: () => {
|
|
261
113
|
rl?.close();
|
|
@@ -291,12 +143,38 @@ const formatBusEvent = (event) => {
|
|
|
291
143
|
if (event.type === "owner.answered") return [style("dim", `→ ${event.answer} (owner)`)];
|
|
292
144
|
return [style("dim", "→ its session ended first; asking you")];
|
|
293
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
|
+
};
|
|
294
171
|
// src/run/format.ts
|
|
295
172
|
/** Who answered a decision, as the log says it. */
|
|
296
173
|
const ANSWERED_BY = {
|
|
297
174
|
terminal: "terminal",
|
|
298
175
|
decide: "ork decide",
|
|
299
|
-
ui: "ork ui"
|
|
176
|
+
ui: "ork ui",
|
|
177
|
+
owner: "its owner"
|
|
300
178
|
};
|
|
301
179
|
const STATUS_MARK = {
|
|
302
180
|
pass: style("green", "✓"),
|
|
@@ -330,7 +208,10 @@ const formatOutcome = (nodeId, outcome) => {
|
|
|
330
208
|
};
|
|
331
209
|
/** The transcript lines for one event; tool names come from `tools`. */
|
|
332
210
|
const formatEvent = (nodeId, event, tools) => {
|
|
333
|
-
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
|
+
}
|
|
334
215
|
if (event.type === "assistant.text") return indent(event.text);
|
|
335
216
|
if (event.type === "tool.called") {
|
|
336
217
|
tools.set(event.id, event.name);
|
|
@@ -361,8 +242,12 @@ const formatRunEvent = (event, width, tools) => {
|
|
|
361
242
|
if (event.type === "node.event") {
|
|
362
243
|
const inner = event.event;
|
|
363
244
|
if (inner.type === "session.finished") return [];
|
|
364
|
-
|
|
365
|
-
|
|
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, "")}`);
|
|
366
251
|
}
|
|
367
252
|
if (event.type === "node.finished") {
|
|
368
253
|
const mark = event.ok ? style("green", "✓") : style("red", "✗");
|
|
@@ -383,11 +268,13 @@ const formatRunEvent = (event, width, tools) => {
|
|
|
383
268
|
if (event.type === "decision.opened") {
|
|
384
269
|
const what = event.kind === "permission" ? "asks to" : "asks:";
|
|
385
270
|
const question = event.kind === "permission" ? event.question.replace(/^Allow /u, "run ").replace(/\?$/u, "") : event.question;
|
|
386
|
-
|
|
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})`)}`];
|
|
387
273
|
}
|
|
388
274
|
if (event.type === "decision.answered") {
|
|
389
|
-
const by = ANSWERED_BY[event.by];
|
|
390
|
-
|
|
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}`)}`];
|
|
391
278
|
}
|
|
392
279
|
if (event.type === "decision.withdrawn") return [`${tag(event.node)} ${style("dim", "→ question withdrawn")}`];
|
|
393
280
|
if (event.type === "run.command") {
|
|
@@ -416,23 +303,6 @@ const formatRunEvent = (event, width, tools) => {
|
|
|
416
303
|
}
|
|
417
304
|
return formatBusEvent(event).map((line) => `${tag(event.node)} ${line}`);
|
|
418
305
|
};
|
|
419
|
-
const RUN_MARK = {
|
|
420
|
-
succeeded: style("green", "✓"),
|
|
421
|
-
failed: style("red", "✗"),
|
|
422
|
-
budget_exhausted: style("yellow", "!"),
|
|
423
|
-
quota_exhausted: style("yellow", "!"),
|
|
424
|
-
timed_out: style("yellow", "!"),
|
|
425
|
-
interrupted: style("yellow", "!")
|
|
426
|
-
};
|
|
427
|
-
/** The run summary: status, cost, what ran, branches to look at, and why. */
|
|
428
|
-
const formatRunResult = (result) => {
|
|
429
|
-
const ran = result.nodes.filter((n) => n.runs > 0).length;
|
|
430
|
-
const skipped = result.nodes.length - ran;
|
|
431
|
-
const lines = ["", `${RUN_MARK[result.status]} run ${result.runId} ${result.status.replace("_", " ")} ${style("dim", `· ${cost(result.costUsd)} · ${String(ran)} ran, ${String(skipped)} skipped`)}`];
|
|
432
|
-
for (const kept of result.worktrees) lines.push(` ${kept.id}: ${kept.branch ?? "detached"} ${style("dim", `at ${kept.path} (${kept.reason})`)}`);
|
|
433
|
-
for (const reason of result.reasons) lines.push(` ${style("red", reason)}`);
|
|
434
|
-
return lines;
|
|
435
|
-
};
|
|
436
306
|
// src/runs/format.ts
|
|
437
307
|
/**
|
|
438
308
|
* @file How stored runs read on the terminal: the `ork ps` table and the `ork
|
|
@@ -466,7 +336,10 @@ const duration = (ms) => {
|
|
|
466
336
|
if (s < 3600) return `${String(Math.floor(s / 60))}m${String(s % 60).padStart(2, "0")}s`;
|
|
467
337
|
return `${String(Math.floor(s / 3600))}h${String(Math.floor(s % 3600 / 60)).padStart(2, "0")}m`;
|
|
468
338
|
};
|
|
469
|
-
/**
|
|
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
|
+
*/
|
|
470
343
|
const formatRuns = (runs, withProject, now = Date.now(), statusOf = liveStatus) => {
|
|
471
344
|
if (runs.length === 0) return ["No runs."];
|
|
472
345
|
const rows = runs.map((run) => {
|
|
@@ -477,7 +350,8 @@ const formatRuns = (runs, withProject, now = Date.now(), statusOf = liveStatus)
|
|
|
477
350
|
workflow: run.workflow,
|
|
478
351
|
status,
|
|
479
352
|
when: `${ago(run.startedAt, now)} · ${duration(end - Date.parse(run.startedAt))}`,
|
|
480
|
-
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,
|
|
481
355
|
project: run.project
|
|
482
356
|
};
|
|
483
357
|
});
|
|
@@ -502,7 +376,7 @@ const formatRuns = (runs, withProject, now = Date.now(), statusOf = liveStatus)
|
|
|
502
376
|
r.workflow.padEnd(w.workflow),
|
|
503
377
|
style(STATUS_STYLE[r.status] ?? "reset", r.status.padEnd(w.status)),
|
|
504
378
|
style("dim", r.when.padEnd(w.when)),
|
|
505
|
-
r.cost.padStart(w.cost),
|
|
379
|
+
r.called ? style("dim", r.cost.padStart(w.cost)) : r.cost.padStart(w.cost),
|
|
506
380
|
...withProject ? [style("dim", r.project)] : []
|
|
507
381
|
].join(" "))];
|
|
508
382
|
};
|
|
@@ -550,4 +424,4 @@ const formatTree = (roots) => {
|
|
|
550
424
|
return `${text.padEnd(label)} ${style("dim", node.type.padEnd(type))} ${state}${usage}`;
|
|
551
425
|
});
|
|
552
426
|
};
|
|
553
|
-
export { formatRunEvent as a,
|
|
427
|
+
export { formatRunEvent as a, formatEvent as i, formatRuns as n, createTerminalHuman as o, formatTree as r, ago as t };
|