@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.
Files changed (75) hide show
  1. package/README.md +12 -7
  2. package/dist/__build_info__-0wqQ-rtf.mjs +180 -0
  3. package/dist/apply-Mb0fNpCX.mjs +17 -0
  4. package/dist/argument-error-CihH46Le.mjs +27 -0
  5. package/dist/attach-C0-YyNoY.mjs +92 -0
  6. package/dist/attach-CAKNqnUK.mjs +1259 -0
  7. package/dist/bin.mjs +1885 -1609
  8. package/dist/brief-C2hew8cS.mjs +116 -0
  9. package/dist/{decide-CGyx9rv7.mjs → builtin-files-YYAnbZGD.mjs} +3 -51
  10. package/dist/chips-CP_pgKlj.mjs +48 -0
  11. package/dist/command-tree-CEkzWNy6.mjs +63 -0
  12. package/dist/{completion-CSU5vkEt.mjs → completion-K4rAVsrf.mjs} +200 -30
  13. package/dist/context-B-fjPJky.mjs +106 -0
  14. package/dist/context-DMK0U1w0.mjs +160 -0
  15. package/dist/detached-q0AYG0Bs.mjs +39 -0
  16. package/dist/extra-arguments-BxQMXARO.mjs +13 -0
  17. package/dist/{find-D6Gj1sxg.mjs → find-Ct5CNl1J.mjs} +2 -2
  18. package/dist/flags-C90N4J1i.mjs +145 -0
  19. package/dist/follow-ups-D3HPyNYM.mjs +568 -0
  20. package/dist/format-CiPhQqbX.mjs +260 -0
  21. package/dist/{help-groups-BwYoAWsh.mjs → help-groups-BQvcKQ9H.mjs} +49 -78
  22. package/dist/in-a-run-D_wg4iJi.mjs +27 -0
  23. package/dist/install-Hji6LIei.mjs +259 -0
  24. package/dist/{limits-B1-DYc3d.mjs → limits-BD6RZRCS.mjs} +2 -2
  25. package/dist/{mcp-DE_EmyVm.mjs → mcp-CpbDmBLG.mjs} +6 -6
  26. package/dist/once-J8Qn5fTG.mjs +112 -0
  27. package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
  28. package/dist/output-DXZbnFt_.mjs +458 -0
  29. package/dist/outputs-text-CNTJ7Qq_.mjs +19 -0
  30. package/dist/{pause-CL6N_Xrj.mjs → pause-Cl4ZYA35.mjs} +7 -4
  31. package/dist/print-DzugGZQB.mjs +19 -0
  32. package/dist/project-records-CtkmlsHN.mjs +233 -0
  33. package/dist/refusals-DQQX3AIb.mjs +116 -0
  34. package/dist/{relay-0SD0erwf.mjs → relay-CiP77vga.mjs} +2 -2
  35. package/dist/run-CtuNY3mE.mjs +234 -0
  36. package/dist/send-DRYGks8K.mjs +104 -0
  37. package/dist/short-flags-DkJLWVqh.mjs +1 -0
  38. package/dist/short-flags-Qf21as0E.mjs +83 -0
  39. package/dist/src-BMFQKHz4.mjs +3122 -0
  40. package/dist/{src-BbyEoMn4.mjs → src-BrFfDKux.mjs} +800 -235
  41. package/dist/{src-Dujmvv4s.mjs → src-BuF9Lbow.mjs} +12580 -5505
  42. package/dist/{src-DOcK8Jfp.mjs → src-k0A_QpST.mjs} +1024 -590
  43. package/dist/store-rTgRu8xy.mjs +25 -0
  44. package/dist/ui/THIRD_PARTY_LICENSES.txt +612 -0
  45. package/dist/ui/assets/app-BYxpKJRh.css +1 -0
  46. package/dist/ui/assets/app-SEFEJLkF.js +112 -0
  47. package/dist/ui/assets/{index-9dFha9Qn.js → index-Cb6Voq_W.js} +3 -3
  48. package/dist/ui/assets/theme-CdXvqTQ6.js +1 -0
  49. package/dist/ui/index.html +2 -2
  50. package/dist/{ui-CTmO2Xzu.mjs → ui-C2f37TSb.mjs} +57 -25
  51. package/dist/usage-BuvGcQSk.mjs +256 -0
  52. package/dist/validate-ObP5lt29.mjs +1 -0
  53. package/dist/validate-_HjACtk9.mjs +632 -0
  54. package/dist/verdict-B592j1W3.mjs +84 -0
  55. package/dist/{watch-CtbgDAcz.mjs → watch-DV3ixCYj.mjs} +45 -17
  56. package/dist/{workflows-D1_WVukk.mjs → workflows-QLjBhGNS.mjs} +30 -5
  57. package/dist/worktrees-D3ubG_Qw.mjs +302 -0
  58. package/dist/yolo-BSH1E_kE.mjs +33 -0
  59. package/package.json +10 -8
  60. package/dist/__build_info__-fy6L1Mrd.mjs +0 -615
  61. package/dist/clean-DaHjs__2.mjs +0 -88
  62. package/dist/context-DT8MfEIR.mjs +0 -274
  63. package/dist/format-Dnfv8zMT.mjs +0 -427
  64. package/dist/in-a-run-pLAZeNys.mjs +0 -22
  65. package/dist/list-BrH8KRSJ.mjs +0 -110
  66. package/dist/output-BLQWD6MJ.mjs +0 -950
  67. package/dist/remove-BeGdqroy.mjs +0 -194
  68. package/dist/report-DK4LGqwF.mjs +0 -129
  69. package/dist/send-C8tunWhv.mjs +0 -163
  70. package/dist/src-CCUKRX51.mjs +0 -1204
  71. package/dist/ui/assets/app-BNT0SuIz.css +0 -1
  72. package/dist/ui/assets/app-T7wVAhgx.js +0 -67
  73. package/dist/ui/assets/theme-3xH5jxKs.js +0 -1
  74. package/dist/usage-DYUmP0i4.mjs +0 -419
  75. package/dist/worktrees-DdMYoNOC.mjs +0 -212
