@krapht/ork 1.0.0-alpha.15 → 1.0.0-alpha.17

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 (50) hide show
  1. package/README.md +10 -4
  2. package/dist/{__build_info__-Bsb1kjgF.mjs → __build_info__-C1Nm0hlJ.mjs} +25 -6
  3. package/dist/apply-Mb0fNpCX.mjs +17 -0
  4. package/dist/{attach-CPespxlQ.mjs → attach-EP374oMP.mjs} +5 -5
  5. package/dist/{attach-BHO3X3cf.mjs → attach-RQx__RzH.mjs} +46 -8
  6. package/dist/bin.mjs +238 -369
  7. package/dist/brief-Bs3cMQU0.mjs +100 -0
  8. package/dist/{builtin-files-2bHf76x-.mjs → builtin-files-yYaNTQRU.mjs} +1 -1
  9. package/dist/command-tree-BkSYWmYJ.mjs +63 -0
  10. package/dist/{completion-CQ2UYKuJ.mjs → completion-DIXWlnAl.mjs} +87 -6
  11. package/dist/{context-BNqDw9z1.mjs → context-Dq-VM_sq.mjs} +3 -3
  12. package/dist/{find-BYLlszqj.mjs → find-k489laBa.mjs} +2 -2
  13. package/dist/flags-B4ZZWspN.mjs +125 -0
  14. package/dist/{follow-ups-B4GJ3Vzw.mjs → follow-ups-DD8NTPe6.mjs} +59 -24
  15. package/dist/{format-C8MsFj8_.mjs → format-DQc9A_ei.mjs} +17 -4
  16. package/dist/{help-groups-CQ98mLjF.mjs → help-groups-hB6kY34d.mjs} +1 -63
  17. package/dist/in-a-run-D_9GlAlS.mjs +27 -0
  18. package/dist/{install-DWtXTq4y.mjs → install-PVlV2QPq.mjs} +1 -1
  19. package/dist/{limits-B1qzS4cX.mjs → limits-CHqBO7wb.mjs} +1 -1
  20. package/dist/{mcp-BOrVkyhN.mjs → mcp-D38Ajb0b.mjs} +6 -6
  21. package/dist/{once-C1fL3GVo.mjs → once-DuUzpzL2.mjs} +3 -3
  22. package/dist/parse-BzWpMN-a.mjs +107 -0
  23. package/dist/{pause-DOMK-R8V.mjs → pause-Bx3ePGrn.mjs} +3 -3
  24. package/dist/{post-BzA6Qobq.mjs → post-2CaJz_mV.mjs} +5 -3
  25. package/dist/print-n2qetUHc.mjs +19 -0
  26. package/dist/project-records-Cune1l6I.mjs +230 -0
  27. package/dist/{relay-OWuu4fib.mjs → relay-D7RN4m68.mjs} +1 -1
  28. package/dist/{run-CiPsW9Kf.mjs → run-BfxdsQJk.mjs} +1 -1
  29. package/dist/{run--4-1xAF2.mjs → run-OOWQIKMJ.mjs} +33 -20
  30. package/dist/{send-CnNNW-ot.mjs → send-CKth_vgn.mjs} +2 -2
  31. package/dist/{src-B9zWIVqo.mjs → src-BHod38UQ.mjs} +104 -72
  32. package/dist/{src-Dr_fTSu-.mjs → src-DLsFsmOD.mjs} +790 -502
  33. package/dist/{src-DQKz2eOH.mjs → src-DOjTY2ET.mjs} +238 -79
  34. package/dist/{src-DcrhgipG.mjs → src-DmmrX2_C.mjs} +162 -58
  35. package/dist/{store-oKJ-cCf3.mjs → store-DACH768X.mjs} +1 -1
  36. package/dist/ui/assets/app-9Q-62Ywz.css +1 -0
  37. package/dist/ui/assets/app-CN3acQ-1.js +111 -0
  38. package/dist/ui/assets/{index-BtwkftKe.js → index-B2uGxA4W.js} +2 -2
  39. package/dist/ui/index.html +1 -1
  40. package/dist/{ui-CzKlsCnm.mjs → ui-CJ-BX12n.mjs} +3 -3
  41. package/dist/{usage-DHGNk2JL.mjs → usage-DxZ6JGd4.mjs} +62 -107
  42. package/dist/{validate-Bs_gg22g.mjs → validate-BS0nCccW.mjs} +35 -8
  43. package/dist/{watch-CCCfw3n7.mjs → watch-Dp7IB4FR.mjs} +5 -5
  44. package/dist/{workflows-CW28ZU-z.mjs → workflows-Djo1edlK.mjs} +2 -2
  45. package/dist/{worktrees-CyE8aF6j.mjs → worktrees-Drof_Nyx.mjs} +49 -20
  46. package/package.json +8 -4
  47. package/dist/brief-za0ozN_E.mjs +0 -58
  48. package/dist/in-a-run-Bp0k0J09.mjs +0 -24
  49. package/dist/ui/assets/app-Bdc6OBeZ.css +0 -1
  50. package/dist/ui/assets/app-Drp8Dm9y.js +0 -111
