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

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 (44) hide show
  1. package/dist/{__build_info__-BJbE3Zvp.mjs → __build_info__-B82-dGJY.mjs} +3 -3
  2. package/dist/{attach-BmV5ckb9.mjs → attach-CdtGX9se.mjs} +5 -5
  3. package/dist/bin.mjs +274 -236
  4. package/dist/{builtin-files-w0YULJ4c.mjs → builtin-files-Bdq23IIi.mjs} +2 -2
  5. package/dist/{chips-CyeB_rhR.mjs → chips-BqcT58qr.mjs} +1 -1
  6. package/dist/{completion-CzElB8nS.mjs → completion--ywde-56.mjs} +52 -11
  7. package/dist/{context-BAwgVSX5.mjs → context-Y_I4Mofq.mjs} +18 -8
  8. package/dist/{find-p05wlbmu.mjs → find-BVX-bL4u.mjs} +1 -1
  9. package/dist/{format-Dni98cbh.mjs → format-CvTE5CZQ.mjs} +12 -12
  10. package/dist/{help-groups-DYP_zMRP.mjs → help-groups-CpR2UzSn.mjs} +20 -6
  11. package/dist/{in-a-run-BI-hLIl_.mjs → in-a-run-cL5N9gpH.mjs} +1 -1
  12. package/dist/{limits-CElmhowV.mjs → limits-Dhe--wmX.mjs} +2 -2
  13. package/dist/{mcp-CM3b65AZ.mjs → mcp-CsmboyXv.mjs} +6 -6
  14. package/dist/once-CrwI-ZdN.mjs +76 -0
  15. package/dist/{output-CnRzrbUM.mjs → output-B1kwo0YU.mjs} +17 -1
  16. package/dist/{pause-BuS83WAI.mjs → pause-DssrYcda.mjs} +4 -4
  17. package/dist/{relay-DAsJA8wF.mjs → relay-DASRgsTQ.mjs} +2 -2
  18. package/dist/run-D8divGCs.mjs +192 -0
  19. package/dist/send-CXv8bx0i.mjs +105 -0
  20. package/dist/{src-S9yK2OHU.mjs → src-8Mul0695.mjs} +8 -10
  21. package/dist/{src-BfxkJOYm.mjs → src-C7z_6fhn.mjs} +200 -72
  22. package/dist/{src-D1QedD5y.mjs → src-njdtejgZ.mjs} +140 -38
  23. package/dist/{src-CdDTQLdQ.mjs → src-tsuZRu0H.mjs} +2787 -1134
  24. package/dist/{store-B2OaE4ID.mjs → store-CleF7KeS.mjs} +33 -76
  25. package/dist/ui/THIRD_PARTY_LICENSES.txt +18 -0
  26. package/dist/ui/assets/app-D_nAPnHM.css +1 -0
  27. package/dist/ui/assets/app-DqBAtXSZ.js +111 -0
  28. package/dist/ui/assets/{index-BL7Ad0OA.js → index-D-_uPI2v.js} +2 -2
  29. package/dist/ui/index.html +1 -1
  30. package/dist/{ui-C4Ys26oO.mjs → ui-CNO1M0qT.mjs} +4 -4
  31. package/dist/{usage-C2zx-X6q.mjs → usage-BVXi0iFY.mjs} +13 -141
  32. package/dist/{verdict-CIy8tAyX.mjs → verdict-CMvPCpWC.mjs} +2 -2
  33. package/dist/{watch-DzCeqM05.mjs → watch-DVNIciHp.mjs} +10 -8
  34. package/dist/{workflows-Rr5w90A0.mjs → workflows-B30QkwTk.mjs} +3 -3
  35. package/dist/{worktrees-f6SAbum5.mjs → worktrees-DD4_TpkH.mjs} +3 -5
  36. package/package.json +2 -2
  37. package/dist/clean-D8GI_2K-.mjs +0 -91
  38. package/dist/decide-DlbyctOE.mjs +0 -50
  39. package/dist/list-zW2xKuGr.mjs +0 -117
  40. package/dist/remove-Dh7D_hVn.mjs +0 -201
  41. package/dist/report-CEN8VoUb.mjs +0 -140
  42. package/dist/send-MFEQZH31.mjs +0 -163
  43. package/dist/ui/assets/app-CAoZtku5.js +0 -75
  44. package/dist/ui/assets/app-QoHVi-mb.css +0 -1
