@krapht/ork 1.0.0-alpha.2 → 1.0.0-alpha.20

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 (71) hide show
  1. package/README.md +12 -7
  2. package/dist/__build_info__-D6mXC4cD.mjs +228 -0
  3. package/dist/apply-Mb0fNpCX.mjs +17 -0
  4. package/dist/argument-error-CihH46Le.mjs +27 -0
  5. package/dist/attach-C1OnxyMh.mjs +92 -0
  6. package/dist/attach-CEGbRg1D.mjs +1230 -0
  7. package/dist/bin.mjs +1312 -2390
  8. package/dist/brief-DH66xmj8.mjs +115 -0
  9. package/dist/{decide-CGyx9rv7.mjs → builtin-files-Cf-NZDqM.mjs} +3 -51
  10. package/dist/chips-BqcT58qr.mjs +43 -0
  11. package/dist/command-tree-CEkzWNy6.mjs +63 -0
  12. package/dist/{completion-CSU5vkEt.mjs → completion-DS0gCZRm.mjs} +200 -30
  13. package/dist/context-CGPh12HV.mjs +106 -0
  14. package/dist/detached-Dakw5RZj.mjs +38 -0
  15. package/dist/extra-arguments-BxQMXARO.mjs +13 -0
  16. package/dist/{find-D6Gj1sxg.mjs → find-BTikxc54.mjs} +2 -2
  17. package/dist/flags-C8yCq0j5.mjs +145 -0
  18. package/dist/follow-ups-CtlhHbwe.mjs +563 -0
  19. package/dist/format-BB4E-T6x.mjs +249 -0
  20. package/dist/{help-groups-BwYoAWsh.mjs → help-groups-BpRgBSTh.mjs} +42 -76
  21. package/dist/in-a-run-CbnKjO7t.mjs +27 -0
  22. package/dist/install-CWcpX5g4.mjs +211 -0
  23. package/dist/{limits-B1-DYc3d.mjs → limits-VQqsBoQk.mjs} +2 -2
  24. package/dist/{mcp-DE_EmyVm.mjs → mcp-wGfkMCVF.mjs} +6 -6
  25. package/dist/once-BHxSdZz_.mjs +112 -0
  26. package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
  27. package/dist/output-B1kwo0YU.mjs +439 -0
  28. package/dist/outputs-text-CNTJ7Qq_.mjs +19 -0
  29. package/dist/{pause-CL6N_Xrj.mjs → pause-C_Wxh-KM.mjs} +7 -4
  30. package/dist/print-DNYzq4Rr.mjs +19 -0
  31. package/dist/project-records-XY3ku95x.mjs +233 -0
  32. package/dist/refusals-DQQX3AIb.mjs +116 -0
  33. package/dist/{relay-0SD0erwf.mjs → relay-BA6tsnpc.mjs} +2 -2
  34. package/dist/run-Bvvg-fiF.mjs +234 -0
  35. package/dist/run-DBUAq-Uu.mjs +1452 -0
  36. package/dist/send-nrKtak5F.mjs +105 -0
  37. package/dist/{src-Dujmvv4s.mjs → src-8cOPOgrT.mjs} +12523 -5507
  38. package/dist/{src-BbyEoMn4.mjs → src-CmEvHdju.mjs} +797 -233
  39. package/dist/src-D4JSymhK.mjs +3104 -0
  40. package/dist/{src-DOcK8Jfp.mjs → src-I_RyKu6_.mjs} +1022 -590
  41. package/dist/store-CDpk8Wtz.mjs +16 -0
  42. package/dist/ui/THIRD_PARTY_LICENSES.txt +612 -0
  43. package/dist/ui/assets/app--FijEXiJ.css +1 -0
  44. package/dist/ui/assets/app-BowhjEAe.js +112 -0
  45. package/dist/ui/assets/{index-9dFha9Qn.js → index-CrMa2oHB.js} +3 -3
  46. package/dist/ui/assets/theme-CdXvqTQ6.js +1 -0
  47. package/dist/ui/index.html +2 -2
  48. package/dist/{ui-CTmO2Xzu.mjs → ui-DY3Dnpho.mjs} +54 -25
  49. package/dist/usage-1VwSg_Qt.mjs +257 -0
  50. package/dist/validate-BxayjjkV.mjs +632 -0
  51. package/dist/verdict-CMvPCpWC.mjs +84 -0
  52. package/dist/{watch-CtbgDAcz.mjs → watch-CT0EtLPZ.mjs} +45 -17
  53. package/dist/{workflows-D1_WVukk.mjs → workflows-DHHi-APg.mjs} +30 -5
  54. package/dist/worktrees-CZvbHzbN.mjs +302 -0
  55. package/package.json +10 -8
  56. package/dist/__build_info__-fy6L1Mrd.mjs +0 -615
  57. package/dist/clean-DaHjs__2.mjs +0 -88
  58. package/dist/context-DT8MfEIR.mjs +0 -274
  59. package/dist/format-Dnfv8zMT.mjs +0 -427
  60. package/dist/in-a-run-pLAZeNys.mjs +0 -22
  61. package/dist/list-BrH8KRSJ.mjs +0 -110
  62. package/dist/output-BLQWD6MJ.mjs +0 -950
  63. package/dist/remove-BeGdqroy.mjs +0 -194
  64. package/dist/report-DK4LGqwF.mjs +0 -129
  65. package/dist/send-C8tunWhv.mjs +0 -163
  66. package/dist/src-CCUKRX51.mjs +0 -1204
  67. package/dist/ui/assets/app-BNT0SuIz.css +0 -1
  68. package/dist/ui/assets/app-T7wVAhgx.js +0 -67
  69. package/dist/ui/assets/theme-3xH5jxKs.js +0 -1
  70. package/dist/usage-DYUmP0i4.mjs +0 -419
  71. package/dist/worktrees-DdMYoNOC.mjs +0 -212
