@krapht/ork 1.0.0-alpha.15 → 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 (46) hide show
  1. package/README.md +10 -4
  2. package/dist/{__build_info__-Bsb1kjgF.mjs → __build_info__-DcRdK0Du.mjs} +25 -6
  3. package/dist/apply-Mb0fNpCX.mjs +17 -0
  4. package/dist/{attach-CPespxlQ.mjs → attach-7lB5_jpr.mjs} +5 -5
  5. package/dist/{attach-BHO3X3cf.mjs → attach-B-FeE1dJ.mjs} +46 -8
  6. package/dist/bin.mjs +238 -369
  7. package/dist/{brief-za0ozN_E.mjs → brief-BvMtIFio.mjs} +4 -4
  8. package/dist/{builtin-files-2bHf76x-.mjs → builtin-files-BRSOObAv.mjs} +1 -1
  9. package/dist/command-tree-BkSYWmYJ.mjs +63 -0
  10. package/dist/{completion-CQ2UYKuJ.mjs → completion-Cgb2qPPh.mjs} +45 -6
  11. package/dist/{context-BNqDw9z1.mjs → context-BN9A2Nnu.mjs} +3 -3
  12. package/dist/{find-BYLlszqj.mjs → find-mLYsR0uC.mjs} +2 -2
  13. package/dist/flags-CdgIH1L6.mjs +230 -0
  14. package/dist/{follow-ups-B4GJ3Vzw.mjs → follow-ups-DxCsOby1.mjs} +59 -24
  15. package/dist/{format-C8MsFj8_.mjs → format-hiDvtNGM.mjs} +17 -4
  16. package/dist/{help-groups-CQ98mLjF.mjs → help-groups-hB6kY34d.mjs} +1 -63
  17. package/dist/{in-a-run-Bp0k0J09.mjs → in-a-run-a447hxq8.mjs} +1 -1
  18. package/dist/{install-DWtXTq4y.mjs → install-23g_1ngs.mjs} +1 -1
  19. package/dist/{limits-B1qzS4cX.mjs → limits-C_Xz_t3C.mjs} +1 -1
  20. package/dist/{mcp-BOrVkyhN.mjs → mcp-CWng9I9n.mjs} +6 -6
  21. package/dist/{once-C1fL3GVo.mjs → once-lvwcWfVf.mjs} +3 -3
  22. package/dist/{pause-DOMK-R8V.mjs → pause-Cy11KrXK.mjs} +3 -3
  23. package/dist/{post-BzA6Qobq.mjs → post-C9soCx7i.mjs} +5 -3
  24. package/dist/print-BaWiWf4v.mjs +19 -0
  25. package/dist/project-records-BRiyQzUo.mjs +230 -0
  26. package/dist/{relay-OWuu4fib.mjs → relay-C0M1TWMB.mjs} +1 -1
  27. package/dist/{run--4-1xAF2.mjs → run-B-j3Qxqc.mjs} +26 -13
  28. package/dist/{run-CiPsW9Kf.mjs → run-D_u9V_Ve.mjs} +1 -1
  29. package/dist/{send-CnNNW-ot.mjs → send-qBxj0JbJ.mjs} +2 -2
  30. package/dist/{src-B9zWIVqo.mjs → src-CXBmaGwS.mjs} +93 -72
  31. package/dist/{src-DcrhgipG.mjs → src-DHDQ0itC.mjs} +91 -42
  32. package/dist/{src-Dr_fTSu-.mjs → src-DN-RPsil.mjs} +781 -502
  33. package/dist/{src-DQKz2eOH.mjs → src-nXLIwDrt.mjs} +9 -12
  34. package/dist/{store-oKJ-cCf3.mjs → store-Dyr1mP8K.mjs} +1 -1
  35. package/dist/ui/assets/app-BPSCxb_l.css +1 -0
  36. package/dist/ui/assets/{app-Drp8Dm9y.js → app-CnNRowFn.js} +40 -40
  37. package/dist/ui/assets/{index-BtwkftKe.js → index-CVzwYlz3.js} +2 -2
  38. package/dist/ui/index.html +1 -1
  39. package/dist/{ui-CzKlsCnm.mjs → ui-DLrSt5ny.mjs} +3 -3
  40. package/dist/{usage-DHGNk2JL.mjs → usage-CxmpYYO4.mjs} +61 -107
  41. package/dist/{validate-Bs_gg22g.mjs → validate-BVrT43GO.mjs} +35 -8
  42. package/dist/{watch-CCCfw3n7.mjs → watch-mkLm4t9D.mjs} +5 -5
  43. package/dist/{workflows-CW28ZU-z.mjs → workflows-Bvl2Fquu.mjs} +2 -2
  44. package/dist/{worktrees-CyE8aF6j.mjs → worktrees-DOOCoS2w.mjs} +49 -20
  45. package/package.json +8 -4
  46. package/dist/ui/assets/app-Bdc6OBeZ.css +0 -1
@@ -1,8 +1,8 @@
1
- import { un as takeFollowUps } from "./src-Dr_fTSu-.mjs";
2
- import { l as briefOf, n as flagValues, u as checkTakeable } from "./run--4-1xAF2.mjs";
3
- import { n as newRunId } from "./find-BYLlszqj.mjs";
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
4
  import { l as writeErr } from "./output-B1kwo0YU.mjs";
5
- import { t as loadWorkflow } from "./validate-Bs_gg22g.mjs";
5
+ import { t as loadWorkflow } from "./validate-BVrT43GO.mjs";
6
6
  // src/follow-ups/brief.ts
7
7
  /**
8
8
  * @file `ork run --follow-ups <ids>`: the `agent` follow-ups become the run's
@@ -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 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 { 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 { 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-CW28ZU-z.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
@@ -1,7 +1,7 @@
1
- import { Pr as LESSON_CAP_TOKENS, wt as explainSession } from "./src-Dr_fTSu-.mjs";
1
+ import { Vr as LESSON_CAP_TOKENS, gt as explainSession } from "./src-DN-RPsil.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-Bvl2Fquu.mjs";
4
+ import { a as formatReport, t as loadWorkflow } from "./validate-BVrT43GO.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 { 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 };
@@ -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 { Pr as placeFor, Vt as followUpListTable, _n as deliverFollowUp, mn as recordPublished, pn as markDue, tr as runGh, vn as followUpProblem } from "./src-DN-RPsil.mjs";
2
+ import { d as followUpsRule, f as refreshDue, n as flagValues } from "./run-B-j3Qxqc.mjs";
3
+ import { g as changeFollowUp, v as findFollowUp, x as isGithubIssueUrl, y as followUpActions } from "./__build_info__-DcRdK0Du.mjs";
4
+ import { t as postFollowUpMessage } from "./post-C9soCx7i.mjs";
5
+ import { t as applyFilters } from "./apply-Mb0fNpCX.mjs";
6
+ import { i as readListArgs, n as filterArgs, o as allAlias } from "./flags-CdgIH1L6.mjs";
7
+ import { t as findRun } from "./find-mLYsR0uC.mjs";
6
8
  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";
9
+ import { t as printList } from "./print-BaWiWf4v.mjs";
10
+ import { t as withStore } from "./store-Dyr1mP8K.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 lockWaitsOf, Ln as isItem, Mn as runStatus, Nn as slotWaitsOf, kn as liveStatus, kt as lockWaitLine } from "./src-DN-RPsil.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
  /**