@@ -0,0 +1,100 @@
1
+ import { _n as takeFollowUps, nr as runGh } from "./src-DLsFsmOD.mjs";
2
+ import { f as refreshDue, l as briefOf, n as flagValues, u as checkTakeable } from "./run-OOWQIKMJ.mjs";
3
+ import { n as newRunId } from "./find-k489laBa.mjs";
4
+ import { l as writeErr } from "./output-B1kwo0YU.mjs";
5
+ import { t as loadWorkflow } from "./validate-BS0nCccW.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
+ const FLAG = "--follow-ups";
15
+ /** What `--follow-ups` takes besides ids: every due agent follow-up. */
16
+ const DUE = "due";
17
+ /**
18
+ * Each `--follow-ups` value as typed (one per use, still comma-joined), and
19
+ * whether one is missing: no value, an empty one, or a next argument that is
20
+ * another flag.
21
+ */
22
+ const given = (rawArgs) => {
23
+ const values = [];
24
+ let empty = false;
25
+ for (const [index, arg] of rawArgs.entries()) {
26
+ if (arg !== FLAG && !arg.startsWith(`${FLAG}=`)) continue;
27
+ const next = rawArgs[index + 1];
28
+ const parts = (arg === FLAG ? next === void 0 || next.startsWith("--") ? "" : next : arg.slice(13)).split(",").map((part) => part.trim()).filter((part) => part !== "");
29
+ if (parts.length === 0) empty = true;
30
+ values.push(...parts);
31
+ }
32
+ return {
33
+ values,
34
+ empty
35
+ };
36
+ };
37
+ /**
38
+ * Says what `--follow-ups` needs when it is given with nothing after it, and
39
+ * fails the command; true when it is fine (or not given).
40
+ */
41
+ const followUpsFlagFine = (rawArgs) => {
42
+ if (!given(rawArgs).empty) return true;
43
+ writeErr(`${FLAG} needs follow-up ids, or \`${DUE}\` for every due agent follow-up. \`ork follow-ups list\` shows them.`);
44
+ process.exitCode = 1;
45
+ return false;
46
+ };
47
+ /**
48
+ * What stops `--follow-ups` from starting a run, one line each; none when
49
+ * nothing does.
50
+ */
51
+ const problemsOf = (loaded, inputs) => {
52
+ const name = loaded.workflow?.name ?? "";
53
+ if (loaded.workflow?.inputs?.["brief"] === void 0) return [`--follow-ups fills a workflow's \`brief\` input, and ${name} has none.`];
54
+ return inputs.some((input) => input.startsWith("brief=")) ? ["--follow-ups and --input brief=… both set the brief."] : [];
55
+ };
56
+ /**
57
+ * The inputs and run id `ork run` starts with: those it was given and, for
58
+ * `--follow-ups`, a `brief` made of the follow-ups with the id of the run that
59
+ * takes them. `undefined` after saying why it can't start.
60
+ */
61
+ const prepareFollowUps = async (store, rawArgs, runId, target, gh = runGh) => {
62
+ const inputs = flagValues(rawArgs, "--input");
63
+ const plain = {
64
+ inputs,
65
+ ...runId === void 0 ? {} : { runId }
66
+ };
67
+ if (!followUpsFlagFine(rawArgs)) return void 0;
68
+ const { values } = given(rawArgs);
69
+ if (values.length === 0) return plain;
70
+ const loaded = loadWorkflow(target.file, process.cwd(), void 0, store.projects());
71
+ if (loaded.workflow === null) return plain;
72
+ const project = loaded.place.root;
73
+ const named = values.filter((value) => value !== DUE);
74
+ if (values.includes(DUE)) await refreshDue(store, project, writeErr, gh);
75
+ const due = values.includes(DUE) ? store.followUps(project).filter((f) => f.for === "agent" && f.status === "due").map((f) => f.id) : [];
76
+ if (named.length === 0 && due.length === 0) {
77
+ writeErr(`${FLAG} ${DUE}: no agent follow-ups are due. \`ork follow-ups list\` shows them.`);
78
+ process.exitCode = 1;
79
+ return;
80
+ }
81
+ const takeable = checkTakeable(store, project, [...named, ...due]);
82
+ const problems = [...problemsOf(loaded, inputs), ...takeable.ok ? [] : takeable.lines];
83
+ if (!takeable.ok || problems.length > 0) {
84
+ for (const line of problems) writeErr(line);
85
+ process.exitCode = 1;
86
+ return;
87
+ }
88
+ const followUps = [...new Map(takeable.followUps.map((f) => [f.id, f])).values()];
89
+ const taker = runId ?? newRunId(store);
90
+ takeFollowUps(store, {
91
+ project,
92
+ run: taker,
93
+ ids: followUps.map((f) => f.id)
94
+ });
95
+ return {
96
+ inputs: [...inputs, `brief=${briefOf(followUps)}`],
97
+ runId: taker
98
+ };
99
+ };
100
+ export { followUpsFlagFine, prepareFollowUps };
@@ -1,4 +1,4 @@
1
- import { Er as placeFiles, Ir as guardedBase, Lr as plainPathInside, Rr as writePlainFile } from "./src-Dr_fTSu-.mjs";
1
+ import { Gr as plainPathInside, Kr as writePlainFile, Pr as placeFiles, Wr as guardedBase } from "./src-DLsFsmOD.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 { Cn as liveStatus, Dr as placeFor, Er as placeFiles, U as openStore, W as storePath, _i as AGENT_DRIVERS, oi as NodeIdSchema, ut as pendingDecisions } from "./src-Dr_fTSu-.mjs";
2
- import { a as isBuiltinArg, i as builtinWorkflowText, r as builtinWorkflowNames } from "./builtin-files-2bHf76x-.mjs";
3
- import { t as findRun } from "./find-BYLlszqj.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-CQ98mLjF.mjs";
1
+ import { An as liveStatus, Fr as placeFor, L as openStore, Pr as placeFiles, R as storePath, Ti as AGENT_DRIVERS, mi as NodeIdSchema, rt as pendingDecisions } from "./src-DLsFsmOD.mjs";
2
+ import { a as isBuiltinArg, i as builtinWorkflowText, r as builtinWorkflowNames } from "./builtin-files-yYaNTQRU.mjs";
3
+ import { t as findRun } from "./find-k489laBa.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-CW28ZU-z.mjs";
7
+ import { a as resolveWorkflowArg, n as WORKFLOW_SUFFIX } from "./workflows-Djo1edlK.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,62 @@ 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
+ ];
145
+ /** The longest title a menu shows. */
146
+ const TITLE_LIMIT = 60;
147
+ /** A title as one short line: first line only, cut with an ellipsis. */
148
+ const shortTitle = (title) => {
149
+ const line = (title.split("\n")[0] ?? "").trim();
150
+ return line.length > TITLE_LIMIT ? `${line.slice(0, 59)}…` : line;
151
+ };
152
+ /**
153
+ * What `ork run --follow-ups` takes: `due` (every due agent follow-up), then
154
+ * the project's open agent follow-ups, due before waiting, each described by
155
+ * its title. Closed and other people's follow-ups are left out; an unreadable
156
+ * store gives `due` only.
157
+ */
158
+ const followUpCandidates = (store, place) => {
159
+ const open = (status) => {
160
+ try {
161
+ return (store?.followUps(place.root) ?? []).filter((f) => f.for === "agent" && f.status === status).map((f) => ({
162
+ value: f.id,
163
+ description: shortTitle(f.title)
164
+ }));
165
+ } catch {
166
+ return [];
167
+ }
168
+ };
169
+ return [
170
+ {
171
+ value: "due",
172
+ description: "every due agent follow-up"
173
+ },
174
+ ...open("due"),
175
+ ...open("waiting")
176
+ ];
177
+ };
121
178
  // src/completion/values.ts
