@krapht/ork 1.0.0-alpha.14 → 1.0.0-alpha.16

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 (48) hide show
  1. package/README.md +10 -4
  2. package/dist/__build_info__-DcRdK0Du.mjs +359 -0
  3. package/dist/apply-Mb0fNpCX.mjs +17 -0
  4. package/dist/{attach-CdtGX9se.mjs → attach-7lB5_jpr.mjs} +5 -4
  5. package/dist/{store-CleF7KeS.mjs → attach-B-FeE1dJ.mjs} +60 -25
  6. package/dist/bin.mjs +328 -1734
  7. package/dist/brief-BvMtIFio.mjs +58 -0
  8. package/dist/{builtin-files-Bdq23IIi.mjs → builtin-files-BRSOObAv.mjs} +1 -1
  9. package/dist/command-tree-BkSYWmYJ.mjs +63 -0
  10. package/dist/{completion--ywde-56.mjs → completion-Cgb2qPPh.mjs} +45 -6
  11. package/dist/context-BN9A2Nnu.mjs +106 -0
  12. package/dist/{find-BVX-bL4u.mjs → find-mLYsR0uC.mjs} +2 -2
  13. package/dist/flags-CdgIH1L6.mjs +230 -0
  14. package/dist/follow-ups-DxCsOby1.mjs +496 -0
  15. package/dist/{format-CvTE5CZQ.mjs → format-hiDvtNGM.mjs} +35 -8
  16. package/dist/{help-groups-CpR2UzSn.mjs → help-groups-hB6kY34d.mjs} +5 -63
  17. package/dist/{in-a-run-cL5N9gpH.mjs → in-a-run-a447hxq8.mjs} +1 -1
  18. package/dist/{__build_info__-B82-dGJY.mjs → install-23g_1ngs.mjs} +3 -227
  19. package/dist/{limits-Dhe--wmX.mjs → limits-C_Xz_t3C.mjs} +1 -1
  20. package/dist/{mcp-CsmboyXv.mjs → mcp-CWng9I9n.mjs} +6 -6
  21. package/dist/{once-CrwI-ZdN.mjs → once-lvwcWfVf.mjs} +3 -3
  22. package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
  23. package/dist/{pause-DssrYcda.mjs → pause-Cy11KrXK.mjs} +3 -3
  24. package/dist/post-C9soCx7i.mjs +46 -0
  25. package/dist/print-BaWiWf4v.mjs +19 -0
  26. package/dist/project-records-BRiyQzUo.mjs +230 -0
  27. package/dist/{relay-DASRgsTQ.mjs → relay-C0M1TWMB.mjs} +1 -1
  28. package/dist/run-B-j3Qxqc.mjs +1464 -0
  29. package/dist/{run-D8divGCs.mjs → run-D_u9V_Ve.mjs} +1 -1
  30. package/dist/{send-CXv8bx0i.mjs → send-qBxj0JbJ.mjs} +2 -2
  31. package/dist/{src-njdtejgZ.mjs → src-CXBmaGwS.mjs} +213 -75
  32. package/dist/{src-C7z_6fhn.mjs → src-DHDQ0itC.mjs} +106 -50
  33. package/dist/{src-tsuZRu0H.mjs → src-DN-RPsil.mjs} +5423 -4467
  34. package/dist/{src-8Mul0695.mjs → src-nXLIwDrt.mjs} +325 -81
  35. package/dist/store-Dyr1mP8K.mjs +16 -0
  36. package/dist/ui/assets/app-BPSCxb_l.css +1 -0
  37. package/dist/ui/assets/app-CnNRowFn.js +111 -0
  38. package/dist/ui/assets/{index-D-_uPI2v.js → index-CVzwYlz3.js} +2 -2
  39. package/dist/ui/index.html +1 -1
  40. package/dist/{ui-CNO1M0qT.mjs → ui-DLrSt5ny.mjs} +3 -3
  41. package/dist/{usage-BVXi0iFY.mjs → usage-CxmpYYO4.mjs} +61 -107
  42. package/dist/{context-Y_I4Mofq.mjs → validate-BVrT43GO.mjs} +35 -108
  43. package/dist/{watch-DVNIciHp.mjs → watch-mkLm4t9D.mjs} +19 -5
  44. package/dist/{workflows-B30QkwTk.mjs → workflows-Bvl2Fquu.mjs} +2 -2
  45. package/dist/{worktrees-DD4_TpkH.mjs → worktrees-DOOCoS2w.mjs} +49 -20
  46. package/package.json +8 -4
  47. package/dist/ui/assets/app-D_nAPnHM.css +0 -1
  48. package/dist/ui/assets/app-DqBAtXSZ.js +0 -111