@@ -0,0 +1,106 @@
1
+ import { bt as explainSession, ei as LESSON_CAP_TOKENS } from "./src-BuF9Lbow.mjs";
2
+ import { c as write, s as style } from "./output-DXZbnFt_.mjs";
3
+ import { a as refusedBuiltin, s as workflowArg } from "./workflows-QLjBhGNS.mjs";
4
+ import { a as formatReport, t as loadWorkflow } from "./validate-_HjACtk9.mjs";
5
+ import { defineCommand } from "citty";
6
+ // src/commands/context.ts
7
+ /**
8
+ * @file `ork context explain <wf> <node>`: what one node's session would load,
9
+ * layer by layer, as core's `explainSession` plans it without writing
10
+ * anything (one of Ork's own `ork:` workflows is written into the project's
11
+ * run folder first, as `ork run` does): its project instructions, every skill
12
+ * with the layer it came from and whether it is live, shadowed or switched
13
+ * off, its context files, its practices, and estimated tokens.
14
+ */
15
+ /** Loads a workflow file and plans one node's session in it, in place. */
16
+ const explainNode = (file, nodeId, cwd, home) => {
17
+ const { report, workflow, graph, place, workflowDir } = loadWorkflow(file, cwd, home);
18
+ if (workflow === null) return {
19
+ ok: false,
20
+ lines: formatReport(report)
21
+ };
22
+ const result = explainSession(workflow, graph, nodeId, {
23
+ root: place.root,
24
+ orkDir: place.dir,
25
+ workflowDir,
26
+ ...home === void 0 ? {} : { home }
27
+ });
28
+ return result.ok ? result : {
29
+ ok: false,
30
+ lines: [result.error]
31
+ };
32
+ };
33
+ const approx = (count) => style("dim", `≈${count.toLocaleString("en")} tokens`);
34
+ const formatExplanation = (e) => {
35
+ const width = Math.max(8, ...e.skills.map((s) => s.name.length));
36
+ const sourceWidth = Math.max(0, ...e.skills.map((s) => s.source.length));
37
+ const lines = [
38
+ `${style("bold", e.node)} ${style("dim", `(${e.type}) in ${e.workflow}`)}${e.template === void 0 ? "" : style("dim", `, planned as ${e.template}, the node it was spawned from`)}`,
39
+ "",
40
+ ...e.agent === void 0 ? [] : [`${style("bold", "Agent")} ${e.agent.name} ${style("dim", `(${e.agent.where})`)} ${approx(e.agent.tokens)}`],
41
+ `${style("bold", "Instructions")} ${e.instructions}`,
42
+ "",
43
+ style("bold", "Skills")
44
+ ];
45
+ if (e.skills.length === 0) lines.push(style("dim", " none"));
46
+ for (const s of e.skills) {
47
+ const mark = s.state === "live" ? style("green", "✓") : style("dim", "−");
48
+ const state = s.why === void 0 ? approx(s.tokens).replace("tokens", "tokens when used") : style("yellow", `off: ${s.why}`);
49
+ lines.push(` ${mark} ${s.name.padEnd(width)} ${s.layer.padEnd(8)} ${style("dim", s.source.padEnd(sourceWidth))} ${state}`);
50
+ }
51
+ lines.push("", style("bold", "Context files"));
52
+ if (e.files.length === 0) lines.push(style("dim", " none"));
53
+ for (const f of e.files) {
54
+ const cut = f.cutFrom === void 0 ? "" : ` ${style("yellow", `cut at the ${LESSON_CAP_TOKENS.toLocaleString("en")}-token cap; the whole file is ${approx(f.cutFrom)}`)}`;
55
+ lines.push(` ${f.layer.padEnd(8)} ${f.name} ${approx(f.tokens)}${cut}`);
56
+ }
57
+ lines.push("", style("bold", "Practices"));
58
+ for (const p of e.practices) lines.push(` ${p.name.padEnd(8)} ${approx(p.tokens)}`);
59
+ for (const name of e.skippedPractices) lines.push(` ${name.padEnd(8)} ${style("dim", "the project's own skill wins")}`);
60
+ if (e.practices.length === 0 && e.skippedPractices.length === 0) lines.push(style("dim", " off"));
61
+ lines.push("", `Appended to Claude Code's system prompt: ${approx(e.appendedTokens)}`);
62
+ return lines;
63
+ };
64
+ const explain = defineCommand({
65
+ meta: {
66
+ name: "explain",
67
+ description: "Show what one node's session loads, layer by layer"
68
+ },
69
+ args: {
70
+ workflow: {
71
+ type: "positional",
72
+ description: "A workflow file, the name of one in the project's workflows (.ork/workflows/), or one of Ork's own (ork:feedback-apply)",
73
+ required: true
74
+ },
75
+ node: {
76
+ type: "positional",
77
+ description: "Node id",
78
+ required: true
79
+ },
80
+ json: {
81
+ type: "boolean",
82
+ description: "Print JSON",
83
+ default: false
84
+ }
85
+ },
86
+ run: ({ args }) => {
87
+ const cwd = process.cwd();
88
+ if (refusedBuiltin([args.workflow], cwd)) return;
89
+ const result = explainNode(workflowArg(args.workflow, cwd).file, args.node, cwd);
90
+ if (!result.ok) {
91
+ for (const line of result.lines) process.stderr.write(`${line}\n`);
92
+ process.exitCode = 1;
93
+ return;
94
+ }
95
+ if (args.json) write(JSON.stringify(result.explanation, void 0, 2));
96
+ else for (const line of formatExplanation(result.explanation)) write(line);
97
+ }
98
+ });
99
+ const context = defineCommand({
100
+ meta: {
101
+ name: "context",
102
+ description: "Inspect the context Ork compiles for each node"
103
+ },
104
+ subCommands: { explain }
105
+ });
106
+ export { explainNode as n, context as t };
@@ -0,0 +1,160 @@
1
+ import { $n as findFollowUp, Kr as placeFiles, Pn as liveStatus, Sn as deliverFollowUp, Yn as loadProject, gr as runGh, vn as checkFollowUpsDue } from "./src-BuF9Lbow.mjs";
2
+ // ../core/src/follow-ups/take.ts
3
+ const put = (rows, f, run, at) => {
4
+ const next = {
5
+ ...f,
6
+ takenBy: run,
7
+ updatedAt: at
8
+ };
9
+ rows.putFollowUp(next);
10
+ return next;
11
+ };
12
+ /**
13
+ * Why `f` is held by the run that took it, as the end of `taken by run <id>,
14
+ * <note>`; `undefined` when it is free. A run the store does not know counts as
15
+ * live while the mark is recent, so a run that fails its preflight frees its
16
+ * follow-ups when the window ends.
17
+ */
18
+ const heldNote = (f, isLive, now) => {
19
+ if (f.takenBy === void 0) return void 0;
20
+ const live = isLive(f.takenBy);
21
+ if (live !== void 0) return live ? "still running" : void 0;
22
+ return now - Date.parse(f.updatedAt) < 12e4 ? "not started yet; free in under 2 minutes" : void 0;
23
+ };
24
+ /** Whether another run holds `f`: it is not free to take again. */
25
+ const heldByLive = (f, run, isLive, now) => f.takenBy !== run && heldNote(f, isLive, now) !== void 0;
26
+ /**
27
+ * The project's due `agent` follow-ups no live run has taken (`ids`), and those
28
+ * a live run holds (`held`), so a second `--follow-ups due` doesn't take them
29
+ * twice.
30
+ */
31
+ const availableDue = (rows, project, isLive, run = "", now = () => (/* @__PURE__ */ new Date()).toISOString()) => {
32
+ const at = Date.parse(now());
33
+ const due = rows.followUps(project).filter((f) => f.for === "agent" && f.status === "due");
34
+ return {
35
+ ids: due.filter((f) => !heldByLive(f, run, isLive, at)).map((f) => f.id),
36
+ held: due.filter((f) => heldByLive(f, run, isLive, at))
37
+ };
38
+ };
39
+ /**
40
+ * `ork run start --follow-ups <ids>`: the open `agent` follow-ups among `ids`
41
+ * become `run`'s brief; why each other id wasn't taken. One a live run took is
42
+ * refused; one a finished, failed or crashed run took can be taken again.
43
+ */
44
+ const takeFollowUps = (rows, taking, now = () => (/* @__PURE__ */ new Date()).toISOString()) => {
45
+ const taken = [];
46
+ const refused = [];
47
+ const at = Date.parse(now());
48
+ for (const id of taking.ids) {
49
+ const f = rows.followUp(taking.project, id);
50
+ if (f === void 0) refused.push(`${id}: there is no such follow-up`);
51
+ else if (f.for !== "agent") refused.push(`${id}: it is for ${f.for}`);
52
+ else if (f.status === "done" || f.status === "dropped") refused.push(`${id}: it is ${f.status}`);
53
+ else {
54
+ const note = f.takenBy === taking.run ? void 0 : heldNote(f, taking.isLive, at);
55
+ if (note === void 0) taken.push(put(rows, f, taking.run, now()));
56
+ else refused.push(`${id}: taken by run ${f.takenBy}, ${note}`);
57
+ }
58
+ }
59
+ return {
60
+ taken,
61
+ refused
62
+ };
63
+ };
64
+ // src/follow-ups/context.ts
65
+ /**
66
+ * @file What `ork follow-ups` and a run share: the project's `followUps` rule,
67
+ * the sink a run hands its sessions' `follow_up` tool, the due check that
68
+ * runs before every call (a `gh` that fails only warns), and the follow-ups
69
+ * `ork run --follow-ups` takes as a run's brief.
70
+ */
71
+ /** The project's `followUps` rule, when it sets one. */
72
+ const followUpsRule = (place) => loadProject(place.root, placeFiles(place).rules).project?.followUps;
73
+ /** What a run hands its sessions: each `follow_up` call, kept as the rule says. */
74
+ const followUpSink = (options) => (input, at) => {
75
+ const rule = followUpsRule(options.place);
76
+ return deliverFollowUp(input, {
77
+ store: options.store,
78
+ project: options.place.root,
79
+ worktree: at.cwd,
80
+ ...rule === void 0 ? {} : { rule },
81
+ ...options.home === void 0 ? {} : { home: options.home },
82
+ ...options.gh === void 0 ? {} : { gh: options.gh }
83
+ });
84
+ };
85
+ /** Gh's own words when it has no login. */
86
+ const SIGNED_OUT = /not logged in|gh auth login/iu;
87
+ /**
88
+ * A gh error's first line, without a trailing period, so the notice is one
89
+ * line.
90
+ */
91
+ const oneLine = (error) => (error.trim().split("\n")[0] ?? "").trim().replace(/\.$/u, "");
92
+ /**
93
+ * Moves the project's follow-ups whose time has come to due, as every `ork
94
+ * follow-ups` call does first. `gh` failing only warns, through `warn`: one
95
+ * line per call, however many refs failed.
96
+ */
97
+ const refreshDue = async (store, project, warn, gh = runGh) => {
98
+ try {
99
+ const checked = await checkFollowUpsDue({
100
+ rows: store,
101
+ project,
102
+ gh
103
+ });
104
+ const [first] = checked.errors;
105
+ if (first !== void 0) warn(checked.errors.some((error) => SIGNED_OUT.test(error)) ? "Can't check PRs: `gh` isn't signed in. After-merge follow-ups stay waiting; run `gh auth login` yourself." : `Can't check PRs: ${oneLine(first)}. After-merge follow-ups stay waiting.`);
106
+ return checked.due;
107
+ } catch (error) {
108
+ warn(`couldn't check what is due: ${error instanceof Error ? error.message : String(error)}`);
109
+ return [];
110
+ }
111
+ };
112
+ /** The text a run starts from when it takes these follow-ups as its brief. */
113
+ const briefOf = (followUps) => ["Do these follow-ups left by earlier changes:", ...followUps.flatMap((f) => [
114
+ "",
115
+ `## ${f.title} (${f.id})`,
116
+ ...f.body === "" ? [] : [f.body],
117
+ ...f.where.length === 0 ? [] : [`Files: ${f.where.join(", ")}`]
118
+ ])].join("\n");
119
+ /**
120
+ * Whether a run is going, as `ork ps` decides: running with its process alive.
121
+ * `undefined` for a run the store does not know yet: core holds its mark for a
122
+ * short while, as the run may be about to begin.
123
+ */
124
+ const runIsLive = (store) => (runId) => {
125
+ const [run] = store.lookup(runId);
126
+ return run === void 0 ? void 0 : liveStatus(run) === "running";
127
+ };
128
+ const nothing = () => {};
129
+ /**
130
+ * The follow-ups `ids` (or prefixes) name, when every one can be taken as a
131
+ * brief; nothing is written, so a run that fails to start leaves them alone.
132
+ */
133
+ const checkTakeable = (store, project, ids) => {
134
+ const full = [];
135
+ const lines = [];
136
+ for (const id of ids) {
137
+ const found = findFollowUp(store, project, id);
138
+ if (found.ok) full.push(found.followUp.id);
139
+ else lines.push(found.error);
140
+ }
141
+ const { taken, refused } = takeFollowUps({
142
+ followUp: (p, id) => store.followUp(p, id),
143
+ followUps: (p) => store.followUps(p),
144
+ putFollowUp: nothing
145
+ }, {
146
+ project,
147
+ run: "",
148
+ ids: full,
149
+ isLive: runIsLive(store)
150
+ });
151
+ const all = [...lines, ...refused];
152
+ return all.length > 0 ? {
153
+ ok: false,
154
+ lines: all.map((line) => `--follow-ups: ${line}`)
155
+ } : {
156
+ ok: true,
157
+ followUps: taken
158
+ };
159
+ };
160
+ export { refreshDue as a, heldNote as c, followUpsRule as i, takeFollowUps as l, checkTakeable as n, runIsLive as o, followUpSink as r, availableDue as s, briefOf as t };
@@ -0,0 +1,39 @@
1
+ import { pt as detachRun } from "./src-BuF9Lbow.mjs";
2
+ import { n as newRunId } from "./find-Ct5CNl1J.mjs";
3
+ import { c as write, i as detachHint, l as writeErr, s as style } from "./output-DXZbnFt_.mjs";
4
+ import { r as isFlag } from "./short-flags-Qf21as0E.mjs";
5
+ // src/run/detached.ts
6
+ /**
7
+ * @file `ork run --detach`: checks what the run would refuse (a `--follow-ups`
8
+ * that can't be taken) before any run folder is made, then starts the run in
9
+ * the background and says how to follow it.
10
+ */
11
+ /** Starts the run in the background; `rawArgs` are `ork run`'s own, as typed. */
12
+ const startDetached = async (store, options) => {
13
+ const { rawArgs, cwd, target } = options;
14
+ const { prepareFollowUps } = await import("./brief-C2hew8cS.mjs");
15
+ if (await prepareFollowUps(store, rawArgs, void 0, target, void 0, false) === void 0) return;
16
+ const single = options.node !== void 0 && options.node !== "";
17
+ const runId = newRunId(store, single ? "node-" : "");
18
+ const detached = await detachRun({
19
+ store,
20
+ runId,
21
+ args: [
22
+ "run",
23
+ ...rawArgs.filter((arg) => !isFlag(arg, "detach")),
24
+ "--run-id",
25
+ runId
26
+ ],
27
+ cwd,
28
+ runDir: target.runDir(runId)
29
+ });
30
+ if (detached.ok) {
31
+ write(`▸ run ${detached.runId} started in the background`);
32
+ write(style("dim", ` ${detachHint(detached.runId)}`));
33
+ } else {
34
+ writeErr(detached.error);
35
+ writeErr(style("dim", `(output: ${detached.log})`));
36
+ process.exitCode = 1;
37
+ }
38
+ };
39
+ export { startDetached };
@@ -0,0 +1,13 @@
1
+ import { n as argsOf } from "./command-tree-CEkzWNy6.mjs";
2
+ import { t as argumentError } from "./argument-error-CihH46Le.mjs";
3
+ // src/extra-arguments.ts
4
+ /** Refuses the first word beyond the positionals the command declares. */
5
+ const refuseExtraArguments = {
6
+ name: "refuse-extra-arguments",
7
+ setup: async ({ cmd, args }) => {
8
+ const declared = Object.values(await argsOf(cmd)).filter((def) => def.type === "positional").length;
9
+ const extra = args._[declared];
10
+ if (extra !== void 0) throw argumentError(`Unexpected argument \`${extra}\`.`);
11
+ }
12
+ };
13
+ export { refuseExtraArguments as t };
@@ -1,4 +1,4 @@
1
- import { xn as placeFor } from "./src-Dujmvv4s.mjs";
1
+ import { qr as placeFor } from "./src-BuF9Lbow.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  // ../core/src/remote/find.ts
4
4
  /**
@@ -16,7 +16,7 @@ const findRun = (store, arg, cwd) => {
16
16
  });
17
17
  return latest === void 0 ? {
18
18
  ok: false,
19
- error: "No runs in this project yet; see `ork ps --all`."
19
+ error: "No runs in this project yet; see `ork ps --project all`."
20
20
  } : {
21
21
  ok: true,
22
22
  run: latest
@@ -0,0 +1,145 @@
1
+ import { qr as placeFor } from "./src-BuF9Lbow.mjs";
2
+ import { i as parseLimit, n as unknownProjectRefusal, r as parseFilters, t as limitRefusal } from "./refusals-DQQX3AIb.mjs";
3
+ import { t as findRun } from "./find-Ct5CNl1J.mjs";
4
+ import { a as resolved, n as argsOf } from "./command-tree-CEkzWNy6.mjs";
5
+ import { l as writeErr } from "./output-DXZbnFt_.mjs";
6
+ import { t as argumentError } from "./argument-error-CihH46Le.mjs";
7
+ // src/list/known-flags.ts
8
+ /** Names citty reads without the command declaring them. */
9
+ const BUILT_IN = /* @__PURE__ */ new Set([
10
+ "_",
11
+ "help",
12
+ "h"
13
+ ]);
14
+ /** A flag's name without its spelling: `run-id`, `runId` and `run_id` agree. */
15
+ const spelling = (name) => name.replaceAll(/[-_]/gu, "").toLowerCase();
16
+ /** How the flag is typed: `--since`, or `-x` for a one-letter name. */
17
+ const written = (name) => name.length === 1 ? `-${name}` : `--${name}`;
18
+ /** Refuses the first flag the command does not declare. */
19
+ const refuseUnknownFlags = {
20
+ name: "refuse-unknown-flags",
21
+ setup: async ({ cmd, rawArgs, args }) => {
22
+ const [first] = rawArgs;
23
+ const subs = await resolved(cmd.subCommands ?? {});
24
+ if (first !== void 0 && first in subs) return;
25
+ const known = /* @__PURE__ */ new Set();
26
+ for (const [name, def] of Object.entries(await argsOf(cmd))) {
27
+ known.add(spelling(name));
28
+ if ("alias" in def && def.alias !== void 0) for (const alias of [def.alias].flat()) known.add(spelling(alias));
29
+ }
30
+ const unknown = Object.keys(args).find((key) => !BUILT_IN.has(key) && !known.has(spelling(key)));
31
+ if (unknown !== void 0) throw argumentError(`Unknown flag \`${written(unknown)}\`.`);
32
+ }
33
+ };
34
+ // src/list/flags.ts
35
+ /**
36
+ * @file The flags every list command shares (RFC-017), made from its field
37
+ * table: one flag per field (an enum field's values as citty `options`, so
38
+ * `--help` and Tab completion read them), `--ids`, `--limit` and `--json`;
39
+ * and reading them back: each value checked by core's `parseFilters`,
40
+ * `--limit` a positive whole number, `--project` a registered one. A refusal
41
+ * says why on stderr and sets the exit code; the command then returns.
42
+ */
43
+ const fieldArg = (field) => {
44
+ if (field.type !== "enum") return {
45
+ type: "string",
46
+ description: field.description
47
+ };
48
+ return {
49
+ type: "enum",
50
+ options: [...field.values ?? [], ...field.all === true ? ["all"] : []],
51
+ description: field.description
52
+ };
53
+ };
54
+ /** The citty flags of `table`, then `--ids`, `--limit`, `--json`, `extra`. */
55
+ const filterArgs = (table, options = {}) => ({
56
+ ...Object.fromEntries(table.fields.map((field) => [field.name, fieldArg(field)])),
57
+ ...options.ids ?? table.id !== void 0 ? { ids: {
58
+ type: "boolean",
59
+ description: "Print only the ids, comma-separated, on one line",
60
+ default: false
61
+ } } : {},
62
+ ...options.limit ?? true ? { limit: {
63
+ type: "string",
64
+ description: "Show at most this many (a positive whole number)"
65
+ } } : {},
66
+ ...options.json ?? true ? { json: {
67
+ type: "boolean",
68
+ description: "Print JSON",
69
+ default: false
70
+ } } : {},
71
+ ...options.extra
72
+ });
73
+ /** Says why, and makes the command exit 1. */
74
+ const fail = (message) => {
75
+ writeErr(message);
76
+ process.exitCode = 1;
77
+ };
78
+ /** The `--ids` with `--json` refusal; `true` when `args` has both. */
79
+ const refuseIdsWithJson = (args) => {
80
+ if (args.ids !== true || args.json !== true) return false;
81
+ fail("--ids and --json can't be used together.");
82
+ return true;
83
+ };
84
+ /**
85
+ * `args` (a command's parsed flags) read against `table`: the filter, and the
86
+ * limit. `undefined` when something was refused (already said, exit code set).
87
+ */
88
+ const readListArgs = (table, args, options = {}) => {
89
+ if (refuseIdsWithJson(args)) return void 0;
90
+ const parsed = parseFilters(table, args, options.now ?? Date.now());
91
+ if (!parsed.ok) {
92
+ fail(parsed.error);
93
+ return;
94
+ }
95
+ const text = args["limit"];
96
+ if (typeof text !== "string") return {
97
+ filter: parsed.filter,
98
+ limit: void 0
99
+ };
100
+ const limit = parseLimit(text);
101
+ if (limit === null) {
102
+ fail(limitRefusal(text));
103
+ return;
104
+ }
105
+ return {
106
+ filter: parsed.filter,
107
+ limit
108
+ };
109
+ };
110
+ /** The project root a scope other than `all` names. */
111
+ const rootOf = (scope, projects) => scope === "here" ? placeFor(process.cwd(), projects).root : scope.root;
112
+ /**
113
+ * The scope the filter's `project` names, checked against the registered
114
+ * projects (core does not know them): an id or a root, or `all`; none given is
115
+ * `here`. A project not registered is refused, listing the registered ones
116
+ * (said, exit code set, `undefined`).
117
+ */
118
+ const projectScope = (filter, projects) => {
119
+ const key = filter["project"];
120
+ if (key === void 0) return "here";
121
+ if (key === "all") return "all";
122
+ const found = projects.find((p) => p.id === key || p.root === key);
123
+ if (found !== void 0) return { root: found.root };
124
+ fail(unknownProjectRefusal(key, projects.map((p) => p.id)));
125
+ };
126
+ /**
127
+ * `filter` with its `run` (a prefix) made the full id of the run it names, as
128
+ * `ork decide --run` reads it; an unknown or ambiguous run is refused with
129
+ * `findRun`'s message (said, exit code set, `undefined`). No `run` (or an empty
130
+ * one) leaves `filter` as it is.
131
+ */
132
+ const resolveRun = (store, filter) => {
133
+ const run = filter["run"];
134
+ if (run === void 0 || run === "") return filter;
135
+ const found = findRun(store, run, process.cwd());
136
+ if (!found.ok) {
137
+ fail(found.error);
138
+ return;
139
+ }
140
+ return {
141
+ ...filter,
142
+ run: found.run.id
143
+ };
144
+ };
145
+ export { refuseIdsWithJson as a, refuseUnknownFlags as c, readListArgs as i, filterArgs as n, resolveRun as o, projectScope as r, rootOf as s, fail as t };