122
179
  const withStore = (context, read) => {
123
180
  const store = context.store();
@@ -154,7 +211,27 @@ const inputs = {
154
211
  };
155
212
  const decisions = {
156
213
  group: "decisions",
157
- values: (context) => withStore(context, (store) => decisionCandidates(store, context.given["run"], context.cwd, placeOf(context.cwd, store)))
214
+ values: (context) => withStore(context, (store) => decisionCandidates(store, context.given["run"] ?? context.given["run-id"], context.cwd, placeOf(context.cwd, store)))
215
+ };
216
+ /** A registered project's id, for `--project`; `all` too (RFC-017). */
217
+ const projects = {
218
+ group: "projects",
219
+ values: (context) => [{
220
+ value: "all",
221
+ description: "every project"
222
+ }, ...withStore(context, projectCandidates)]
223
+ };
224
+ const forWhom = {
225
+ group: "for",
226
+ values: (context) => forCandidates(context.store())
227
+ };
228
+ /** `ork run --follow-ups`: `due`, then the project's open agent follow-ups. */
229
+ const followUps = {
230
+ group: "follow-ups",
231
+ values: (context) => {
232
+ const store = context.store();
233
+ return followUpCandidates(store, placeOf(context.cwd, store));
234
+ }
158
235
  };
159
236
  const drivers = {
160
237
  group: "drivers",
@@ -178,6 +255,7 @@ const shells = {
178
255
  /** Providers by argument name, or by command path and name. */
179
256
  const VALUE_PROVIDERS = {
180
257
  run: runs,
258
+ "decide run-id": runs,
181
259
  workflow: workflows,
182
260
  files: {
183
261
  ...workflows,
@@ -186,8 +264,11 @@ const VALUE_PROVIDERS = {
186
264
  node: nodes,
187
265
  input: inputs,
188
266
  "decide id": decisions,
267
+ "run follow-ups": followUps,
189
268
  "completion shell": shells,
190
269
  driver: drivers,
270
+ project: projects,
271
+ for: forWhom,
191
272
  path: files,
192
273
  file: files,
193
274
  output: files
@@ -1,7 +1,7 @@
1
- import { Pr as LESSON_CAP_TOKENS, wt as explainSession } from "./src-Dr_fTSu-.mjs";
1
+ import { Hr as LESSON_CAP_TOKENS, gt as explainSession } from "./src-DLsFsmOD.mjs";
2
2
  import { c as write, s as style } from "./output-B1kwo0YU.mjs";
3
- import { i as refusedBuiltin, o as workflowArg } from "./workflows-CW28ZU-z.mjs";
4
- import { a as formatReport, t as loadWorkflow } from "./validate-Bs_gg22g.mjs";
3
+ import { i as refusedBuiltin, o as workflowArg } from "./workflows-Djo1edlK.mjs";
4
+ import { a as formatReport, t as loadWorkflow } from "./validate-BS0nCccW.mjs";
5
5
  import { defineCommand } from "citty";
6
6
  // src/commands/context.ts
7
7
  /**
@@ -1,4 +1,4 @@
1
- import { Dr as placeFor } from "./src-Dr_fTSu-.mjs";
1
+ import { Fr as placeFor } from "./src-DLsFsmOD.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,125 @@
1
+ import "./src-DLsFsmOD.mjs";
2
+ import { n as parseLimit, t as parseFilters } from "./parse-BzWpMN-a.mjs";
3
+ import { t as INTERNAL } from "./command-tree-BkSYWmYJ.mjs";
4
+ import { l as writeErr } from "./output-B1kwo0YU.mjs";
5
+ // src/list/all-alias.ts
6
+ /**
7
+ * @file The deprecated `--all` of `ps`, `usage`, `decide`, `follow-ups list`
8
+ * and `feedback list` (RFC-017): it stands for `--project all` or `--status
9
+ * all`, is left out of `--help` and Tab completion (an `Internal:` flag), and
10
+ * warns once on stderr. `prune --all` is not this one.
11
+ */
12
+ /** The hidden `--all` flag, for a command's `args`. */
13
+ const allAlias = { all: {
14
+ type: "boolean",
15
+ description: `${INTERNAL} deprecated, use --project all or --status all`,
16
+ default: false
17
+ } };
18
+ /**
19
+ * `args` with `all` in `--all`'s place when `--all` was given and that flag was
20
+ * not (a given one wins); the warning prints either way. `args` as is when
21
+ * `--all` was not given.
22
+ */
23
+ const warnAll = (args, flag) => {
24
+ if (args["all"] !== true) return args;
25
+ writeErr(`--all is deprecated; use --${flag} all.`);
26
+ return args[flag] === void 0 ? {
27
+ ...args,
28
+ [flag]: "all"
29
+ } : args;
30
+ };
31
+ // src/list/flags.ts
32
+ /**
33
+ * @file The flags every list command shares (RFC-017), made from its field
34
+ * table: one flag per field (an enum field's values as citty `options`, so
35
+ * `--help` and Tab completion read them), `--ids`, `--limit` and `--json`;
36
+ * and reading them back: each value checked by core's `parseFilters`,
37
+ * `--limit` a positive whole number, `--project` a registered one. A refusal
38
+ * says why on stderr and sets the exit code; the command then returns.
39
+ */
40
+ const fieldArg = (field) => {
41
+ if (field.type !== "enum") return {
42
+ type: "string",
43
+ description: field.description
44
+ };
45
+ return {
46
+ type: "enum",
47
+ options: [...field.values ?? [], ...field.all === true ? ["all"] : []],
48
+ description: field.description
49
+ };
50
+ };
51
+ /** The citty flags of `table`, then `--ids`, `--limit`, `--json`, `extra`. */
52
+ const filterArgs = (table, options = {}) => ({
53
+ ...Object.fromEntries(table.fields.map((field) => [field.name, fieldArg(field)])),
54
+ ...options.ids ?? table.id !== void 0 ? { ids: {
55
+ type: "boolean",
56
+ description: "Print only the ids, comma-separated, on one line",
57
+ default: false
58
+ } } : {},
59
+ ...options.limit ?? true ? { limit: {
60
+ type: "string",
61
+ description: "Show at most this many (a positive whole number)"
62
+ } } : {},
63
+ ...options.json ?? true ? { json: {
64
+ type: "boolean",
65
+ description: "Print JSON",
66
+ default: false
67
+ } } : {},
68
+ ...options.extra
69
+ });
70
+ /** Says why, and makes the command exit 1. */
71
+ const fail = (message) => {
72
+ writeErr(message);
73
+ process.exitCode = 1;
74
+ };
75
+ /** The `--ids` with `--json` refusal; `true` when `args` has both. */
76
+ const refuseIdsWithJson = (args) => {
77
+ if (args.ids !== true || args.json !== true) return false;
78
+ fail("--ids and --json can't be used together.");
79
+ return true;
80
+ };
81
+ /**
82
+ * `args` (a command's parsed flags) read against `table`: the filter, and the
83
+ * limit. `all` names the flag `--all` stands for (`project` or `status`):
84
+ * `--all` fills it when it is not given, with one warning. `undefined` when
85
+ * something was refused (already said, exit code set).
86
+ */
87
+ const readListArgs = (table, args, options = {}) => {
88
+ if (refuseIdsWithJson(args)) return void 0;
89
+ const given = options.all === void 0 ? args : warnAll(args, options.all);
90
+ const parsed = parseFilters(table, given, 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(`--limit takes a positive whole number, not \`${text}\`.`);
103
+ return;
104
+ }
105
+ return {
106
+ filter: parsed.filter,
107
+ limit
108
+ };
109
+ };
110
+ /**
111
+ * The scope the filter's `project` names, checked against the registered
112
+ * projects (core does not know them): an id or a root, or `all`; none given is
113
+ * `here`. A project not registered is refused, listing the registered ones
114
+ * (said, exit code set, `undefined`).
115
+ */
116
+ const projectScope = (filter, projects) => {
117
+ const key = filter["project"];
118
+ if (key === void 0) return "here";
119
+ if (key === "all") return "all";
120
+ const found = projects.find((p) => p.id === key || p.root === key);
121
+ if (found !== void 0) return { root: found.root };
122
+ const names = projects.map((p) => p.id);
123
+ fail(`No registered project \`${key}\`; registered: ${names.length === 0 ? "none" : names.join(", ")}.`);
124
+ };
125
+ export { refuseIdsWithJson as a, readListArgs as i, filterArgs as n, allAlias as o, projectScope as r, fail as t };
@@ -1,10 +1,13 @@
1
- import { Dr as placeFor, Yn as runGh, cn as recordPublished, dn as deliverFollowUp, fn as followUpProblem, sn as markDue } from "./src-Dr_fTSu-.mjs";
2
- import { d as followUpsRule, f as refreshDue, n as flagValues } from "./run--4-1xAF2.mjs";
3
- import { g as changeFollowUp, v as findFollowUp, y as followUpActions } from "./__build_info__-Bsb1kjgF.mjs";
4
- import { t as postFollowUpMessage } from "./post-BzA6Qobq.mjs";
5
- import { t as findRun } from "./find-BYLlszqj.mjs";
1
+ import { Fr as placeFor, Ht as followUpListTable, hn as recordPublished, mn as markDue, nr as runGh, vn as deliverFollowUp, yn as followUpProblem } from "./src-DLsFsmOD.mjs";
2
+ import { d as followUpsRule, f as refreshDue, n as flagValues } from "./run-OOWQIKMJ.mjs";
3
+ import { g as changeFollowUp, v as findFollowUp, x as isGithubIssueUrl, y as followUpActions } from "./__build_info__-C1Nm0hlJ.mjs";
4
+ import { t as postFollowUpMessage } from "./post-2CaJz_mV.mjs";
5
+ import { t as applyFilters } from "./apply-Mb0fNpCX.mjs";
6
+ import { t as findRun } from "./find-k489laBa.mjs";
6
7
  import { a as plain, c as write, l as writeErr, s as style } from "./output-B1kwo0YU.mjs";
7
- import { t as withStore } from "./store-oKJ-cCf3.mjs";
8
+ import { i as readListArgs, n as filterArgs, o as allAlias } from "./flags-B4ZZWspN.mjs";
9
+ import { t as printList } from "./print-n2qetUHc.mjs";
10
+ import { t as withStore } from "./store-DACH768X.mjs";
8
11
  import { homedir } from "node:os";
9
12
  import { defineCommand } from "citty";
10
13
  // src/follow-ups/command.ts
@@ -53,6 +56,20 @@ const inProject = (key, body) => withStore(async (store) => {
53
56
  });
54
57
  await body(store, place);
55
58
  });
59
+ /**
60
+ * Like `inProject`, for a list: `key` may be `all`, which gives every
61
+ * registered project's place. The due check runs for each.
62
+ */
63
+ const inProjects = (key, body) => withStore(async (store) => {
64
+ const projects = store.projects();
65
+ const places = key === "all" ? projects.map((p) => placeFor(p.root, projects)) : [placeOf(store, key)];
66
+ if (places.includes(void 0)) return;
67
+ const found = places.filter((place) => place !== void 0);
68
+ await Promise.all(found.map((place) => refreshDue(store, place.root, (line) => {
69
+ writeErr(style("dim", line));
70
+ })));
71
+ await body(store, found);
72
+ });
56
73
  /** Runs `body` on the follow-up `id` names, or says why there is none. */
57
74
  const withFollowUp = (args, body) => inProject(args.project, async (store, place) => {
58
75
  const found = findFollowUp(store, place.root, args.id);
@@ -90,6 +107,7 @@ const FOR_ORDER = [
90
107
  "person:"
91
108
  ];
92
109
  const forOrder = (value) => FOR_ORDER.findIndex((prefix) => value.startsWith(prefix));
110
+ /** The list's order within a status: by who it is for. */
93
111
  const byFor = (a, b) => forOrder(a.for) - forOrder(b.for) || a.for.localeCompare(b.for);
94
112
  /** A multi-line text, each line safe to print. */
95
113
  const linesOf = (text) => text.split("\n").map((line) => plain(line));
@@ -213,7 +231,7 @@ const post = defineCommand({
213
231
  if (!args.confirm && draft !== void 0) {
214
232
  write(`Would post to ${plain(draft.to)} through your gh:`);
215
233
  for (const line of draft.text.split("\n")) write(` ${plain(line)}`);
216
- fail("Run again with --confirm to post it.");
234
+ writeErr("Run again with --confirm to post it.");
217
235
  return;
218
236
  }
219
237
  const posted = await postFollowUpMessage(followUp, {
@@ -325,7 +343,7 @@ const add = defineCommand({
325
343
  },
326
344
  to: {
327
345
  type: "string",
328
- description: "A message: who it is to"
346
+ description: "A message: who it is to (default: the person); for --channel github, the issue or PR URL"
329
347
  },
330
348
  channel: {
331
349
  type: "string",
@@ -354,6 +372,14 @@ const add = defineCommand({
354
372
  fail(isFor(recipient) ? "--due is now, after-merge, after-deploy or a YYYY-MM-DD date." : "--for is agent, me, project:<name> or person:<name>.");
355
373
  return;
356
374
  }
375
+ if (kind === "message" && args.text === void 0) {
376
+ fail("a message needs --text.");
377
+ return;
378
+ }
379
+ if (args.channel === "github" && !isGithubIssueUrl(args.to ?? "")) {
380
+ fail("--channel github needs --to <the issue or pull request URL>, https://github.com/<owner>/<repo>/(issues|pull)/<n>.");
381
+ return;
382
+ }
357
383
  const steps = flagValues(rawArgs, "--step").map((raw) => stepOf(raw));
358
384
  const input = {
359
385
  title: args.title,
@@ -404,6 +430,15 @@ const add = defineCommand({
404
430
  * `follow-ups-act.ts`). Every call first moves what has come due, reading
405
431
  * pull requests through the person's own `gh`; a `gh` that fails only warns.
406
432
  */
433
+ /** What a list with no `--status` shows. */
434
+ const OPEN = /* @__PURE__ */ new Set(["due", "waiting"]);
435
+ /** The list's own order: due first, then waiting, done, dropped; then `byFor`. */
436
+ const RANK = {
437
+ due: 0,
438
+ waiting: 1,
439
+ done: 2,
440
+ dropped: 3
441
+ };
407
442
  const followUps = defineCommand({
408
443
  meta: {
409
444
  name: "follow-ups",
@@ -416,23 +451,23 @@ const followUps = defineCommand({
416
451
  description: "List the follow-ups, due first, then by who they are for"
417
452
  },
418
453
  args: {
419
- all: {
420
- type: "boolean",
421
- description: "Done and dropped ones too",
422
- default: false
423
- },
424
- for: {
425
- type: "string",
426
- description: "Only those for agent, me, project:<name> or person:<name>"
427
- },
428
- project,
429
- json
454
+ ...filterArgs(followUpListTable),
455
+ ...allAlias
430
456
  },
431
- run: ({ args }) => inProject(args.project, (store, place) => {
432
- const shown = store.followUps(place.root).filter((f) => (args.all || f.status === "due" || f.status === "waiting") && (args.for === void 0 || f.for === args.for));
433
- if (args.json) write(JSON.stringify(shown, void 0, 2));
434
- else for (const line of formatFollowUps(shown, args.all)) write(line);
435
- })
457
+ run: async ({ args }) => {
458
+ const given = args;
459
+ const asked = readListArgs(followUpListTable, given, { all: "status" });
460
+ if (asked === void 0) return;
461
+ const { filter, limit } = asked;
462
+ const status = filter["status"];
463
+ await inProjects(filter["project"], (store, places) => {
464
+ const stored = places.flatMap((place) => store.followUps(place.root));
465
+ const shown = applyFilters(followUpListTable, stored, filter).filter((f) => status !== void 0 || OPEN.has(f.status)).toSorted((a, b) => RANK[a.status] - RANK[b.status] || byFor(a, b)).slice(0, limit);
466
+ printList(shown, followUpListTable, given, (rows) => {
467
+ for (const line of formatFollowUps(rows, status !== void 0)) write(line);
468
+ });
469
+ });
470
+ }
436
471
  }),
437
472
  show: defineCommand({
438
473
  meta: {
@@ -1,4 +1,4 @@
1
- import { Cn as liveStatus, Dn as slotWaitsOf, En as runStatus, Lt as lockWaitLine, jn as isItem, wn as lockWaitsOf } from "./src-Dr_fTSu-.mjs";
1
+ import { An as liveStatus, Nn as runStatus, Pn as slotWaitsOf, Rn as isItem, jn as lockWaitsOf, kt as lockWaitLine } from "./src-DLsFsmOD.mjs";
2
2
  import { C as termOf, E as widthOf, n as cost, o as shortCount, r as costOf, s as style, w as cut } from "./output-B1kwo0YU.mjs";
3
3
  import { i as statusChip, n as chip } from "./chips-BqcT58qr.mjs";
4
4
  import { n as nodeVerdict } from "./verdict-CMvPCpWC.mjs";
@@ -180,6 +180,18 @@ const stateText = (node) => {
180
180
  }
181
181
  return node.state;
182
182
  };
183
+ /** A node's counted tokens: input, output and cache write. */
184
+ const countedOf = (node) => node.inputTokens + node.outputTokens + (node.cacheWriteTokens ?? 0);
185
+ /**
186
+ * What a node spent: its counted tokens, then dollars unless its driver reports
187
+ * none, where `$0.00` would be false. Blank with no tokens.
188
+ */
189
+ const worth = (node) => {
190
+ const counted = countedOf(node);
191
+ if (counted === 0) return "";
192
+ const tok = `${count(counted)} tok`;
193
+ return style("dim", ` ${node.unpriced === true ? tok : `${tok} · ${cost(node.costUsd)}`}`);
194
+ };
183
195
  /**
184
196
  * The ownership tree, one line per node, children under their owner. A node in
185
197
  * `waits` (a live run's `lockWaitsOf`) reads `waiting for <lock>`, an item
@@ -188,8 +200,9 @@ const stateText = (node) => {
188
200
  const formatTree = (roots, now = Date.now(), waits = []) => {
189
201
  const lines = [];
190
202
  const walk = (node, lead, branch) => {
203
+ const named = node.description === void 0 ? "" : ` ${node.description}`;
191
204
  lines.push({
192
- label: `${lead}${branch}${node.id}`,
205
+ label: `${lead}${branch}${node.id}${named}`,
193
206
  node
194
207
  });
195
208
  const next = lead + (branch === "├─ " ? "│ " : branch === "" ? "" : " ");
@@ -201,11 +214,11 @@ const formatTree = (roots, now = Date.now(), waits = []) => {
201
214
  const label = Math.max(...lines.map((l) => l.label.length));
202
215
  const type = Math.max(...lines.map((l) => l.node.type.length));
203
216
  return lines.map(({ label: text, node }) => {
204
- const usage = node.inputTokens + node.outputTokens === 0 ? "" : style("dim", ` ${count(node.inputTokens)} in / ${count(node.outputTokens)} out · ${cost(node.costUsd)}`);
217
+ const usage = worth(node);
205
218
  const lock = waits.find((wait) => wait.id === node.id)?.lock;
206
219
  const state = node.held === true ? `${style("yellow", "‖")} paused` : lock === void 0 ? `${STATE_MARK[node.state]()} ${stateText(node)}` : `${STATE_MARK.waiting()} waiting for ${lock}`;
207
220
  const spent = isItem(node) && node.startedAt !== void 0 ? style("dim", ` ${duration((node.finishedAt === void 0 ? now : Date.parse(node.finishedAt)) - Date.parse(node.startedAt))}`) : "";
208
- return `${text.padEnd(label)} ${style("dim", node.type.padEnd(type))} ${state}${spent}${usage}`;
221
+ return `${node.description === void 0 ? text : `${text.slice(0, text.length - node.description.length)}${dim(node.description)}`}${" ".repeat(label - text.length)} ${style("dim", node.type.padEnd(type))} ${state}${spent}${usage}`;
209
222
  });
210
223
  };
211
224
  /**