@@ -0,0 +1,58 @@
1
+ import { gn as takeFollowUps } from "./src-DN-RPsil.mjs";
2
+ import { l as briefOf, n as flagValues, u as checkTakeable } from "./run-B-j3Qxqc.mjs";
3
+ import { n as newRunId } from "./find-mLYsR0uC.mjs";
4
+ import { l as writeErr } from "./output-B1kwo0YU.mjs";
5
+ import { t as loadWorkflow } from "./validate-BVrT43GO.mjs";
6
+ // src/follow-ups/brief.ts
7
+ /**
8
+ * @file `ork run --follow-ups <ids>`: the `agent` follow-ups become the run's
9
+ * `brief` input, and are marked taken by the run, which finishes them when it
10
+ * publishes (`ork follow-ups published`). Everything that could refuse them
11
+ * (an unknown id, one for someone else, a workflow with no `brief` input) is
12
+ * checked before any is marked, so a run that doesn't start leaves them be.
13
+ */
14
+ /** The ids `--follow-ups` was given, one per comma or repeat. */
15
+ const idsOf = (rawArgs) => flagValues(rawArgs, "--follow-ups").flatMap((value) => value.split(",")).map((id) => id.trim()).filter((id) => id !== "");
16
+ /**
17
+ * What stops `--follow-ups` from starting a run, one line each; none when
18
+ * nothing does.
19
+ */
20
+ const problemsOf = (loaded, inputs) => {
21
+ const name = loaded.workflow?.name ?? "";
22
+ if (loaded.workflow?.inputs?.["brief"] === void 0) return [`--follow-ups fills a workflow's \`brief\` input, and ${name} has none.`];
23
+ return inputs.some((input) => input.startsWith("brief=")) ? ["--follow-ups and --input brief=… both set the brief."] : [];
24
+ };
25
+ /**
26
+ * The inputs and run id `ork run` starts with: those it was given and, for
27
+ * `--follow-ups`, a `brief` made of the follow-ups with the id of the run that
28
+ * takes them. `undefined` after saying why it can't start.
29
+ */
30
+ const prepareFollowUps = (store, rawArgs, runId, target) => {
31
+ const inputs = flagValues(rawArgs, "--input");
32
+ const ids = idsOf(rawArgs);
33
+ const plain = {
34
+ inputs,
35
+ ...runId === void 0 ? {} : { runId }
36
+ };
37
+ if (ids.length === 0) return plain;
38
+ const loaded = loadWorkflow(target.file, process.cwd(), void 0, store.projects());
39
+ if (loaded.workflow === null) return plain;
40
+ const takeable = checkTakeable(store, loaded.place.root, ids);
41
+ const problems = [...problemsOf(loaded, inputs), ...takeable.ok ? [] : takeable.lines];
42
+ if (!takeable.ok || problems.length > 0) {
43
+ for (const line of problems) writeErr(line);
44
+ process.exitCode = 1;
45
+ return;
46
+ }
47
+ const taker = runId ?? newRunId(store);
48
+ takeFollowUps(store, {
49
+ project: loaded.place.root,
50
+ run: taker,
51
+ ids: takeable.followUps.map((f) => f.id)
52
+ });
53
+ return {
54
+ inputs: [...inputs, `brief=${briefOf(takeable.followUps)}`],
55
+ runId: taker
56
+ };
57
+ };
58
+ export { prepareFollowUps };
@@ -1,4 +1,4 @@
1
- import { Cr as guardedBase, Tr as writePlainFile, pr as placeFiles, wr as plainPathInside } from "./src-tsuZRu0H.mjs";
1
+ import { Gr as writePlainFile, Nr as placeFiles, Ur as guardedBase, Wr as plainPathInside } from "./src-DN-RPsil.mjs";
2
2
  import path from "node:path";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { createHash } from "node:crypto";
