@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.
- package/README.md +12 -7
- package/dist/__build_info__-D6mXC4cD.mjs +228 -0
- package/dist/apply-Mb0fNpCX.mjs +17 -0
- package/dist/argument-error-CihH46Le.mjs +27 -0
- package/dist/attach-C1OnxyMh.mjs +92 -0
- package/dist/attach-CEGbRg1D.mjs +1230 -0
- package/dist/bin.mjs +1312 -2390
- package/dist/brief-DH66xmj8.mjs +115 -0
- package/dist/{decide-CGyx9rv7.mjs → builtin-files-Cf-NZDqM.mjs} +3 -51
- package/dist/chips-BqcT58qr.mjs +43 -0
- package/dist/command-tree-CEkzWNy6.mjs +63 -0
- package/dist/{completion-CSU5vkEt.mjs → completion-DS0gCZRm.mjs} +200 -30
- package/dist/context-CGPh12HV.mjs +106 -0
- package/dist/detached-Dakw5RZj.mjs +38 -0
- package/dist/extra-arguments-BxQMXARO.mjs +13 -0
- package/dist/{find-D6Gj1sxg.mjs → find-BTikxc54.mjs} +2 -2
- package/dist/flags-C8yCq0j5.mjs +145 -0
- package/dist/follow-ups-CtlhHbwe.mjs +563 -0
- package/dist/format-BB4E-T6x.mjs +249 -0
- package/dist/{help-groups-BwYoAWsh.mjs → help-groups-BpRgBSTh.mjs} +42 -76
- package/dist/in-a-run-CbnKjO7t.mjs +27 -0
- package/dist/install-CWcpX5g4.mjs +211 -0
- package/dist/{limits-B1-DYc3d.mjs → limits-VQqsBoQk.mjs} +2 -2
- package/dist/{mcp-DE_EmyVm.mjs → mcp-wGfkMCVF.mjs} +6 -6
- package/dist/once-BHxSdZz_.mjs +112 -0
- package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
- package/dist/output-B1kwo0YU.mjs +439 -0
- package/dist/outputs-text-CNTJ7Qq_.mjs +19 -0
- package/dist/{pause-CL6N_Xrj.mjs → pause-C_Wxh-KM.mjs} +7 -4
- package/dist/print-DNYzq4Rr.mjs +19 -0
- package/dist/project-records-XY3ku95x.mjs +233 -0
- package/dist/refusals-DQQX3AIb.mjs +116 -0
- package/dist/{relay-0SD0erwf.mjs → relay-BA6tsnpc.mjs} +2 -2
- package/dist/run-Bvvg-fiF.mjs +234 -0
- package/dist/run-DBUAq-Uu.mjs +1452 -0
- package/dist/send-nrKtak5F.mjs +105 -0
- package/dist/{src-Dujmvv4s.mjs → src-8cOPOgrT.mjs} +12523 -5507
- package/dist/{src-BbyEoMn4.mjs → src-CmEvHdju.mjs} +797 -233
- package/dist/src-D4JSymhK.mjs +3104 -0
- package/dist/{src-DOcK8Jfp.mjs → src-I_RyKu6_.mjs} +1022 -590
- package/dist/store-CDpk8Wtz.mjs +16 -0
- package/dist/ui/THIRD_PARTY_LICENSES.txt +612 -0
- package/dist/ui/assets/app--FijEXiJ.css +1 -0
- package/dist/ui/assets/app-BowhjEAe.js +112 -0
- package/dist/ui/assets/{index-9dFha9Qn.js → index-CrMa2oHB.js} +3 -3
- package/dist/ui/assets/theme-CdXvqTQ6.js +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/{ui-CTmO2Xzu.mjs → ui-DY3Dnpho.mjs} +54 -25
- package/dist/usage-1VwSg_Qt.mjs +257 -0
- package/dist/validate-BxayjjkV.mjs +632 -0
- package/dist/verdict-CMvPCpWC.mjs +84 -0
- package/dist/{watch-CtbgDAcz.mjs → watch-CT0EtLPZ.mjs} +45 -17
- package/dist/{workflows-D1_WVukk.mjs → workflows-DHHi-APg.mjs} +30 -5
- package/dist/worktrees-CZvbHzbN.mjs +302 -0
- package/package.json +10 -8
- package/dist/__build_info__-fy6L1Mrd.mjs +0 -615
- package/dist/clean-DaHjs__2.mjs +0 -88
- package/dist/context-DT8MfEIR.mjs +0 -274
- package/dist/format-Dnfv8zMT.mjs +0 -427
- package/dist/in-a-run-pLAZeNys.mjs +0 -22
- package/dist/list-BrH8KRSJ.mjs +0 -110
- package/dist/output-BLQWD6MJ.mjs +0 -950
- package/dist/remove-BeGdqroy.mjs +0 -194
- package/dist/report-DK4LGqwF.mjs +0 -129
- package/dist/send-C8tunWhv.mjs +0 -163
- package/dist/src-CCUKRX51.mjs +0 -1204
- package/dist/ui/assets/app-BNT0SuIz.css +0 -1
- package/dist/ui/assets/app-T7wVAhgx.js +0 -67
- package/dist/ui/assets/theme-3xH5jxKs.js +0 -1
- package/dist/usage-DYUmP0i4.mjs +0 -419
- package/dist/worktrees-DdMYoNOC.mjs +0 -212
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# @krapht/ork
|
|
2
2
|
|
|
3
|
-
The `ork` command: orchestrate
|
|
4
|
-
serializable workflows.
|
|
3
|
+
The `ork` command: orchestrate coding agents (Claude Code, OpenCode, Command
|
|
4
|
+
Code) on your machine with hierarchical, serializable workflows.
|
|
5
5
|
|
|
6
|
-
> **Alpha.** Workflows of
|
|
6
|
+
> **Alpha.** Workflows of coding agents and tools, run from the terminal or the
|
|
7
7
|
> browser (`ork ui`). Expect changes until 1.0.0. See the
|
|
8
8
|
> [project README](https://github.com/mimshins/ork#readme), the
|
|
9
9
|
> [guides](https://github.com/mimshins/ork/tree/main/docs#readme) and
|
|
@@ -26,10 +26,15 @@ ork worktrees clean # removes the merged ones; asks first
|
|
|
26
26
|
ork upgrade # a newer version? upgrade the way you installed Ork
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
A workflow that names no
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
A workflow that names no agent driver runs on the one that's ready. Today that
|
|
30
|
+
is `claude-code`: your own `claude`, logged in or with `ANTHROPIC_API_KEY` or a
|
|
31
|
+
cloud provider set. Your own `opencode` (`open-code`) and Command Code's `cmd`
|
|
32
|
+
(`command-code`) work too: `--driver <id>`, or a driver per node
|
|
33
|
+
([Drivers](https://github.com/mimshins/ork/blob/main/docs/guides/drivers.md)).
|
|
34
|
+
|
|
35
|
+
Ork is built with Ork: its own workflows plan, build, review and document its
|
|
36
|
+
features, and each run is measured against a manual session in
|
|
37
|
+
[Benchmarks: Ork building Ork](https://github.com/mimshins/ork/blob/main/benchmarks/README.md).
|
|
33
38
|
|
|
34
39
|
Ork never stores or asks for credentials: it uses the tools you already have,
|
|
35
40
|
signed in with your own accounts. Once a day it asks the npm registry whether a
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { E as parseVersion, T as isNewer, w as compareVersions } from "./src-8cOPOgrT.mjs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
// ../core/src/remote/session.ts
|
|
6
|
+
const chattable = (type) => type === "agent.leader" || type === "agent.worker";
|
|
7
|
+
/** Reads `node`'s session from a run's events, and every node's last output. */
|
|
8
|
+
const nodeSession = (events, node) => {
|
|
9
|
+
const session = {
|
|
10
|
+
known: false,
|
|
11
|
+
chattable: false,
|
|
12
|
+
running: false
|
|
13
|
+
};
|
|
14
|
+
const outputs = {};
|
|
15
|
+
for (const { event } of events) {
|
|
16
|
+
if (event.type === "run.started") {
|
|
17
|
+
const declared = event.nodes.find((n) => n.id === node);
|
|
18
|
+
if (declared !== void 0) {
|
|
19
|
+
session.known = true;
|
|
20
|
+
session.chattable = chattable(declared.type);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (event.type === "node.finished") outputs[event.id] = event.output;
|
|
24
|
+
if (!("id" in event) || event.id !== node) continue;
|
|
25
|
+
if (event.type === "node.started") {
|
|
26
|
+
session.known = true;
|
|
27
|
+
session.chattable = chattable(event.nodeType);
|
|
28
|
+
session.running = true;
|
|
29
|
+
}
|
|
30
|
+
if (event.type === "node.finished") session.running = false;
|
|
31
|
+
if (event.type === "worktree" && event.action === "created") session.checkout = {
|
|
32
|
+
path: event.path,
|
|
33
|
+
...event.branch === void 0 ? {} : { branch: event.branch },
|
|
34
|
+
own: true,
|
|
35
|
+
owner: node
|
|
36
|
+
};
|
|
37
|
+
if (event.type !== "node.event") continue;
|
|
38
|
+
const inner = event.event;
|
|
39
|
+
if (inner.fork === true) continue;
|
|
40
|
+
if (inner.type === "session.started") {
|
|
41
|
+
session.sessionId = inner.sessionId;
|
|
42
|
+
if (inner.cwd !== void 0) session.cwd = inner.cwd;
|
|
43
|
+
if (inner.driver !== void 0) session.driver = inner.driver;
|
|
44
|
+
if (inner.model !== "") session.model = inner.model;
|
|
45
|
+
}
|
|
46
|
+
if (inner.type === "session.finished" && inner.outcome.sessionId !== "") session.sessionId = inner.outcome.sessionId;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
session,
|
|
50
|
+
outputs
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
/** The dist-tags every install follows, toward and after 1.0.0. */
|
|
54
|
+
const CHANNEL_TAGS = [
|
|
55
|
+
"latest",
|
|
56
|
+
"alpha",
|
|
57
|
+
"beta",
|
|
58
|
+
"rc"
|
|
59
|
+
];
|
|
60
|
+
/** The dist-tag of snapshots from `main`, followed only by a canary. */
|
|
61
|
+
const CANARY_TAG = "canary";
|
|
62
|
+
const updateCachePath = (home = homedir()) => path.join(home, ".ork", "update-check.json");
|
|
63
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
64
|
+
const text = (record, key) => {
|
|
65
|
+
const value = record[key];
|
|
66
|
+
return typeof value === "string" ? value : void 0;
|
|
67
|
+
};
|
|
68
|
+
const notifiedOf = (value) => {
|
|
69
|
+
if (!isRecord(value)) return void 0;
|
|
70
|
+
const version = text(value, "version");
|
|
71
|
+
const at = text(value, "at");
|
|
72
|
+
return version === void 0 || at === void 0 ? void 0 : {
|
|
73
|
+
version,
|
|
74
|
+
at
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
/** The cache, or `null` when there is none or it cannot be read. */
|
|
78
|
+
const readUpdateCache = (file) => {
|
|
79
|
+
let parsed;
|
|
80
|
+
try {
|
|
81
|
+
parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
if (!isRecord(parsed)) return null;
|
|
86
|
+
const checkedAt = text(parsed, "checkedAt");
|
|
87
|
+
if (checkedAt === void 0) return null;
|
|
88
|
+
const latest = text(parsed, "latest");
|
|
89
|
+
const notified = notifiedOf(parsed["notified"]);
|
|
90
|
+
return {
|
|
91
|
+
checkedAt,
|
|
92
|
+
...latest === void 0 ? {} : { latest },
|
|
93
|
+
...notified === void 0 ? {} : { notified }
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
/** Writes the cache whole (a temporary file, then a rename). */
|
|
97
|
+
const writeUpdateCache = (file, cache) => {
|
|
98
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
99
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
100
|
+
writeFileSync(temporary, `${JSON.stringify(cache, null, 2)}\n`);
|
|
101
|
+
renameSync(temporary, file);
|
|
102
|
+
};
|
|
103
|
+
const age = (iso, now) => {
|
|
104
|
+
const time = Date.parse(iso);
|
|
105
|
+
return Number.isNaN(time) ? Infinity : now.getTime() - time;
|
|
106
|
+
};
|
|
107
|
+
/** Whether it is time to ask the registry again. */
|
|
108
|
+
const checkDue = (cache, now) => cache === null || age(cache.checkedAt, now) >= 864e5;
|
|
109
|
+
/** Whether `version` is a canary snapshot: its pre-release starts `canary`. */
|
|
110
|
+
const isCanary = (version) => parseVersion(version)?.pre[0]?.startsWith(CANARY_TAG) === true;
|
|
111
|
+
/**
|
|
112
|
+
* The newest version, by semver precedence, among the dist-tags `current`
|
|
113
|
+
* follows: the channel tags, and `canary` too when `current` is a canary. A tag
|
|
114
|
+
* whose value doesn't parse is skipped; `null` when none does.
|
|
115
|
+
*/
|
|
116
|
+
const newestTagged = (tags, current) => {
|
|
117
|
+
return (current !== void 0 && isCanary(current) ? [...CHANNEL_TAGS, "canary"] : CHANNEL_TAGS).flatMap((tag) => {
|
|
118
|
+
const value = tags[tag];
|
|
119
|
+
const version = typeof value === "string" ? parseVersion(value) : null;
|
|
120
|
+
return typeof value === "string" && version !== null ? [{
|
|
121
|
+
value,
|
|
122
|
+
version
|
|
123
|
+
}] : [];
|
|
124
|
+
}).reduce((best, candidate) => best === null || compareVersions(candidate.version, best.version) > 0 ? candidate : best, null)?.value ?? null;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* The newest version on the registry that the running version follows ({@link
|
|
128
|
+
* newestTagged}), or `null` on any failure.
|
|
129
|
+
*/
|
|
130
|
+
const fetchLatest = async (name, options = {}) => {
|
|
131
|
+
const registry = (options.registry ?? "https://registry.npmjs.org").replace(/\/+$/u, "");
|
|
132
|
+
const get = options.fetch ?? globalThis.fetch;
|
|
133
|
+
try {
|
|
134
|
+
const response = await get(`${registry}/-/package/${name}/dist-tags`, {
|
|
135
|
+
headers: { accept: "application/json" },
|
|
136
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? 5e3)
|
|
137
|
+
});
|
|
138
|
+
if (!response.ok) return null;
|
|
139
|
+
const tags = await response.json();
|
|
140
|
+
return isRecord(tags) ? newestTagged(tags, options.current) : null;
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Asks the registry and records the answer. A failed check is recorded too, so
|
|
147
|
+
* an offline machine is not asked again until the interval passes; the last
|
|
148
|
+
* known version is kept. `options.current` is the running version.
|
|
149
|
+
*/
|
|
150
|
+
const refreshUpdateCache = async (file, name, now, options = {}) => {
|
|
151
|
+
const before = readUpdateCache(file);
|
|
152
|
+
const latest = await fetchLatest(name, options) ?? before?.latest;
|
|
153
|
+
const cache = {
|
|
154
|
+
...before,
|
|
155
|
+
checkedAt: now.toISOString(),
|
|
156
|
+
...latest === void 0 ? {} : { latest }
|
|
157
|
+
};
|
|
158
|
+
writeUpdateCache(file, cache);
|
|
159
|
+
return cache;
|
|
160
|
+
};
|
|
161
|
+
/** The newer version the cache knows of, if any. */
|
|
162
|
+
const newerVersion = (cache, current) => cache?.latest !== void 0 && isNewer(cache.latest, current) ? cache.latest : null;
|
|
163
|
+
/**
|
|
164
|
+
* The version to tell the user about now: newer than theirs, and not already
|
|
165
|
+
* told today. `null` when there is nothing to say.
|
|
166
|
+
*/
|
|
167
|
+
const pendingNotice = (cache, current, now) => {
|
|
168
|
+
const newer = newerVersion(cache, current);
|
|
169
|
+
if (newer === null) return null;
|
|
170
|
+
const told = cache?.notified;
|
|
171
|
+
return told?.version === newer && age(told.at, now) < 864e5 ? null : newer;
|
|
172
|
+
};
|
|
173
|
+
// ../core/src/update/switches.ts
|
|
174
|
+
/**
|
|
175
|
+
* @file The environment's say over the update check (ADR-038): the variables
|
|
176
|
+
* that turn it off (`ORK_NO_UPDATE_CHECK`, the common `NO_UPDATE_NOTIFIER`,
|
|
177
|
+
* and `CI`), and the registry to ask instead of npm's. The CLI adds its own
|
|
178
|
+
* rules about where a notice may go; the server behind `ork ui` uses only
|
|
179
|
+
* these.
|
|
180
|
+
*/
|
|
181
|
+
/** Turns the automatic check and its notice off. */
|
|
182
|
+
const NO_UPDATE_ENV = "ORK_NO_UPDATE_CHECK";
|
|
183
|
+
/** A registry to ask instead of npm's, e.g. a company mirror. */
|
|
184
|
+
const UPDATE_REGISTRY_ENV = "ORK_UPDATE_REGISTRY";
|
|
185
|
+
/** The variables that turn the check off, in the order they are looked at. */
|
|
186
|
+
const UPDATE_SWITCHES = [
|
|
187
|
+
NO_UPDATE_ENV,
|
|
188
|
+
"NO_UPDATE_NOTIFIER",
|
|
189
|
+
"CI"
|
|
190
|
+
];
|
|
191
|
+
/** Set to anything but empty, `0` or `false`. */
|
|
192
|
+
const isSwitchedOn = (value) => value !== void 0 && value !== "" && value !== "0" && value.toLowerCase() !== "false";
|
|
193
|
+
/** The variable that turns the check off in `env`, or `null` when none does. */
|
|
194
|
+
const updateSwitchOff = (env) => UPDATE_SWITCHES.find((name) => isSwitchedOn(env[name])) ?? null;
|
|
195
|
+
/** The registry `ORK_UPDATE_REGISTRY` names, if it names one. */
|
|
196
|
+
const registryOf = (env) => {
|
|
197
|
+
const registry = env[UPDATE_REGISTRY_ENV];
|
|
198
|
+
return registry === void 0 || registry === "" ? void 0 : registry;
|
|
199
|
+
};
|
|
200
|
+
/** The host of a registry that isn't an http(s) URL, past any `user:pass@`. */
|
|
201
|
+
const hostOf = (registry) => registry.replace(/^[a-z][\w+.-]*:\/\//iu, "").replace(/^[^/?#]*@/u, "").split(/[/?#]/u)[0] ?? "";
|
|
202
|
+
/**
|
|
203
|
+
* The registry as it may be shown (on a page, in a message): an http(s) URL
|
|
204
|
+
* without its user name and password, which a mirror's URL may carry; anything
|
|
205
|
+
* else only as its host.
|
|
206
|
+
*/
|
|
207
|
+
const registryShown = (registry) => {
|
|
208
|
+
try {
|
|
209
|
+
const url = new URL(registry);
|
|
210
|
+
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
211
|
+
if (url.username === "" && url.password === "") return registry;
|
|
212
|
+
url.username = "";
|
|
213
|
+
url.password = "";
|
|
214
|
+
return url.href;
|
|
215
|
+
}
|
|
216
|
+
} catch {}
|
|
217
|
+
return hostOf(registry);
|
|
218
|
+
};
|
|
219
|
+
// src/__build_info__.ts
|
|
220
|
+
/**
|
|
221
|
+
* @file The published package's name and version, bound to
|
|
222
|
+
* `packages/cli/package.json`. Do not edit by hand: `pnpm changesets:version`
|
|
223
|
+
* and `pnpm build` rewrite it (`tools/release/build-info.ts`), and a test
|
|
224
|
+
* fails when it is stale.
|
|
225
|
+
*/
|
|
226
|
+
const ORK_NAME = "@krapht/ork";
|
|
227
|
+
const ORK_VERSION = "1.0.0-alpha.20";
|
|
228
|
+
export { registryShown as a, fetchLatest as c, readUpdateCache as d, refreshUpdateCache as f, nodeSession as h, registryOf as i, newerVersion as l, writeUpdateCache as m, ORK_VERSION as n, updateSwitchOff as o, updateCachePath as p, isSwitchedOn as r, checkDue as s, ORK_NAME as t, pendingNotice as u };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// ../core/src/list-filters/apply.ts
|
|
2
|
+
/**
|
|
3
|
+
* @file Keeps the rows a list's filter matches (RFC-017). The list's own order
|
|
4
|
+
* and `--limit` stay the caller's: filter, then order, then limit.
|
|
5
|
+
*/
|
|
6
|
+
const fieldMatches = (field, row, wanted) => {
|
|
7
|
+
if (wanted === void 0 || wanted === "all" || field.get === void 0) return true;
|
|
8
|
+
const got = field.get(row);
|
|
9
|
+
if (got === void 0) return false;
|
|
10
|
+
const values = typeof got === "string" ? [got] : got;
|
|
11
|
+
if (field.type === "since") return values.some((value) => Date.parse(value) >= Date.parse(wanted));
|
|
12
|
+
if (field.type === "until") return values.some((value) => Date.parse(value) < Date.parse(wanted));
|
|
13
|
+
return values.includes(wanted);
|
|
14
|
+
};
|
|
15
|
+
/** The rows of `rows` every field of `filter` matches, in their order. */
|
|
16
|
+
const applyFilters = (table, rows, filter) => rows.filter((row) => table.fields.every((field) => fieldMatches(field, row, filter[field.name])));
|
|
17
|
+
export { applyFilters as t };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { parseArgs } from "citty";
|
|
2
|
+
// src/argument-error.ts
|
|
3
|
+
/**
|
|
4
|
+
* @file The error citty's `runMain` takes for an argument error: the plugins
|
|
5
|
+
* that refuse a flag (`list/known-flags.ts`) or an extra word
|
|
6
|
+
* (`extra-arguments.ts`) throw one, so the message is one line and the exit
|
|
7
|
+
* code is 1, as for a wrong enum value.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* An error citty's `runMain` takes for an argument error (its `CLIError`, which
|
|
11
|
+
* it does not export): a failed parse gives one, and it takes `message`.
|
|
12
|
+
*/
|
|
13
|
+
const argumentError = (message) => {
|
|
14
|
+
try {
|
|
15
|
+
parseArgs([], { x: {
|
|
16
|
+
type: "string",
|
|
17
|
+
required: true
|
|
18
|
+
} });
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (error instanceof Error) {
|
|
21
|
+
error.message = message;
|
|
22
|
+
return error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return new Error(message);
|
|
26
|
+
};
|
|
27
|
+
export { argumentError as t };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import "./src-8cOPOgrT.mjs";
|
|
2
|
+
import { t as findRun } from "./find-BTikxc54.mjs";
|
|
3
|
+
import { c as write, l as writeErr, s as style } from "./output-B1kwo0YU.mjs";
|
|
4
|
+
import { m as createTerminalHuman, t as attachRun } from "./attach-CEGbRg1D.mjs";
|
|
5
|
+
import { n as attachRefusal } from "./in-a-run-CbnKjO7t.mjs";
|
|
6
|
+
import { t as refuseExtraArguments } from "./extra-arguments-BxQMXARO.mjs";
|
|
7
|
+
import { t as withStore } from "./store-CDpk8Wtz.mjs";
|
|
8
|
+
import { defineCommand } from "citty";
|
|
9
|
+
// src/commands/attach.ts
|
|
10
|
+
/**
|
|
11
|
+
* @file `ork attach`: follow a run and the runs it called on this terminal, and
|
|
12
|
+
* answer their open decisions here (specs/cli.md). Ctrl-C detaches: the run
|
|
13
|
+
* keeps going and its decisions stay open (specs/run-control.md).
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Follows `runId` and what it called until it ends or the person detaches, and
|
|
17
|
+
* returns the exit code: 0 when the run succeeded or they detached, else 1.
|
|
18
|
+
*/
|
|
19
|
+
const attachSession = async (store, runId, session) => {
|
|
20
|
+
const detach = new AbortController();
|
|
21
|
+
session.signal?.addEventListener("abort", () => {
|
|
22
|
+
detach.abort();
|
|
23
|
+
});
|
|
24
|
+
const interactive = session.terminal?.interactive === true;
|
|
25
|
+
const human = session.terminal === void 0 || !interactive ? void 0 : createTerminalHuman({
|
|
26
|
+
...session.terminal,
|
|
27
|
+
interrupt: () => {
|
|
28
|
+
detach.abort();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
try {
|
|
32
|
+
const attached = await attachRun(store, runId, session.out, {
|
|
33
|
+
signal: detach.signal,
|
|
34
|
+
hintNoTerminal: human === void 0,
|
|
35
|
+
...human === void 0 ? {} : { human },
|
|
36
|
+
...session.home === void 0 ? {} : { home: session.home },
|
|
37
|
+
...session.intervalMs === void 0 ? {} : { intervalMs: session.intervalMs }
|
|
38
|
+
});
|
|
39
|
+
if (detach.signal.aborted) {
|
|
40
|
+
session.out(style("dim", `detached; run ${runId} keeps going · ork attach ${runId} · ork stop ${runId}`));
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
return attached.exitCode;
|
|
44
|
+
} finally {
|
|
45
|
+
human?.close();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const attach = defineCommand({
|
|
49
|
+
meta: {
|
|
50
|
+
name: "attach",
|
|
51
|
+
description: "Follow a run and the runs it called, and answer their questions here; Ctrl-C detaches"
|
|
52
|
+
},
|
|
53
|
+
plugins: [refuseExtraArguments],
|
|
54
|
+
args: { run: {
|
|
55
|
+
type: "positional",
|
|
56
|
+
description: "A run id or a prefix of one",
|
|
57
|
+
required: true
|
|
58
|
+
} },
|
|
59
|
+
run: ({ args }) => withStore(async (store) => {
|
|
60
|
+
const refusal = process.stdin.isTTY ? attachRefusal() : null;
|
|
61
|
+
if (refusal !== null) {
|
|
62
|
+
writeErr(refusal);
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const found = findRun(store, args.run, process.cwd());
|
|
67
|
+
if (!found.ok) {
|
|
68
|
+
writeErr(found.error);
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const abort = new AbortController();
|
|
73
|
+
const onSigint = () => {
|
|
74
|
+
abort.abort();
|
|
75
|
+
};
|
|
76
|
+
process.once("SIGINT", onSigint);
|
|
77
|
+
try {
|
|
78
|
+
process.exitCode = await attachSession(store, found.run.id, {
|
|
79
|
+
out: write,
|
|
80
|
+
signal: abort.signal,
|
|
81
|
+
terminal: {
|
|
82
|
+
input: process.stdin,
|
|
83
|
+
output: process.stdout,
|
|
84
|
+
interactive: process.stdin.isTTY
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
} finally {
|
|
88
|
+
process.off("SIGINT", onSigint);
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
});
|
|
92
|
+
export { attach };
|