@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.
- package/README.md +10 -4
- package/dist/__build_info__-DcRdK0Du.mjs +359 -0
- package/dist/apply-Mb0fNpCX.mjs +17 -0
- package/dist/{attach-CdtGX9se.mjs → attach-7lB5_jpr.mjs} +5 -4
- package/dist/{store-CleF7KeS.mjs → attach-B-FeE1dJ.mjs} +60 -25
- package/dist/bin.mjs +328 -1734
- package/dist/brief-BvMtIFio.mjs +58 -0
- package/dist/{builtin-files-Bdq23IIi.mjs → builtin-files-BRSOObAv.mjs} +1 -1
- package/dist/command-tree-BkSYWmYJ.mjs +63 -0
- package/dist/{completion--ywde-56.mjs → completion-Cgb2qPPh.mjs} +45 -6
- package/dist/context-BN9A2Nnu.mjs +106 -0
- package/dist/{find-BVX-bL4u.mjs → find-mLYsR0uC.mjs} +2 -2
- package/dist/flags-CdgIH1L6.mjs +230 -0
- package/dist/follow-ups-DxCsOby1.mjs +496 -0
- package/dist/{format-CvTE5CZQ.mjs → format-hiDvtNGM.mjs} +35 -8
- package/dist/{help-groups-CpR2UzSn.mjs → help-groups-hB6kY34d.mjs} +5 -63
- package/dist/{in-a-run-cL5N9gpH.mjs → in-a-run-a447hxq8.mjs} +1 -1
- package/dist/{__build_info__-B82-dGJY.mjs → install-23g_1ngs.mjs} +3 -227
- package/dist/{limits-Dhe--wmX.mjs → limits-C_Xz_t3C.mjs} +1 -1
- package/dist/{mcp-CsmboyXv.mjs → mcp-CWng9I9n.mjs} +6 -6
- package/dist/{once-CrwI-ZdN.mjs → once-lvwcWfVf.mjs} +3 -3
- package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
- package/dist/{pause-DssrYcda.mjs → pause-Cy11KrXK.mjs} +3 -3
- package/dist/post-C9soCx7i.mjs +46 -0
- package/dist/print-BaWiWf4v.mjs +19 -0
- package/dist/project-records-BRiyQzUo.mjs +230 -0
- package/dist/{relay-DASRgsTQ.mjs → relay-C0M1TWMB.mjs} +1 -1
- package/dist/run-B-j3Qxqc.mjs +1464 -0
- package/dist/{run-D8divGCs.mjs → run-D_u9V_Ve.mjs} +1 -1
- package/dist/{send-CXv8bx0i.mjs → send-qBxj0JbJ.mjs} +2 -2
- package/dist/{src-njdtejgZ.mjs → src-CXBmaGwS.mjs} +213 -75
- package/dist/{src-C7z_6fhn.mjs → src-DHDQ0itC.mjs} +106 -50
- package/dist/{src-tsuZRu0H.mjs → src-DN-RPsil.mjs} +5423 -4467
- package/dist/{src-8Mul0695.mjs → src-nXLIwDrt.mjs} +325 -81
- package/dist/store-Dyr1mP8K.mjs +16 -0
- package/dist/ui/assets/app-BPSCxb_l.css +1 -0
- package/dist/ui/assets/app-CnNRowFn.js +111 -0
- package/dist/ui/assets/{index-D-_uPI2v.js → index-CVzwYlz3.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/{ui-CNO1M0qT.mjs → ui-DLrSt5ny.mjs} +3 -3
- package/dist/{usage-BVXi0iFY.mjs → usage-CxmpYYO4.mjs} +61 -107
- package/dist/{context-Y_I4Mofq.mjs → validate-BVrT43GO.mjs} +35 -108
- package/dist/{watch-DVNIciHp.mjs → watch-mkLm4t9D.mjs} +19 -5
- package/dist/{workflows-B30QkwTk.mjs → workflows-Bvl2Fquu.mjs} +2 -2
- package/dist/{worktrees-DD4_TpkH.mjs → worktrees-DOOCoS2w.mjs} +49 -20
- package/package.json +8 -4
- package/dist/ui/assets/app-D_nAPnHM.css +0 -1
- 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 {
|
|
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 {
|
|
2
|
-
import { a as isBuiltinArg, i as builtinWorkflowText, r as builtinWorkflowNames } from "./builtin-files-
|
|
3
|
-
import { t as findRun } from "./find-
|
|
4
|
-
import { a as
|
|
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-
|
|
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 {
|
|
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 };
|