@@ -0,0 +1,63 @@
1
+ // src/command-tree.ts
2
+ /** How a flag that only Ork passes to itself starts its description. */
3
+ const INTERNAL = "Internal:";
4
+ /** Whether citty was given a function that gives the value. */
5
+ const isThunk = (value) => typeof value === "function";
6
+ /** A value citty lets be given as is, as a promise, or as a function. */
7
+ const resolved = (value) => Promise.resolve(isThunk(value) ? value() : value);
8
+ /** `args` without the flags only Ork passes to itself. */
9
+ const publicArgs = (args) => Object.fromEntries(Object.entries(args).filter(([, def]) => !(def.description ?? "").startsWith(INTERNAL)));
10
+ /** A command's flags and positionals, which citty lets be given lazily. */
11
+ const argsOf = (cmd) => cmd.args === void 0 ? Promise.resolve({}) : resolved(cmd.args);
12
+ const listOf = (value) => value === void 0 ? [] : [value].flat();
13
+ const flagOf = (name, def) => ({
14
+ name,
15
+ description: def.description ?? "",
16
+ takesValue: def.type === "string" || def.type === "enum",
17
+ aliases: "alias" in def ? listOf(def.alias) : [],
18
+ options: def.type === "enum" ? def.options ?? [] : [],
19
+ negatable: def.type === "boolean" && def.default === true
20
+ });
21
+ /** A command's public flags and positionals, in the order it defines them. */
22
+ const argInfoOf = async (cmd) => {
23
+ const entries = Object.entries(publicArgs(await argsOf(cmd)));
24
+ return {
25
+ flags: entries.filter(([, def]) => def.type !== "positional").map(([name, def]) => flagOf(name, def)),
26
+ positionals: entries.filter(([, def]) => def.type === "positional").map(([name, def]) => ({
27
+ name,
28
+ description: def.description ?? ""
29
+ }))
30
+ };
31
+ };
32
+ /**
33
+ * A command's subcommands, in order, loaded ones resolved. A name whose
34
+ * definition is one listed before it (`wt`, `worktrees`'s) is an alias of that
35
+ * one, not an entry of its own.
36
+ */
37
+ const subcommandsOf = async (cmd) => {
38
+ const subs = cmd.subCommands === void 0 ? {} : await resolved(cmd.subCommands);
39
+ const loaded = await Promise.all(Object.entries(subs).map(async ([name, sub]) => {
40
+ const def = await resolved(sub);
41
+ return {
42
+ name,
43
+ def,
44
+ meta: def.meta === void 0 ? {} : await resolved(def.meta)
45
+ };
46
+ }));
47
+ const entries = [];
48
+ for (const { name, def, meta } of loaded) {
49
+ const same = entries.find((entry) => entry.def === def);
50
+ if (same === void 0) entries.push({
51
+ name,
52
+ aliases: listOf(meta.alias),
53
+ description: meta.description ?? "",
54
+ hidden: meta.hidden === true,
55
+ def
56
+ });
57
+ else same.aliases.push(name);
58
+ }
59
+ return entries;
60
+ };
61
+ /** The subcommand `name` (or one of its aliases) of `entries`. */
62
+ const findEntry = (entries, name) => entries.find((entry) => entry.name === name || entry.aliases.includes(name));
63
+ export { publicArgs as a, findEntry as i, argInfoOf as n, resolved as o, argsOf as r, subcommandsOf as s, INTERNAL as t };
@@ -1,9 +1,10 @@
1
- import { U as openStore, Ut as liveStatus, W as storePath, ii as AGENT_DRIVERS, mr as placeFor, pr as placeFiles, qr as NodeIdSchema, ut as pendingDecisions } from "./src-tsuZRu0H.mjs";
2
- import { a as isBuiltinArg, i as builtinWorkflowText, r as builtinWorkflowNames } from "./builtin-files-Bdq23IIi.mjs";
3
- import { t as findRun } from "./find-BVX-bL4u.mjs";
4
- import { a as findEntry, c as subcommandsOf, d as SHELL_SCRIPTS, l as COMPLETE_COMMAND, n as groupedEntries, r as argInfoOf, t as groupOf, u as SHELLS } from "./help-groups-CpR2UzSn.mjs";
1
+ import { L as openStore, Nr as placeFiles, Pr as placeFor, R as storePath, kn as liveStatus, pi as NodeIdSchema, rt as pendingDecisions, wi as AGENT_DRIVERS } from "./src-DN-RPsil.mjs";
2
+ import { a as isBuiltinArg, i as builtinWorkflowText, r as builtinWorkflowNames } from "./builtin-files-BRSOObAv.mjs";
3
+ import { t as findRun } from "./find-mLYsR0uC.mjs";
4
+ import { a as SHELL_SCRIPTS, i as SHELLS, n as groupedEntries, r as COMPLETE_COMMAND, t as groupOf } from "./help-groups-hB6kY34d.mjs";
5
+ import { i as findEntry, n as argInfoOf, s as subcommandsOf } from "./command-tree-BkSYWmYJ.mjs";
5
6
  import { l as writeErr } from "./output-B1kwo0YU.mjs";