@@ -1,194 +0,0 @@
1
- import { ht as liveStatus, j as lines, k as git } from "./src-Dujmvv4s.mjs";
2
- import { n as listOrkWorktrees, t as createdWorktrees } from "./list-BrH8KRSJ.mjs";
3
- import { i as keyOf, n as applyStep, r as cleanStep, s as stepPrunes } from "./clean-DaHjs__2.mjs";
4
- // ../core/src/worktree/words.ts
5
- /**
6
- * @file The plain words for what `ork worktrees` shows, in one place, so the
7
- * CLI and the Worktrees panel say the same thing: an item's name, its merge
8
- * state, its state as parts (each marked when it's work that removing would
9
- * lose), a plan's line, and why an item can't be removed. The CLI colors
10
- * them; the panel lays them out.
11
- */
12
- /** An item's name: its branch, or its run and node when created detached. */
13
- const labelOf = (item) => item.branch ?? `${item.runId}/${item.nodeId}`;
14
- /** Merged into the current branch, not, or detached on no branch at all. */
15
- const mergeStateOf = (item) => {
16
- if (item.branch === void 0 && item.checkedOut === void 0) return "detached";
17
- return item.merged ? "merged" : "unmerged";
18
- };
19
- /** `3 commits`, `1 commit`. */
20
- const counted = (count, noun) => `${String(count)} ${noun}${count === 1 ? "" : "s"}`;
21
- /**
22
- * An item's state, in order: where its worktree is when it isn't plain, the
23
- * workflow's own branch, uncommitted changes, unmerged or unreachable commits,
24
- * and the main checkout or another worktree holding the branch. `into` names
25
- * the branch "not merged" is measured against, when the words should say it.
26
- */
27
- const stateParts = (item, into) => {
28
- const parts = [];
29
- const part = (text, risk = false) => {
30
- parts.push({
31
- text,
32
- risk
33
- });
34
- };
35
- if (item.path === null) part("no worktree");
36
- else if (item.missing) part("missing (prunable)");
37
- if (item.checkedOut !== void 0) part(`on the workflow's branch ${item.checkedOut}`);
38
- if (item.dirty) part("uncommitted changes", true);
39
- const state = mergeStateOf(item);
40
- if (state === "unmerged") {
41
- const where = into === void 0 ? "" : ` into ${into}`;
42
- part(`${counted(item.ahead, "commit")} not merged${where}`);
43
- }
44
- if (state === "detached") {
45
- if (item.ahead > 0) part(`${counted(item.ahead, "commit")} on no branch`, true);
46
- else if (item.path !== null && !item.missing) part("nothing committed");
47
- }
48
- if (item.inMainCheckout) part("checked out in the main checkout");
49
- if (item.heldBy !== void 0) part(`checked out in ${item.heldBy}`);
50
- return parts;
51
- };
52
- /** What a step does, as a plan says it. */
53
- const planWhat = (step) => {
54
- const actions = [
55
- ...step.removeWorktree ? ["remove worktree"] : [],
56
- ...stepPrunes(step) && !step.removeWorktree ? ["prune worktree"] : [],
57
- ...step.deleteBranch ? ["delete branch"] : []
58
- ];
59
- return actions.length > 0 ? actions.join(" + ") : "keep";
60
- };
61
- /** `ork/r1/lead: remove worktree`, `r1/check (detached): keep (…)`. */
62
- const planLine = (step) => {
63
- const { item } = step;
64
- return {
65
- text: `${item.branch === void 0 && item.checkedOut === void 0 ? `${labelOf(item)} (detached)` : labelOf(item)}: ${planWhat(step)}`,
66
- ...step.skip === void 0 ? {} : { reason: step.skip }
67
- };
68
- };
69
- /**
70
- * Why an item can't be removed at all, even forced, if it can't: the main
71
- * checkout or a worktree Ork didn't create holds it, or its run is still
72
- * going.
73
- */
74
- const removeRefusal = (item, live) => {
75
- if (item.inMainCheckout) return "checked out in the main checkout; switch it to another branch first";
76
- if (item.heldBy !== void 0) return `checked out in ${item.heldBy}, a worktree Ork didn't create; switch it to another branch first`;
77
- return live ? `run ${item.runId} is still going; stop it first` : void 0;
78
- };
79
- // ../core/src/worktree/remove.ts
80
- /** The ids of the project's runs whose process is still going. */
81
- const liveRunIds = (store, root) => new Set(store.runs({ project: root }).filter((run) => liveStatus(run) === "running").map((run) => run.id));
82
- /**
83
- * Whether `root` is a repository with a commit, and the branch checked out
84
- * there, which "merged" means merged into (null when its HEAD is detached).
85
- */
86
- const projectHead = async (root) => {
87
- if ((await git(root, [
88
- "rev-parse",
89
- "--verify",
90
- "--quiet",
91
- "HEAD"
92
- ])).code !== 0) return { ok: false };
93
- const answer = await git(root, [
94
- "symbolic-ref",
95
- "--short",
96
- "-q",
97
- "HEAD"
98
- ]);
99
- return {
100
- ok: true,
101
- branch: answer.code === 0 ? answer.stdout.trim() : null
102
- };
103
- };
104
- /** What Ork created in the repo at `root`, from its runs' records and git. */
105
- const readWorktrees = async (store, root) => ({
106
- items: await listOrkWorktrees(root, createdWorktrees(store, root)),
107
- live: liveRunIds(store, root)
108
- });
109
- /**
110
- * The plan to remove one item, named by `keyOf`. A branch with no worktree is
111
- * removed by deleting it, so `deleteBranch` is implied there.
112
- */
113
- const planRemove = ({ items, live }, key, deleteBranch) => {
114
- const item = items.find((i) => keyOf(i) === key);
115
- if (item === void 0) return {
116
- ok: false,
117
- error: `No Ork worktree or branch ${key}.`,
118
- gone: true
119
- };
120
- const refusal = removeRefusal(item, live.has(item.runId));
121
- if (refusal !== void 0) return {
122
- ok: false,
123
- error: `Can't remove ${labelOf(item)}: ${refusal}.`,
124
- gone: false
125
- };
126
- const branches = item.branch !== void 0 && (deleteBranch || item.path === null);
127
- const careful = cleanStep(item, {
128
- branches,
129
- force: false,
130
- live
131
- });
132
- if (careful.skip === void 0) return {
133
- ok: true,
134
- plan: {
135
- step: careful,
136
- force: false
137
- }
138
- };
139
- return {
140
- ok: true,
141
- plan: {
142
- step: cleanStep(item, {
143
- branches,
144
- force: true,
145
- live
146
- }),
147
- force: true
148
- }
149
- };
150
- };
151
- const out = async (cwd, args) => lines((await git(cwd, args)).stdout);
152
- /** What carrying out `plan` would lose, read from git. */
153
- const lostWork = async (root, { step }) => {
154
- const { item } = step;
155
- const inside = item.path !== null && !item.missing ? item.path : null;
156
- const changes = inside !== null && item.dirty && step.removeWorktree ? await out(inside, ["status", "--porcelain"]) : [];
157
- if (step.deleteBranch && item.branch !== void 0 && !item.merged) return {
158
- changes,
159
- commits: await out(root, [
160
- "log",
161
- "--oneline",
162
- `HEAD..${item.branch}`
163
- ]),
164
- commitsOn: "unmerged"
165
- };
166
- if (item.branch === void 0 && item.checkedOut === void 0 && inside !== null && item.ahead > 0 && step.removeWorktree) return {
167
- changes,
168
- commits: await out(inside, [
169
- "log",
170
- "--oneline",
171
- "HEAD",
172
- "--not",
173
- "--branches",
174
- "--tags",
175
- "--remotes"
176
- ]),
177
- commitsOn: "no branch"
178
- };
179
- return {
180
- changes,
181
- commits: [],
182
- commitsOn: null
183
- };
184
- };
185
- /**
186
- * Carries out a remove plan; what it did goes into `done` as it goes. The
187
- * caller has checked that a plan needing `force` was asked to force. It touches
188
- * only its own item, as each of clean's steps does.
189
- */
190
- const applyRemove = async (root, { step, force }, done = []) => {
191
- await applyStep(root, step, force, done);
192
- return done;
193
- };
194
- export { readWorktrees as a, planLine as c, projectHead as i, removeRefusal as l, lostWork as n, labelOf as o, planRemove as r, mergeStateOf as s, applyRemove as t, stateParts as u };
@@ -1,129 +0,0 @@
1
- // ../core/src/usage/report.ts
2
- const USAGE_DIMENSIONS = [
3
- "day",
4
- "week",
5
- "month",
6
- "workflow",
7
- "node",
8
- "model",
9
- "driver",
10
- "project"
11
- ];
12
- const pad = (value) => String(value).padStart(2, "0");
13
- /** `2026-11-03`, in the machine's own time zone. */
14
- const dayOf = (date) => `${String(date.getFullYear())}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
15
- /** The Monday of a time's week. */
16
- const weekOf = (date) => {
17
- const monday = new Date(date);
18
- monday.setDate(date.getDate() - (date.getDay() + 6) % 7);
19
- return dayOf(monday);
20
- };
21
- const KEYS = {
22
- day: (row) => dayOf(new Date(row.at)),
23
- week: (row) => weekOf(new Date(row.at)),
24
- month: (row) => dayOf(new Date(row.at)).slice(0, 7),
25
- workflow: (row) => row.workflow,
26
- node: (row) => `${row.workflow}/${row.template ?? row.node}`,
27
- model: (row) => row.model ?? "unknown",
28
- driver: (row) => row.driver ?? "unknown",
29
- project: (row) => row.project
30
- };
31
- const TIMES = /* @__PURE__ */ new Set([
32
- "day",
33
- "week",
34
- "month"
35
- ]);
36
- const totalsOf = (rows) => {
37
- const totals = {
38
- costUsd: 0,
39
- inputTokens: 0,
40
- outputTokens: 0,
41
- cacheReadTokens: 0,
42
- cacheWriteTokens: 0,
43
- turns: 0,
44
- durationMs: 0,
45
- sessions: rows.length,
46
- runs: new Set(rows.map((row) => row.runId)).size,
47
- failedUsd: 0,
48
- reworkUsd: 0,
49
- chatUsd: 0,
50
- cacheHitRate: 0
51
- };
52
- for (const row of rows) {
53
- totals.costUsd += row.costUsd;
54
- totals.inputTokens += row.inputTokens;
55
- totals.outputTokens += row.outputTokens;
56
- totals.cacheReadTokens += row.cacheReadTokens;
57
- totals.cacheWriteTokens += row.cacheWriteTokens;
58
- totals.turns += row.turns;
59
- totals.durationMs += row.durationMs;
60
- if (row.failed) totals.failedUsd += row.costUsd;
61
- if (row.kind === "chat") totals.chatUsd += row.costUsd;
62
- else if (row.round > 1) totals.reworkUsd += row.costUsd;
63
- }
64
- const read = totals.inputTokens + totals.cacheReadTokens + totals.cacheWriteTokens;
65
- totals.cacheHitRate = read === 0 ? 0 : totals.cacheReadTokens / read;
66
- return totals;
67
- };
68
- const groupBy = (rows, dimension) => {
69
- const buckets = /* @__PURE__ */ new Map();
70
- for (const row of rows) {
71
- const key = KEYS[dimension](row);
72
- const bucket = buckets.get(key);
73
- if (bucket === void 0) buckets.set(key, [row]);
74
- else bucket.push(row);
75
- }
76
- const groups = [...buckets].map(([key, bucket]) => Object.assign(totalsOf(bucket), { key }));
77
- return TIMES.has(dimension) ? groups.toSorted((a, b) => a.key.localeCompare(b.key)) : groups.toSorted((a, b) => b.costUsd - a.costUsd);
78
- };
79
- /** Below this, a percentage says nothing worth acting on. */
80
- const MIN_SPEND_USD = .5;
81
- const REWORK_SHARE = .2;
82
- const FAILED_SHARE = .1;
83
- const CONCENTRATED_SHARE = .5;
84
- const LOW_CACHE_RATE = .5;
85
- const MIN_CACHE_TOKENS = 1e5;
86
- const percent = (share) => `${String(Math.round(share * 100))}%`;
87
- const hintsOf = (totals, nodes) => {
88
- const spend = totals.costUsd;
89
- if (spend < MIN_SPEND_USD) return [];
90
- const hints = [];
91
- if (totals.reworkUsd / spend >= REWORK_SHARE) hints.push({
92
- code: "rework",
93
- message: `${percent(totals.reworkUsd / spend)} of the spend went to loop rounds after the first. Sharper briefs or an earlier check can stop work coming back.`
94
- });
95
- if (totals.failedUsd / spend >= FAILED_SHARE) hints.push({
96
- code: "failed",
97
- message: `${percent(totals.failedUsd / spend)} of the spend went to sessions that failed. A cheap check before them, or a lower budget per session, can stop them sooner.`
98
- });
99
- const top = nodes[0];
100
- if (top !== void 0 && nodes.length > 1 && top.costUsd / spend >= CONCENTRATED_SHARE) hints.push({
101
- code: "concentrated",
102
- message: `${top.key} took ${percent(top.costUsd / spend)} of the spend. It is the first place to try a smaller model, a lower effort or a tighter brief.`
103
- });
104
- if (totals.inputTokens + totals.cacheReadTokens + totals.cacheWriteTokens >= MIN_CACHE_TOKENS && totals.cacheHitRate < LOW_CACHE_RATE) hints.push({
105
- code: "cache",
106
- message: `Only ${percent(totals.cacheHitRate)} of input tokens came from the prompt cache. Long runs of fresh sessions cost more than resumed ones.`
107
- });
108
- return hints;
109
- };
110
- /** Everything a report shows, from the ledger's rows. */
111
- const usageReport = (rows) => {
112
- const totals = totalsOf(rows);
113
- const groups = {
114
- day: groupBy(rows, "day"),
115
- week: groupBy(rows, "week"),
116
- month: groupBy(rows, "month"),
117
- workflow: groupBy(rows, "workflow"),
118
- node: groupBy(rows, "node"),
119
- model: groupBy(rows, "model"),
120
- driver: groupBy(rows, "driver"),
121
- project: groupBy(rows, "project")
122
- };
123
- return {
124
- totals,
125
- groups,
126
- hints: hintsOf(totals, groups.node)
127
- };
128
- };
129
- export { usageReport as n, USAGE_DIMENSIONS as t };
@@ -1,163 +0,0 @@
1
- import { an as wakeRun, ht as liveStatus, kt as scriptContactsOf, rn as socketPath } from "./src-Dujmvv4s.mjs";
2
- import { c as openStore, o as writeErr } from "./output-BLQWD6MJ.mjs";
3
- import { u as inheritedScrubber } from "./src-DOcK8Jfp.mjs";
4
- import { defineCommand } from "citty";
5
- // ../core/src/remote/send.ts
6
- /**
7
- * @file `ork send` (RFC-005): a program in a tool node writes to a session on
8
- * its run's bus. The check is on the sender's side, as a refusal must be an
9
- * exit code a script can act on; then a row in the store and a wake, as for a
10
- * message between linked runs. `ORK_RUN` and `ORK_NODE` say who it is, and
11
- * they are attribution, not proof: the check stops mistakes, not a program
12
- * running as the same user.
13
- */
14
- /** A first value, as for links; real use will show whether it is the right one. */
15
- const DEFAULT_SCRIPT_MESSAGES = 100;
16
- /**
17
- * Why this sender may not write to this node; `null` when it may. The body is
18
- * not needed, so a program is told before it pipes a whole message in.
19
- */
20
- const scriptRefusal = (store, send) => {
21
- const { run: runId, node, to } = send;
22
- if (runId === void 0 || node === void 0) return "`ork send` runs inside a tool node of a run: `ORK_RUN` and `ORK_NODE` are not set.";
23
- if (runId.startsWith("node-")) return `Run ${runId} runs one node, which has no message bus; run the whole workflow.`;
24
- const run = store.lookup(runId).find((found) => found.id === runId);
25
- if (run === void 0) return `There is no run ${runId}.`;
26
- const status = liveStatus(run);
27
- if (status !== "running") return `Run ${runId} is not running (${status}).`;
28
- const started = store.events(runId).map((stored) => stored.event).findLast((event) => event.type === "run.started");
29
- if (started === void 0) return `Run ${runId} has not started yet.`;
30
- const own = started.nodes.find((found) => found.id === node);
31
- if (own === void 0 || !own.type.startsWith("tool.")) return `${node} is not a tool node of run ${runId}.`;
32
- const contacts = scriptContactsOf(node, started.nodes, started.channels ?? []);
33
- if (contacts.includes(to)) return null;
34
- return contacts.length === 0 ? `\`${node}\` has no one to write to: give it an owner, or a channel under \`messages:\`.` : `\`${to}\` is not a contact of \`${node}\`, which can write to \`${contacts.join("`, `")}\` (its owner and its channels).`;
35
- };
36
- /** Reading all of it would cost as much as sending it. */
37
- const TOO_LONG = "The message is over 64 KiB. Write it to a file and send the file's path.";
38
- /** Checks the message, writes it for the run, and wakes it. */
39
- const sendFromScript = async (store, send, home) => {
40
- const refused = scriptRefusal(store, send);
41
- if (refused !== null) return {
42
- ok: false,
43
- error: refused
44
- };
45
- if (Buffer.byteLength(send.body) > 65536) return {
46
- ok: false,
47
- error: TOO_LONG
48
- };
49
- const run = String(send.run);
50
- const node = String(send.node);
51
- if (!store.scriptCommand(run, {
52
- kind: "script",
53
- node: send.to,
54
- from: node,
55
- body: send.body
56
- }, DEFAULT_SCRIPT_MESSAGES)) return {
57
- ok: false,
58
- error: `\`${node}\` has sent ${String(DEFAULT_SCRIPT_MESSAGES)} messages in this run, which is the most it may.`
59
- };
60
- await wakeRun(socketPath(run, home));
61
- return { ok: true };
62
- };
63
- // src/commands/send.ts
64
- /**
65
- * @file `ork send` (RFC-005): a program in a tool node writes to a session on
66
- * its run's bus. It prints nothing when it works, as what a program prints is
67
- * its node's output, and says why on stderr, with exit 1, when it is refused:
68
- * before reading a message it could not send, and for anything wrong with the
69
- * command itself, which would otherwise go to stdout as usage.
70
- */
71
- const refuse = (message) => {
72
- writeErr(message);
73
- process.exitCode = 1;
74
- };
75
- /** Stdin, up to the limit: past it, the rest is not worth holding. */
76
- const readBounded = async () => {
77
- const chunks = [];
78
- let size = 0;
79
- for await (const chunk of process.stdin) {
80
- const part = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
81
- size += part.length;
82
- if (size > 65536) return null;
83
- chunks.push(part);
84
- }
85
- return Buffer.concat(chunks);
86
- };
87
- /** The message, or why it can't go. */
88
- const bodyOf = async (given) => {
89
- if (given !== void 0) return { body: given };
90
- const read = await readBounded();
91
- if (read === null) return { refused: TOO_LONG };
92
- try {
93
- return { body: new TextDecoder("utf-8", { fatal: true }).decode(read) };
94
- } catch {
95
- return { refused: "The message is not UTF-8 text." };
96
- }
97
- };
98
- const deliver = async (store, to, given) => {
99
- const sender = {
100
- run: process.env["ORK_RUN"],
101
- node: process.env["ORK_NODE"],
102
- to
103
- };
104
- const refused = scriptRefusal(store, sender);
105
- if (refused !== null) {
106
- refuse(refused);
107
- return;
108
- }
109
- const message = await bodyOf(given);
110
- if ("refused" in message) {
111
- refuse(message.refused);
112
- return;
113
- }
114
- if (message.body.replaceAll(/[\p{Cc}\s]/gu, "") === "") {
115
- refuse("The message is empty.");
116
- return;
117
- }
118
- const sent = await sendFromScript(store, {
119
- ...sender,
120
- body: inheritedScrubber(process.env)(message.body)
121
- });
122
- if (!sent.ok) refuse(sent.error);
123
- };
124
- const send = defineCommand({
125
- meta: {
126
- name: "send",
127
- description: "From a tool node's program: write to a session in its run"
128
- },
129
- args: {
130
- to: {
131
- type: "positional",
132
- description: "The node to write to: the owner, or a channel",
133
- required: false
134
- },
135
- message: {
136
- type: "string",
137
- alias: "m",
138
- description: "The message"
139
- },
140
- stdin: {
141
- type: "boolean",
142
- description: "Read the message from stdin",
143
- default: false
144
- }
145
- },
146
- run: async ({ args }) => {
147
- if (args.to === void 0 || args._.length !== 1) {
148
- refuse("Give one node to write to: ork send <to> -m <body>.");
149
- return;
150
- }
151
- if (args.message === void 0 === !args.stdin) {
152
- refuse("Give the message with -m or --stdin, one of them.");
153
- return;
154
- }
155
- const store = openStore();
156
- try {
157
- await deliver(store, args.to, args.message);
158
- } finally {
159
- store.close();
160
- }
161
- }
162
- });
163
- export { send };