@@ -1,201 +0,0 @@
1
- import { A as lines, O as git, xt as liveStatus } from "./src-CdDTQLdQ.mjs";
2
- import { n as listOrkWorktrees, t as createdWorktrees } from "./list-zW2xKuGr.mjs";
3
- import { i as keyOf, n as applyStep, r as cleanStep, s as stepPrunes } from "./clean-D8GI_2K-.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.recorded) part("not created by Ork");
49
- if (item.inMainCheckout) part("checked out in the main checkout");
50
- if (item.heldBy !== void 0) part(`checked out in ${item.heldBy}`);
51
- return parts;
52
- };
53
- /** What a step does, as a plan says it. */
54
- const planWhat = (step) => {
55
- const actions = [
56
- ...step.removeWorktree ? ["remove worktree"] : [],
57
- ...stepPrunes(step) && !step.removeWorktree ? ["prune worktree"] : [],
58
- ...step.deleteBranch ? ["delete branch"] : []
59
- ];
60
- return actions.length > 0 ? actions.join(" + ") : "keep";
61
- };
62
- /** `ork/r1/lead: remove worktree`, `r1/check (detached): keep (…)`. */
63
- const planLine = (step) => {
64
- const { item } = step;
65
- return {
66
- text: `${item.branch === void 0 && item.checkedOut === void 0 ? `${labelOf(item)} (detached)` : labelOf(item)}: ${planWhat(step)}`,
67
- ...step.skip === void 0 ? {} : { reason: step.skip }
68
- };
69
- };
70
- /**
71
- * Why an item can't be removed at all, even forced, if it can't: no run
72
- * recorded creating it, the main checkout or a worktree Ork didn't create holds
73
- * it, or its run is still going.
74
- */
75
- const removeRefusal = (item, live) => {
76
- if (!item.recorded) return "not created by Ork (no run recorded it); delete it with git if it's yours to delete";
77
- if (item.inMainCheckout) return "checked out in the main checkout; switch it to another branch first";
78
- if (item.heldBy !== void 0) return `checked out in ${item.heldBy}, a worktree Ork didn't create; switch it to another branch first`;
79
- return live ? `run ${item.runId} is still going; stop it first` : void 0;
80
- };
81
- // ../core/src/worktree/remove.ts
82
- /** The ids of the project's runs whose process is still going. */
83
- const liveRunIds = (store, root) => new Set(store.runs({ project: root }).filter((run) => liveStatus(run) === "running").map((run) => run.id));
84
- /**
85
- * Whether `root` is a repository with a commit, and the branch checked out
86
- * there, which "merged" means merged into (null when its HEAD is detached).
87
- */
88
- const projectHead = async (root) => {
89
- if ((await git(root, [
90
- "rev-parse",
91
- "--verify",
92
- "--quiet",
93
- "HEAD"
94
- ])).code !== 0) return { ok: false };
95
- const answer = await git(root, [
96
- "symbolic-ref",
97
- "--short",
98
- "-q",
99
- "HEAD"
100
- ]);
101
- return {
102
- ok: true,
103
- branch: answer.code === 0 ? answer.stdout.trim() : null
104
- };
105
- };
106
- /** The top of the repository `cwd` is in, even one with no commit yet. */
107
- const repoRoot = async (cwd) => {
108
- const top = await git(cwd, ["rev-parse", "--show-toplevel"]);
109
- return top.code === 0 ? top.stdout.trim() : null;
110
- };
111
- /** What Ork created in the repo at `root`, from its runs' records and git. */
112
- const readWorktrees = async (store, root) => ({
113
- items: await listOrkWorktrees(root, createdWorktrees(store, root)),
114
- live: liveRunIds(store, root)
115
- });
116
- /**
117
- * The plan to remove one item, named by `keyOf`. A branch with no worktree is
118
- * removed by deleting it, so `deleteBranch` is implied there.
119
- */
120
- const planRemove = ({ items, live }, key, deleteBranch) => {
121
- const item = items.find((i) => keyOf(i) === key);
122
- if (item === void 0) return {
123
- ok: false,
124
- error: `No Ork worktree or branch ${key}.`,
125
- gone: true
126
- };
127
- const refusal = removeRefusal(item, live.has(item.runId));
128
- if (refusal !== void 0) return {
129
- ok: false,
130
- error: `Can't remove ${labelOf(item)}: ${refusal}.`,
131
- gone: false
132
- };
133
- const branches = item.branch !== void 0 && (deleteBranch || item.path === null);
134
- const careful = cleanStep(item, {
135
- branches,
136
- force: false,
137
- live
138
- });
139
- if (careful.skip === void 0) return {
140
- ok: true,
141
- plan: {
142
- step: careful,
143
- force: false
144
- }
145
- };
146
- return {
147
- ok: true,
148
- plan: {
149
- step: cleanStep(item, {
150
- branches,
151
- force: true,
152
- live
153
- }),
154
- force: true
155
- }
156
- };
157
- };
158
- const out = async (cwd, args) => lines((await git(cwd, args)).stdout);
159
- /** What carrying out `plan` would lose, read from git. */
160
- const lostWork = async (root, { step }) => {
161
- const { item } = step;
162
- const inside = item.path !== null && !item.missing ? item.path : null;
163
- const changes = inside !== null && item.dirty && step.removeWorktree ? await out(inside, ["status", "--porcelain"]) : [];
164
- if (step.deleteBranch && item.branch !== void 0 && !item.merged) return {
165
- changes,
166
- commits: await out(root, [
167
- "log",
168
- "--oneline",
169
- `HEAD..${item.branch}`
170
- ]),
171
- commitsOn: "unmerged"
172
- };
173
- if (item.branch === void 0 && item.checkedOut === void 0 && inside !== null && item.ahead > 0 && step.removeWorktree) return {
174
- changes,
175
- commits: await out(inside, [
176
- "log",
177
- "--oneline",
178
- "HEAD",
179
- "--not",
180
- "--branches",
181
- "--tags",
182
- "--remotes"
183
- ]),
184
- commitsOn: "no branch"
185
- };
186
- return {
187
- changes,
188
- commits: [],
189
- commitsOn: null
190
- };
191
- };
192
- /**
193
- * Carries out a remove plan; what it did goes into `done` as it goes. The
194
- * caller has checked that a plan needing `force` was asked to force. It touches
195
- * only its own item, as each of clean's steps does.
196
- */
197
- const applyRemove = async (root, { step, force }, done = []) => {
198
- await applyStep(root, step, force, done);
199
- return done;
200
- };
201
- export { readWorktrees as a, mergeStateOf as c, stateParts as d, projectHead as i, planLine as l, lostWork as n, repoRoot as o, planRemove as r, labelOf as s, applyRemove as t, removeRefusal as u };
@@ -1,140 +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
- /** Dimensions a tool command has no value in. */
37
- const SESSION_ONLY = /* @__PURE__ */ new Set(["model", "driver"]);
38
- const totalsOf = (rows, cpu) => {
39
- const totals = {
40
- costUsd: 0,
41
- inputTokens: 0,
42
- outputTokens: 0,
43
- cacheReadTokens: 0,
44
- cacheWriteTokens: 0,
45
- turns: 0,
46
- durationMs: 0,
47
- sessions: rows.length,
48
- runs: new Set(rows.map((row) => row.runId)).size,
49
- failedUsd: 0,
50
- reworkUsd: 0,
51
- chatUsd: 0,
52
- cacheHitRate: 0,
53
- cpuMs: cpu.reduce((sum, row) => sum + row.userMs + row.systemMs, 0)
54
- };
55
- for (const row of rows) {
56
- totals.costUsd += row.costUsd;
57
- totals.inputTokens += row.inputTokens;
58
- totals.outputTokens += row.outputTokens;
59
- totals.cacheReadTokens += row.cacheReadTokens;
60
- totals.cacheWriteTokens += row.cacheWriteTokens;
61
- totals.turns += row.turns;
62
- totals.durationMs += row.durationMs;
63
- if (row.failed) totals.failedUsd += row.costUsd;
64
- if (row.kind === "chat") totals.chatUsd += row.costUsd;
65
- else if (row.round > 1) totals.reworkUsd += row.costUsd;
66
- }
67
- const read = totals.inputTokens + totals.cacheReadTokens + totals.cacheWriteTokens;
68
- totals.cacheHitRate = read === 0 ? 0 : totals.cacheReadTokens / read;
69
- return totals;
70
- };
71
- const groupBy = (rows, cpu, dimension) => {
72
- const buckets = /* @__PURE__ */ new Map();
73
- const bucketOf = (row) => {
74
- const key = KEYS[dimension](row);
75
- const found = buckets.get(key);
76
- if (found !== void 0) return found;
77
- const fresh = {
78
- rows: [],
79
- cpu: []
80
- };
81
- buckets.set(key, fresh);
82
- return fresh;
83
- };
84
- for (const row of rows) bucketOf(row).rows.push(row);
85
- if (!SESSION_ONLY.has(dimension)) for (const row of cpu) bucketOf(row).cpu.push(row);
86
- const groups = [...buckets].map(([key, bucket]) => Object.assign(totalsOf(bucket.rows, bucket.cpu), { key }));
87
- return TIMES.has(dimension) ? groups.toSorted((a, b) => a.key.localeCompare(b.key)) : groups.toSorted((a, b) => b.costUsd - a.costUsd);
88
- };
89
- /** Below this, a percentage says nothing worth acting on. */
90
- const MIN_SPEND_USD = .5;
91
- const REWORK_SHARE = .2;
92
- const FAILED_SHARE = .1;
93
- const CONCENTRATED_SHARE = .5;
94
- const LOW_CACHE_RATE = .5;
95
- const MIN_CACHE_TOKENS = 1e5;
96
- const percent = (share) => `${String(Math.round(share * 100))}%`;
97
- const hintsOf = (totals, nodes) => {
98
- const spend = totals.costUsd;
99
- if (spend < MIN_SPEND_USD) return [];
100
- const hints = [];
101
- if (totals.reworkUsd / spend >= REWORK_SHARE) hints.push({
102
- code: "rework",
103
- 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.`
104
- });
105
- if (totals.failedUsd / spend >= FAILED_SHARE) hints.push({
106
- code: "failed",
107
- 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.`
108
- });
109
- const top = nodes[0];
110
- if (top !== void 0 && nodes.length > 1 && top.costUsd / spend >= CONCENTRATED_SHARE) hints.push({
111
- code: "concentrated",
112
- 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.`
113
- });
114
- if (totals.inputTokens + totals.cacheReadTokens + totals.cacheWriteTokens >= MIN_CACHE_TOKENS && totals.cacheHitRate < LOW_CACHE_RATE) hints.push({
115
- code: "cache",
116
- message: `Only ${percent(totals.cacheHitRate)} of input tokens came from the prompt cache. Long runs of fresh sessions cost more than resumed ones.`
117
- });
118
- return hints;
119
- };
120
- /** Everything a report shows, from the ledger's rows and tool commands' CPU. */
121
- const usageReport = (rows, cpu = []) => {
122
- const totals = totalsOf(rows, cpu);
123
- const by = (dimension) => groupBy(rows, cpu, dimension);
124
- const groups = {
125
- day: by("day"),
126
- week: by("week"),
127
- month: by("month"),
128
- workflow: by("workflow"),
129
- node: by("node"),
130
- model: by("model"),
131
- driver: by("driver"),
132
- project: by("project")
133
- };
134
- return {
135
- totals,
136
- groups,
137
- hints: hintsOf(totals, groups.node)
138
- };
139
- };
140
- export { usageReport as n, USAGE_DIMENSIONS as t };
@@ -1,163 +0,0 @@
1
- import { C as openStore, Cn as wakeRun, jt as scriptContactsOf, xn as socketPath, xt as liveStatus } from "./src-CdDTQLdQ.mjs";
2
- import { s as writeErr } from "./output-CnRzrbUM.mjs";
3
- import { u as inheritedScrubber } from "./src-BfxkJOYm.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 };