6
- import { a as resolveWorkflowArg, n as WORKFLOW_SUFFIX } from "./workflows-B30QkwTk.mjs";
7
+ import { a as resolveWorkflowArg, n as WORKFLOW_SUFFIX } from "./workflows-Bvl2Fquu.mjs";
7
8
  import path from "node:path";
8
9
  import { parse } from "yaml";
9
10
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
@@ -118,6 +119,29 @@ const decisionCandidates = (store, arg, cwd, place) => {
118
119
  description: `${decision.kind} of ${decision.runId}/${decision.node}`
119
120
  }));
120
121
  };
122
+ /** The registered projects, by id, with their root. */
123
+ const projectCandidates = (store) => store.projects().map((p) => ({
124
+ value: p.id,
125
+ description: p.root
126
+ }));
127
+ /**
128
+ * Whom a follow-up is for (`--for`): `agent`, `me`, a registered project as
129
+ * `project:<id>`; a person has no list, so `person:` is left to be typed.
130
+ */
131
+ const forCandidates = (store) => [
132
+ {
133
+ value: "agent",
134
+ description: "an agent picks it up"
135
+ },
136
+ {
137
+ value: "me",
138
+ description: "you do"
139
+ },
140
+ ...(store?.projects() ?? []).map((p) => ({
141
+ value: `project:${p.id}`,
142
+ description: p.root
143
+ }))
144
+ ];
121
145
  // src/completion/values.ts
122
146
  const withStore = (context, read) => {
123
147
  const store = context.store();
@@ -154,7 +178,19 @@ const inputs = {
154
178
  };
155
179
  const decisions = {
156
180
  group: "decisions",
157
- values: (context) => withStore(context, (store) => decisionCandidates(store, context.given["run"], context.cwd, placeOf(context.cwd, store)))
181
+ values: (context) => withStore(context, (store) => decisionCandidates(store, context.given["run"] ?? context.given["run-id"], context.cwd, placeOf(context.cwd, store)))
182
+ };
183
+ /** A registered project's id, for `--project`; `all` too (RFC-017). */
184
+ const projects = {
185
+ group: "projects",
186
+ values: (context) => [{
187
+ value: "all",
188
+ description: "every project"
189
+ }, ...withStore(context, projectCandidates)]
190
+ };
191
+ const forWhom = {
192
+ group: "for",
193
+ values: (context) => forCandidates(context.store())
158
194
  };
159
195
  const drivers = {
160
196
  group: "drivers",
@@ -178,6 +214,7 @@ const shells = {
178
214
  /** Providers by argument name, or by command path and name. */
179
215
  const VALUE_PROVIDERS = {
180
216
  run: runs,
217
+ "decide run-id": runs,
181
218
  workflow: workflows,
182
219
  files: {
183
220
  ...workflows,
@@ -188,6 +225,8 @@ const VALUE_PROVIDERS = {
188
225
  "decide id": decisions,
189
226
  "completion shell": shells,
190
227
  driver: drivers,
228
+ project: projects,
229
+ for: forWhom,
191
230
  path: files,
192
231
  file: files,
193
232
  output: files
@@ -0,0 +1,106 @@
1
+ import { Vr as LESSON_CAP_TOKENS, gt as explainSession } from "./src-DN-RPsil.mjs";
2
+ import { c as write, s as style } from "./output-B1kwo0YU.mjs";
3
+ import { i as refusedBuiltin, o as workflowArg } from "./workflows-Bvl2Fquu.mjs";
4
+ import { a as formatReport, t as loadWorkflow } from "./validate-BVrT43GO.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 };
@@ -1,4 +1,4 @@
1
- import { mr as placeFor } from "./src-tsuZRu0H.mjs";
1
+ import { Pr as placeFor } from "./src-DN-RPsil.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,230 @@
1
+ import "./src-DN-RPsil.mjs";
2
+ import { t as INTERNAL } from "./command-tree-BkSYWmYJ.mjs";
3
+ import { l as writeErr } from "./output-B1kwo0YU.mjs";
4
+ // ../core/src/list-filters/time.ts
5
+ /**
6
+ * @file The times every list's `--since` and `--until` take (specs/usage.md,
7
+ * RFC-017), and `ork prune --older-than`'s age: an age (`24h`, `30d`, `12w`)
8
+ * or a day (`2026-11-01`, in the machine's time zone).
9
+ */
10
+ const UNITS = {
11
+ h: 36e5,
12
+ d: 864e5,
13
+ w: 6048e5
14
+ };
15
+ /** `30d`, `12w`, `24h` before `now`, as an ISO time; `null` when it is not one. */
16
+ const agoTime = (text, now) => {
17
+ const match = /^(\d+)([hdw])$/u.exec(text);
18
+ if (match === null) return null;
19
+ const [, amount = "0", unit = "d"] = match;
20
+ if (Number(amount) === 0) return null;
21
+ const date = new Date(now - Number(amount) * (UNITS[unit] ?? 0));
22
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
23
+ };
24
+ /** A `YYYY-MM-DD` day's start in the machine's time zone, `after` days on. */
25
+ const dayTime = (text, after) => {
26
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/u.exec(text);
27
+ if (match === null) return null;
28
+ const [, year = "", month = "", day = ""] = match;
29
+ const start = new Date(Number(year), Number(month) - 1, Number(day));
30
+ if (start.getMonth() !== Number(month) - 1) return null;
31
+ if (start.getDate() !== Number(day)) return null;
32
+ start.setDate(start.getDate() + after);
33
+ return start.toISOString();
34
+ };
35
+ /** The start of the day `days` whole days ago, counting today as one. */
36
+ const wholeDays = (days, now) => {
37
+ const today = new Date(now);
38
+ return new Date(today.getFullYear(), today.getMonth(), today.getDate() - (days - 1)).toISOString();
39
+ };
40
+ /**
41
+ * `--since`: an age or the day it starts from. Days and weeks are whole days,
42
+ * today among them, as the Usage panel counts them; hours are exact.
43
+ */
44
+ const sinceTime = (text, now) => {
45
+ const ago = agoTime(text, now);
46
+ if (ago === null) return dayTime(text, 0);
47
+ if (text.endsWith("h")) return ago;
48
+ const amount = Number(text.slice(0, -1));
49
+ return wholeDays(text.endsWith("w") ? amount * 7 : amount, now);
50
+ };
51
+ /** `--until`: the last day it includes. */
52
+ const untilTime = (text) => dayTime(text, 1);
53
+ // ../core/src/list-filters/parse.ts
54
+ /**
55
+ * @file Reads a list's filter flags against its field table (RFC-017): each
56
+ * value checked, times turned into ISO times, and `--limit`.
57
+ */
58
+ const FOR = /^(agent|me|(project|person):\S+)$/u;
59
+ /** The field's value, checked; `null` when the field does not take it. */
60
+ const valueOf = (field, text, now) => {
61
+ if (text === "all" && field.all === true) return "all";
62
+ return {
63
+ enum: () => field.values?.includes(text) === true ? text : null,
64
+ text: () => text === "" ? null : text,
65
+ for: () => FOR.test(text) ? text : null,
66
+ since: () => sinceTime(text, now),
67
+ until: () => untilTime(text)
68
+ }[field.type]();
69
+ };
70
+ /** What the field takes, for the error that refuses a value. */
71
+ const takes = (field) => {
72
+ const all = field.all === true ? ["all"] : [];
73
+ const or = all.length === 0 ? "" : " or all";
74
+ return {
75
+ enum: `one of ${[...field.values ?? [], ...all].join(", ")}`,
76
+ text: `a value${or}`,
77
+ for: "agent, me, project:<name> or person:<name>",
78
+ since: `an age (30d), a day (2026-11-01)${or}`,
79
+ until: "a day (2026-11-01)"
80
+ }[field.type];
81
+ };
82
+ /**
83
+ * The filter `given` (a command's parsed flags, by field name) asks for, each
84
+ * value checked against `table`; `now` dates an age. The first value a field
85
+ * does not take is refused, naming the flag and what it takes.
86
+ */
87
+ const parseFilters = (table, given, now) => {
88
+ const filter = {};
89
+ for (const field of table.fields) {
90
+ const text = given[field.name];
91
+ if (typeof text !== "string") continue;
92
+ const value = valueOf(field, text, now);
93
+ if (value === null) return {
94
+ ok: false,
95
+ error: `--${field.name} takes ${takes(field)}, not \`${text}\`.`
96
+ };
97
+ filter[field.name] = value;
98
+ }
99
+ return {
100
+ ok: true,
101
+ filter
102
+ };
103
+ };
104
+ /** `--limit`: a positive whole number, else `null` (`0`, `-1`, `abc`, `2.5`). */
105
+ const parseLimit = (text) => {
106
+ if (!/^[1-9]\d*$/u.test(text)) return null;
107
+ const limit = Number(text);
108
+ return Number.isSafeInteger(limit) ? limit : null;
109
+ };
110
+ // src/list/all-alias.ts
111
+ /**
112
+ * @file The deprecated `--all` of `ps`, `usage`, `decide`, `follow-ups list`
113
+ * and `feedback list` (RFC-017): it stands for `--project all` or `--status
114
+ * all`, is left out of `--help` and Tab completion (an `Internal:` flag), and
115
+ * warns once on stderr. `prune --all` is not this one.
116
+ */
117
+ /** The hidden `--all` flag, for a command's `args`. */
118
+ const allAlias = { all: {
119
+ type: "boolean",
120
+ description: `${INTERNAL} deprecated, use --project all or --status all`,
121
+ default: false
122
+ } };
123
+ /**
124
+ * `args` with `all` in `--all`'s place when `--all` was given and that flag was
125
+ * not (a given one wins); the warning prints either way. `args` as is when
126
+ * `--all` was not given.
127
+ */
128
+ const warnAll = (args, flag) => {
129
+ if (args["all"] !== true) return args;
130
+ writeErr(`--all is deprecated; use --${flag} all.`);
131
+ return args[flag] === void 0 ? {
132
+ ...args,
133
+ [flag]: "all"
134
+ } : args;
135
+ };
136
+ // src/list/flags.ts
137
+ /**
138
+ * @file The flags every list command shares (RFC-017), made from its field
139
+ * table: one flag per field (an enum field's values as citty `options`, so
140
+ * `--help` and Tab completion read them), `--ids`, `--limit` and `--json`;
141
+ * and reading them back: each value checked by core's `parseFilters`,
142
+ * `--limit` a positive whole number, `--project` a registered one. A refusal
143
+ * says why on stderr and sets the exit code; the command then returns.
144
+ */
145
+ const fieldArg = (field) => {
146
+ if (field.type !== "enum") return {
147
+ type: "string",
148
+ description: field.description
149
+ };
150
+ return {
151
+ type: "enum",
152
+ options: [...field.values ?? [], ...field.all === true ? ["all"] : []],
153
+ description: field.description
154
+ };
155
+ };
156
+ /** The citty flags of `table`, then `--ids`, `--limit`, `--json`, `extra`. */
157
+ const filterArgs = (table, options = {}) => ({
158
+ ...Object.fromEntries(table.fields.map((field) => [field.name, fieldArg(field)])),
159
+ ...options.ids ?? table.id !== void 0 ? { ids: {
160
+ type: "boolean",
161
+ description: "Print only the ids, comma-separated, on one line",
162
+ default: false
163
+ } } : {},
164
+ ...options.limit ?? true ? { limit: {
165
+ type: "string",
166
+ description: "Show at most this many (a positive whole number)"
167
+ } } : {},
168
+ ...options.json ?? true ? { json: {
169
+ type: "boolean",
170
+ description: "Print JSON",
171
+ default: false
172
+ } } : {},
173
+ ...options.extra
174
+ });
175
+ /** Says why, and makes the command exit 1. */
176
+ const fail = (message) => {
177
+ writeErr(message);
178
+ process.exitCode = 1;
179
+ };
180
+ /** The `--ids` with `--json` refusal; `true` when `args` has both. */
181
+ const refuseIdsWithJson = (args) => {
182
+ if (args.ids !== true || args.json !== true) return false;
183
+ fail("--ids and --json can't be used together.");
184
+ return true;
185
+ };
186
+ /**
187
+ * `args` (a command's parsed flags) read against `table`: the filter, and the
188
+ * limit. `all` names the flag `--all` stands for (`project` or `status`):
189
+ * `--all` fills it when it is not given, with one warning. `undefined` when
190
+ * something was refused (already said, exit code set).
191
+ */
192
+ const readListArgs = (table, args, options = {}) => {
193
+ if (refuseIdsWithJson(args)) return void 0;
194
+ const given = options.all === void 0 ? args : warnAll(args, options.all);
195
+ const parsed = parseFilters(table, given, options.now ?? Date.now());
196
+ if (!parsed.ok) {
197
+ fail(parsed.error);
198
+ return;
199
+ }
200
+ const text = args["limit"];
201
+ if (typeof text !== "string") return {
202
+ filter: parsed.filter,
203
+ limit: void 0
204
+ };
205
+ const limit = parseLimit(text);
206
+ if (limit === null) {
207
+ fail(`--limit takes a positive whole number, not \`${text}\`.`);
208
+ return;
209
+ }
210
+ return {
211
+ filter: parsed.filter,
212
+ limit
213
+ };
214
+ };
215
+ /**
216
+ * The scope the filter's `project` names, checked against the registered
217
+ * projects (core does not know them): an id or a root, or `all`; none given is
218
+ * `here`. A project not registered is refused, listing the registered ones
219
+ * (said, exit code set, `undefined`).
220
+ */
221
+ const projectScope = (filter, projects) => {
222
+ const key = filter["project"];
223
+ if (key === void 0) return "here";
224
+ if (key === "all") return "all";
225
+ const found = projects.find((p) => p.id === key || p.root === key);
226
+ if (found !== void 0) return { root: found.root };
227
+ const names = projects.map((p) => p.id);
228
+ fail(`No registered project \`${key}\`; registered: ${names.length === 0 ? "none" : names.join(", ")}.`);
229
+ };
230
+ export { refuseIdsWithJson as a, readListArgs as i, filterArgs as n, allAlias as o, projectScope as r, agoTime as s, fail as t };