@krapht/ork 0.3.0 → 1.0.0-alpha.0
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 +16 -12
- package/dist/__build_info__-DdTVd2p5.mjs +615 -0
- package/dist/bin.mjs +1572 -909
- package/dist/clean-NnboOuz6.mjs +88 -0
- package/dist/completion-snnYGjgK.mjs +405 -0
- package/dist/{context-BFGC_C02.mjs → context-DO3sBy9v.mjs} +30 -191
- package/dist/decide-B81wCZQg.mjs +113 -0
- package/dist/{find-BcvhT6UN.mjs → find-BKW-uGey.mjs} +1 -1
- package/dist/{format-DqpObyMl.mjs → format-CmTlTOjT.mjs} +72 -198
- package/dist/help-groups-BwYoAWsh.mjs +354 -0
- package/dist/{in-a-run-BbLhZOFW.mjs → in-a-run-BPXX09Bs.mjs} +1 -1
- package/dist/{limits-BKYAmqCR.mjs → limits-JCve2QpI.mjs} +1 -1
- package/dist/list-C7UEgmtd.mjs +110 -0
- package/dist/{mcp-BWsdQyri.mjs → mcp-CKPyNaKD.mjs} +7 -7
- package/dist/{output-Q2EdeVUt.mjs → output-B1V6ZJSx.mjs} +130 -36
- package/dist/{pause-DKACDfV1.mjs → pause-B_SL_yDz.mjs} +4 -4
- package/dist/{relay-CbK-Wme9.mjs → relay-o-Luee1c.mjs} +2 -2
- package/dist/remove-DAZ7ZCzt.mjs +194 -0
- package/dist/{send-C6yr04Fe.mjs → send-3B64fBNB.mjs} +3 -3
- package/dist/{src-D5ruZFgD.mjs → src-BJTegEzY.mjs} +1212 -57
- package/dist/{src-DtN1k-B3.mjs → src-C38BPzit.mjs} +251 -18
- package/dist/{src-Cz1yOUTg.mjs → src-DHvWU4tX.mjs} +2742 -1659
- package/dist/{src-CZOVF2yU.mjs → src-Dyz_Gy1q.mjs} +35 -8
- package/dist/ui/THIRD_PARTY_LICENSES.txt +2 -2
- package/dist/ui/assets/app-C8nJ6NpT.js +67 -0
- package/dist/ui/assets/app-CH7YC3in.css +1 -0
- package/dist/ui/assets/{index-BiO54L90.css → index-BPIbqRRb.css} +1 -1
- package/dist/ui/assets/{index-BoG6O-Lp.js → index-D83F2OKZ.js} +2 -2
- package/dist/ui/index.html +2 -3
- package/dist/ui-CY4xxJUu.mjs +472 -0
- package/dist/{usage-BIjf9Xdt.mjs → usage-CJnytoe4.mjs} +6 -5
- package/dist/{watch-B8o4Szd_.mjs → watch-BJB82ZJ7.mjs} +7 -7
- package/dist/workflows-CiM7S3l0.mjs +88 -0
- package/dist/worktrees-86ySXxR6.mjs +212 -0
- package/package.json +6 -6
- package/dist/__build_info__-CXXMLvjt.mjs +0 -104
- package/dist/ui/assets/app-BlDdP396.js +0 -61
- package/dist/ui/assets/app-C722xNZ3.css +0 -1
- package/dist/ui-CZS1XKla.mjs +0 -160
package/dist/ui/index.html
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html
|
|
3
3
|
lang="en"
|
|
4
|
-
data-theme="dark"
|
|
5
4
|
data-contrast="standard"
|
|
6
5
|
>
|
|
7
6
|
<head>
|
|
@@ -20,9 +19,9 @@
|
|
|
20
19
|
href="/favicon.svg"
|
|
21
20
|
type="image/svg+xml"
|
|
22
21
|
/>
|
|
23
|
-
<script type="module" crossorigin src="/assets/index-
|
|
22
|
+
<script type="module" crossorigin src="/assets/index-D83F2OKZ.js"></script>
|
|
24
23
|
<link rel="modulepreload" crossorigin href="/assets/theme-3xH5jxKs.js">
|
|
25
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
24
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BPIbqRRb.css">
|
|
26
25
|
</head>
|
|
27
26
|
<body>
|
|
28
27
|
<div id="root"></div>
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { n as ORK_VERSION, t as ORK_NAME } from "./__build_info__-DdTVd2p5.mjs";
|
|
2
|
+
import { a as write, i as style, o as writeErr } from "./output-B1V6ZJSx.mjs";
|
|
3
|
+
import { i as uiFilePath, l as newToken, n as startServer, r as runningServer, t as DEFAULT_PORT } from "./src-BJTegEzY.mjs";
|
|
4
|
+
import { a as mayShowSignIn } from "./in-a-run-BPXX09Bs.mjs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { constants } from "node:os";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { defineCommand } from "citty";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
// src/browser/open.ts
|
|
11
|
+
/**
|
|
12
|
+
* @file Opening a link in the user's browser, for `ork ui`. Each way is an
|
|
13
|
+
* opener in a registry, tried in order when it applies to this platform:
|
|
14
|
+
* `open` on macOS, `wslview` then `xdg-open` under WSL, `xdg-open` on Linux.
|
|
15
|
+
* Adding a platform is one more entry. When none starts, the caller prints
|
|
16
|
+
* the link instead.
|
|
17
|
+
*/
|
|
18
|
+
const wsl = (env) => env["WSL_DISTRO_NAME"] !== void 0;
|
|
19
|
+
const OPENERS = [
|
|
20
|
+
{
|
|
21
|
+
id: "macos",
|
|
22
|
+
applies: (platform) => platform === "darwin",
|
|
23
|
+
program: "open"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "wslview",
|
|
27
|
+
applies: (platform, env) => platform === "linux" && wsl(env),
|
|
28
|
+
program: "wslview"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "xdg-open",
|
|
32
|
+
applies: (platform) => platform === "linux" || platform === "freebsd",
|
|
33
|
+
program: "xdg-open"
|
|
34
|
+
}
|
|
35
|
+
];
|
|
36
|
+
/** Starts one program with the link; false when it cannot be started. */
|
|
37
|
+
const start = (program, url) => new Promise((resolve) => {
|
|
38
|
+
const child = spawn(program, [url], {
|
|
39
|
+
detached: true,
|
|
40
|
+
stdio: "ignore"
|
|
41
|
+
});
|
|
42
|
+
child.once("error", () => {
|
|
43
|
+
resolve(false);
|
|
44
|
+
});
|
|
45
|
+
child.once("spawn", () => {
|
|
46
|
+
child.unref();
|
|
47
|
+
resolve(true);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
/** Opens `url` with the first opener that starts; false when none did. */
|
|
51
|
+
const openInBrowser = async (url, options = {}) => {
|
|
52
|
+
const platform = options.platform ?? process.platform;
|
|
53
|
+
const env = options.env ?? process.env;
|
|
54
|
+
for (const opener of options.openers ?? OPENERS) {
|
|
55
|
+
if (!opener.applies(platform, env)) continue;
|
|
56
|
+
if (await start(opener.program, url)) return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
};
|
|
60
|
+
// src/browser/folder.ts
|
|
61
|
+
/**
|
|
62
|
+
* @file Where `ork ui` finds the built UI: `dist/ui` beside the published
|
|
63
|
+
* bundle, or, run from source, `packages/ui/dist` once it is built (`pnpm
|
|
64
|
+
* --filter @krapht/ork-ui build`). The CLI takes the UI's built files, never
|
|
65
|
+
* its source; without them the server shows its placeholder page.
|
|
66
|
+
*/
|
|
67
|
+
/** The UI package's built folder in the workspace, or null outside it. */
|
|
68
|
+
const workspaceUi = () => {
|
|
69
|
+
try {
|
|
70
|
+
const manifest = fileURLToPath(import.meta.resolve("@krapht/ork-ui/package.json"));
|
|
71
|
+
return path.join(path.dirname(manifest), "dist");
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
/** The folders that may hold the built UI, in the order they are tried. */
|
|
77
|
+
const uiFolders = () => {
|
|
78
|
+
const workspace = workspaceUi();
|
|
79
|
+
return [path.join(import.meta.dirname, "ui"), ...workspace === null ? [] : [workspace]];
|
|
80
|
+
};
|
|
81
|
+
// src/browser/ui-protocol.ts
|
|
82
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
83
|
+
const TYPES = /* @__PURE__ */ new Set([
|
|
84
|
+
"ork-ui-hello",
|
|
85
|
+
"ork-ui-ready",
|
|
86
|
+
"ork-ui-failed",
|
|
87
|
+
"ork-ui-restart"
|
|
88
|
+
]);
|
|
89
|
+
/** A message from the child, or `null` for anything else. */
|
|
90
|
+
const childMessage = (value) => {
|
|
91
|
+
if (!isRecord(value) || !TYPES.has(value["type"])) return null;
|
|
92
|
+
const { type, port, version, error } = value;
|
|
93
|
+
if (type === "ork-ui-ready") return typeof port === "number" && typeof version === "string" ? {
|
|
94
|
+
type,
|
|
95
|
+
port,
|
|
96
|
+
version
|
|
97
|
+
} : null;
|
|
98
|
+
if (type === "ork-ui-failed") return {
|
|
99
|
+
type,
|
|
100
|
+
error: typeof error === "string" ? error : "It failed."
|
|
101
|
+
};
|
|
102
|
+
return type === "ork-ui-hello" ? { type } : { type: "ork-ui-restart" };
|
|
103
|
+
};
|
|
104
|
+
/** The supervisor's `start`, or `null` for anything else. */
|
|
105
|
+
const startMessage = (value) => isRecord(value) && value["type"] === "ork-ui-start" && typeof value["port"] === "number" && typeof value["token"] === "string" ? {
|
|
106
|
+
type: "ork-ui-start",
|
|
107
|
+
port: value["port"],
|
|
108
|
+
token: value["token"]
|
|
109
|
+
} : null;
|
|
110
|
+
// src/browser/serve.ts
|
|
111
|
+
/**
|
|
112
|
+
* @file The server's side of `ork ui` (ADR-060): `ork ui --serve`, which only
|
|
113
|
+
* `ork ui` starts. It says hello over its IPC channel, takes the port and the
|
|
114
|
+
* token from the supervisor, serves, and says it's ready. A restart asked by
|
|
115
|
+
* the page tells the supervisor, then closes and exits, so the supervisor
|
|
116
|
+
* starts the installed `ork` in its place. Ctrl-C, SIGTERM, or losing the
|
|
117
|
+
* supervisor closes it too, so no server is left behind; an upgrade still
|
|
118
|
+
* running is waited for first.
|
|
119
|
+
*/
|
|
120
|
+
const NOT_SUPERVISED = "`ork ui --serve` is how `ork ui` starts its server; run `ork ui`.";
|
|
121
|
+
const send = (message) => new Promise((resolve) => {
|
|
122
|
+
if (!process.connected || process.send === void 0) {
|
|
123
|
+
resolve();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
process.send(message, void 0, {}, () => {
|
|
127
|
+
resolve();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
/** The supervisor's `start`, after saying hello. */
|
|
131
|
+
const started = () => new Promise((resolve) => {
|
|
132
|
+
const listen = (raw) => {
|
|
133
|
+
const message = startMessage(raw);
|
|
134
|
+
if (message === null) return;
|
|
135
|
+
process.off("message", listen);
|
|
136
|
+
resolve(message);
|
|
137
|
+
};
|
|
138
|
+
process.on("message", listen);
|
|
139
|
+
send({ type: "ork-ui-hello" });
|
|
140
|
+
});
|
|
141
|
+
const ignore = () => {};
|
|
142
|
+
/** What the terminal says while an upgrade holds the stop up. */
|
|
143
|
+
const WAITING_FOR_UPGRADE = "Waiting for the upgrade to finish… Press Ctrl-C again to stop now.";
|
|
144
|
+
/**
|
|
145
|
+
* Ends on Ctrl-C, SIGTERM, the supervisor's channel closing, or `end`. Each
|
|
146
|
+
* Ctrl-C reaches the child twice (from the terminal, and passed on by the
|
|
147
|
+
* supervisor), so while the supervisor is there, forcing a stop is its job: it
|
|
148
|
+
* kills the child on the second. Without it, a signal after the first stop
|
|
149
|
+
* exits at once.
|
|
150
|
+
*/
|
|
151
|
+
const stopper = () => {
|
|
152
|
+
let end = ignore;
|
|
153
|
+
let stopped = false;
|
|
154
|
+
const promise = new Promise((resolve) => {
|
|
155
|
+
end = () => {
|
|
156
|
+
stopped = true;
|
|
157
|
+
resolve();
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
const onSignal = (signal) => {
|
|
161
|
+
if (stopped && !process.connected) process.exit(128 + constants.signals[signal]);
|
|
162
|
+
end();
|
|
163
|
+
};
|
|
164
|
+
process.on("SIGINT", onSignal);
|
|
165
|
+
process.on("SIGTERM", onSignal);
|
|
166
|
+
process.once("disconnect", () => {
|
|
167
|
+
end();
|
|
168
|
+
});
|
|
169
|
+
return {
|
|
170
|
+
promise,
|
|
171
|
+
end: () => {
|
|
172
|
+
end();
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Waits for an upgrade still running before the server closes, saying so on the
|
|
178
|
+
* terminal: exiting mid-install could leave `ork` half-installed. The
|
|
179
|
+
* installer's output is read to its end meanwhile.
|
|
180
|
+
*/
|
|
181
|
+
const upgradeFinished = async (server, say) => {
|
|
182
|
+
const running = server.upgrading();
|
|
183
|
+
if (running === null) return;
|
|
184
|
+
say(WAITING_FOR_UPGRADE);
|
|
185
|
+
await running.catch(ignore);
|
|
186
|
+
};
|
|
187
|
+
const serve = async (start, end) => {
|
|
188
|
+
try {
|
|
189
|
+
return await startServer({
|
|
190
|
+
version: ORK_VERSION,
|
|
191
|
+
port: start.port,
|
|
192
|
+
token: start.token,
|
|
193
|
+
ui: uiFolders(),
|
|
194
|
+
update: { name: ORK_NAME },
|
|
195
|
+
restart: () => {
|
|
196
|
+
send({ type: "ork-ui-restart" }).then(end);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
} catch (error) {
|
|
200
|
+
await send({
|
|
201
|
+
type: "ork-ui-failed",
|
|
202
|
+
error: error instanceof Error ? error.message : String(error)
|
|
203
|
+
});
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
/** `ork ui --serve`: serves until stopped; exits when it closes. */
|
|
208
|
+
const serveForSupervisor = async () => {
|
|
209
|
+
if (process.send === void 0) {
|
|
210
|
+
writeErr(NOT_SUPERVISED);
|
|
211
|
+
process.exitCode = 1;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const stop = stopper();
|
|
215
|
+
const start = await Promise.race([started(), stop.promise.then(() => null)]);
|
|
216
|
+
const server = start === null ? null : await serve(start, stop.end);
|
|
217
|
+
if (server !== null) {
|
|
218
|
+
await send({
|
|
219
|
+
type: "ork-ui-ready",
|
|
220
|
+
port: server.port,
|
|
221
|
+
version: ORK_VERSION
|
|
222
|
+
});
|
|
223
|
+
await stop.promise;
|
|
224
|
+
await upgradeFinished(server, writeErr);
|
|
225
|
+
await server.close();
|
|
226
|
+
}
|
|
227
|
+
process.exit(server === null ? 1 : 0);
|
|
228
|
+
};
|
|
229
|
+
// src/browser/supervise.ts
|
|
230
|
+
/**
|
|
231
|
+
* @file `ork ui`'s process model (ADR-060): the command in the terminal only
|
|
232
|
+
* supervises; its server runs in a child, the launcher the user ran
|
|
233
|
+
* (`process.argv[1]` as given, so after an upgrade it is the new install's)
|
|
234
|
+
* with `ui --serve`. The port and the token go over the child's IPC channel.
|
|
235
|
+
* When the page asks for a restart, the child says so and exits, and the
|
|
236
|
+
* supervisor starts the launcher again on the same port with the same token,
|
|
237
|
+
* so the open tab stays signed in. There is only ever one child; Ctrl-C (or
|
|
238
|
+
* SIGTERM) stops it and then the supervisor, which exits with its code. The
|
|
239
|
+
* child may wait for an upgrade to finish first; a second Ctrl-C (or SIGTERM)
|
|
240
|
+
* kills it and exits at once.
|
|
241
|
+
*/
|
|
242
|
+
/**
|
|
243
|
+
* How long an exited child's channel may take to close before the supervisor
|
|
244
|
+
* decides without it; a child's channel closes with it, so this is only a
|
|
245
|
+
* backstop.
|
|
246
|
+
*/
|
|
247
|
+
const CHANNEL_CLOSE_MS = 2e3;
|
|
248
|
+
/** A shell's exit code for a process a signal stopped. */
|
|
249
|
+
const signalled = (signal) => 128 + constants.signals[signal];
|
|
250
|
+
/** Supervises the server until it stops; resolves the exit code. */
|
|
251
|
+
const supervise = (options) => new Promise((resolve) => {
|
|
252
|
+
const { token, say, sayErr } = options;
|
|
253
|
+
let port = options.port;
|
|
254
|
+
let stopping = false;
|
|
255
|
+
let ended = false;
|
|
256
|
+
let restarts = 0;
|
|
257
|
+
let current = null;
|
|
258
|
+
const end = (code) => {
|
|
259
|
+
if (ended) return;
|
|
260
|
+
ended = true;
|
|
261
|
+
resolve(code);
|
|
262
|
+
};
|
|
263
|
+
const start = () => {
|
|
264
|
+
let restarting = false;
|
|
265
|
+
let ready = false;
|
|
266
|
+
let exited = null;
|
|
267
|
+
let disconnected = false;
|
|
268
|
+
let backstop;
|
|
269
|
+
const child = options.spawnServer();
|
|
270
|
+
current = child;
|
|
271
|
+
const decide = () => {
|
|
272
|
+
clearTimeout(backstop);
|
|
273
|
+
if (exited === null || ended || current !== child) return;
|
|
274
|
+
const { code } = exited;
|
|
275
|
+
if (restarting && !stopping) {
|
|
276
|
+
restarts += 1;
|
|
277
|
+
say("Restarting Ork's UI from the installed `ork`…");
|
|
278
|
+
start();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (!ready && restarts > 0) sayErr(`Ork's UI did not start again (exit ${String(code)}). Start it with: ork ui --port ${String(port)}`);
|
|
282
|
+
else if (ready && code !== 0 && !stopping) sayErr(`Ork's UI stopped: its server exited with ${String(code)}.`);
|
|
283
|
+
if (ready) say("Ork's UI stopped.");
|
|
284
|
+
end(code ?? (stopping ? 0 : 1));
|
|
285
|
+
};
|
|
286
|
+
child.onMessage((raw) => {
|
|
287
|
+
const message = childMessage(raw);
|
|
288
|
+
if (message?.type === "ork-ui-hello") child.send({
|
|
289
|
+
type: "ork-ui-start",
|
|
290
|
+
port,
|
|
291
|
+
token
|
|
292
|
+
});
|
|
293
|
+
else if (message?.type === "ork-ui-ready") {
|
|
294
|
+
ready = true;
|
|
295
|
+
({port} = message);
|
|
296
|
+
options.onReady({
|
|
297
|
+
port,
|
|
298
|
+
version: message.version,
|
|
299
|
+
restarted: restarts > 0
|
|
300
|
+
});
|
|
301
|
+
} else if (message?.type === "ork-ui-failed") sayErr(message.error);
|
|
302
|
+
else if (message?.type === "ork-ui-restart") restarting = true;
|
|
303
|
+
});
|
|
304
|
+
child.onExit((code) => {
|
|
305
|
+
exited = { code };
|
|
306
|
+
if (disconnected) decide();
|
|
307
|
+
else backstop = setTimeout(decide, CHANNEL_CLOSE_MS);
|
|
308
|
+
});
|
|
309
|
+
child.onDisconnect(() => {
|
|
310
|
+
disconnected = true;
|
|
311
|
+
decide();
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
options.onStop((signal) => {
|
|
315
|
+
if (!stopping) {
|
|
316
|
+
stopping = true;
|
|
317
|
+
current?.kill(signal);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
current?.kill("SIGKILL");
|
|
321
|
+
say("Ork's UI stopped.");
|
|
322
|
+
end(signalled(signal));
|
|
323
|
+
});
|
|
324
|
+
start();
|
|
325
|
+
});
|
|
326
|
+
/**
|
|
327
|
+
* Starts `launcher ui --serve` with the same Node, on this terminal, with an
|
|
328
|
+
* IPC channel for the port and the token.
|
|
329
|
+
*/
|
|
330
|
+
const spawnServerFrom = (launcher) => () => {
|
|
331
|
+
const child = spawn(process.execPath, [
|
|
332
|
+
...process.execArgv,
|
|
333
|
+
launcher,
|
|
334
|
+
"ui",
|
|
335
|
+
"--serve"
|
|
336
|
+
], { stdio: [
|
|
337
|
+
"inherit",
|
|
338
|
+
"inherit",
|
|
339
|
+
"inherit",
|
|
340
|
+
"ipc"
|
|
341
|
+
] });
|
|
342
|
+
return {
|
|
343
|
+
send: (message) => {
|
|
344
|
+
if (child.connected) child.send(message);
|
|
345
|
+
},
|
|
346
|
+
kill: (signal) => {
|
|
347
|
+
child.kill(signal);
|
|
348
|
+
},
|
|
349
|
+
onMessage: (listener) => {
|
|
350
|
+
child.on("message", listener);
|
|
351
|
+
},
|
|
352
|
+
onExit: (listener) => {
|
|
353
|
+
let told = false;
|
|
354
|
+
const once = (code) => {
|
|
355
|
+
if (told) return;
|
|
356
|
+
told = true;
|
|
357
|
+
listener(code);
|
|
358
|
+
};
|
|
359
|
+
child.on("error", () => {
|
|
360
|
+
if (child.pid === void 0) once(1);
|
|
361
|
+
});
|
|
362
|
+
child.on("exit", once);
|
|
363
|
+
},
|
|
364
|
+
onDisconnect: (listener) => {
|
|
365
|
+
let told = false;
|
|
366
|
+
const once = () => {
|
|
367
|
+
if (told) return;
|
|
368
|
+
told = true;
|
|
369
|
+
listener();
|
|
370
|
+
};
|
|
371
|
+
child.on("error", () => {
|
|
372
|
+
if (child.pid === void 0) once();
|
|
373
|
+
});
|
|
374
|
+
child.on("disconnect", once);
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
};
|
|
378
|
+
/** Ctrl-C and SIGTERM, for {@link SuperviseOptions.onStop}. */
|
|
379
|
+
const onStopSignals = (stop) => {
|
|
380
|
+
for (const signal of ["SIGINT", "SIGTERM"]) process.on(signal, () => {
|
|
381
|
+
stop(signal);
|
|
382
|
+
});
|
|
383
|
+
};
|
|
384
|
+
// src/commands/ui.ts
|
|
385
|
+
/**
|
|
386
|
+
* @file `ork ui`: starts the local server (ADR-039) on loopback and opens it in
|
|
387
|
+
* the browser, signed in with the server's token; or, when one is already
|
|
388
|
+
* running (`~/.ork/ui.json`), opens that one. The command supervises the
|
|
389
|
+
* server in a child process, so the page can restart it onto a newly
|
|
390
|
+
* installed `ork` (ADR-060). The server runs nothing: runs it starts keep
|
|
391
|
+
* running when it stops, and the CLI works without it.
|
|
392
|
+
*/
|
|
393
|
+
/** The link that signs the browser in; the server swaps it for a cookie. */
|
|
394
|
+
const signInLink = (port, token) => `http://127.0.0.1:${String(port)}/?token=${token}`;
|
|
395
|
+
/** `--port`: 0 to 65535 (0 picks a free port); `null` when invalid. */
|
|
396
|
+
const portArg = (value) => {
|
|
397
|
+
if (value === void 0) return DEFAULT_PORT;
|
|
398
|
+
const port = Number(value);
|
|
399
|
+
return Number.isInteger(port) && port >= 0 && port <= 65535 ? port : null;
|
|
400
|
+
};
|
|
401
|
+
/** Opens the link, or prints it when there is no browser to open. */
|
|
402
|
+
const show = async (link, open) => {
|
|
403
|
+
if (!mayShowSignIn()) {
|
|
404
|
+
writeErr("A program or an agent in a run does not get the sign-in link, which acts as you. Run `ork ui` in your own terminal.");
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (!(open && process.stdout.isTTY && await openInBrowser(link))) write(`Open it with this link, which signs you in: ${link}`);
|
|
408
|
+
};
|
|
409
|
+
/** What `ork ui` says once its server serves, the first time or again. */
|
|
410
|
+
const ready = async ({ port, version, restarted }, token, open) => {
|
|
411
|
+
const url = `http://127.0.0.1:${String(port)}`;
|
|
412
|
+
if (restarted) {
|
|
413
|
+
write(`Ork's UI restarted: Ork ${version} at ${url}`);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
write(`Ork's UI is at ${url}`);
|
|
417
|
+
write(style("dim", "Runs keep running when it stops, and every ork command works without it. Ctrl-C stops it."));
|
|
418
|
+
await show(signInLink(port, token), open);
|
|
419
|
+
};
|
|
420
|
+
const ui = defineCommand({
|
|
421
|
+
meta: {
|
|
422
|
+
name: "ui",
|
|
423
|
+
description: "Start Ork's local UI and open it, or open the running one"
|
|
424
|
+
},
|
|
425
|
+
args: {
|
|
426
|
+
port: {
|
|
427
|
+
type: "string",
|
|
428
|
+
description: `The port to serve on (default ${String(DEFAULT_PORT)})`
|
|
429
|
+
},
|
|
430
|
+
open: {
|
|
431
|
+
type: "boolean",
|
|
432
|
+
description: "Open the browser (--no-open only prints the address)",
|
|
433
|
+
default: true
|
|
434
|
+
},
|
|
435
|
+
serve: {
|
|
436
|
+
type: "boolean",
|
|
437
|
+
description: "Only for `ork ui` itself: be the server it supervises",
|
|
438
|
+
default: false
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
run: async ({ args }) => {
|
|
442
|
+
const port = portArg(args.port);
|
|
443
|
+
if (port === null) {
|
|
444
|
+
writeErr(`--port must be a number from 0 to 65535, not \`${args.port ?? ""}\`.`);
|
|
445
|
+
process.exitCode = 1;
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (args.serve) {
|
|
449
|
+
await serveForSupervisor();
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const running = runningServer(uiFilePath());
|
|
453
|
+
if (running !== null) {
|
|
454
|
+
write(`Ork's UI is already running at http://127.0.0.1:${String(running.port)} (process ${String(running.pid)}).`);
|
|
455
|
+
await show(signInLink(running.port, running.token), args.open);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
const token = newToken();
|
|
459
|
+
process.exitCode = await supervise({
|
|
460
|
+
port,
|
|
461
|
+
token,
|
|
462
|
+
spawnServer: spawnServerFrom(process.argv[1] ?? ""),
|
|
463
|
+
onReady: (answer) => ready(answer, token, args.open),
|
|
464
|
+
onStop: onStopSignals,
|
|
465
|
+
say: (line) => {
|
|
466
|
+
write(style("dim", line));
|
|
467
|
+
},
|
|
468
|
+
sayErr: writeErr
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
export { ui };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as write, c as openStore, i as style, n as cost, o as writeErr, t as confirm } from "./output-
|
|
3
|
-
import {
|
|
1
|
+
import { Cn as runDirOf, c as usd, ht as liveStatus, xn as placeFor } from "./src-DHvWU4tX.mjs";
|
|
2
|
+
import { a as write, c as openStore, i as style, n as cost, o as writeErr, t as confirm } from "./output-B1V6ZJSx.mjs";
|
|
3
|
+
import { n as listOrkWorktrees, t as createdWorktrees } from "./list-C7UEgmtd.mjs";
|
|
4
4
|
import { n as usageReport, t as USAGE_DIMENSIONS } from "./report-DK4LGqwF.mjs";
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
5
|
+
import "./format-CmTlTOjT.mjs";
|
|
6
|
+
import { n as limitsHere } from "./limits-JCve2QpI.mjs";
|
|
7
7
|
import { existsSync, rmSync } from "node:fs";
|
|
8
|
+
import { defineCommand } from "citty";
|
|
8
9
|
// ../core/src/store/prune.ts
|
|
9
10
|
/**
|
|
10
11
|
* @file `ork prune` (ADR-046): which runs may lose their transcript, and doing
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as newRunId } from "./find-
|
|
3
|
-
import { a as write, c as openStore, i as style, o as writeErr, r as plain, t as confirm } from "./output-
|
|
4
|
-
import { i as runLimits, r as limitsRefusal } from "./limits-
|
|
5
|
-
import { c as toolExecutor, o as spawnTool, s as mcpCallExecutor } from "./src-
|
|
6
|
-
import { t as acceptRefusal } from "./in-a-run-
|
|
7
|
-
import { defineCommand } from "citty";
|
|
1
|
+
import { $n as timeoutMs, S as readProjectTriggers, T as watchingPid, Wn as CheckReportSchema, Xn as isToolNode, b as triggerStanding, bn as placeFiles, ht as liveStatus, ot as detachRun, v as triggersReport, w as takeWatchLock, x as declaredTriggers, xt as stillTheSameProcess, y as createWatcher } from "./src-DHvWU4tX.mjs";
|
|
2
|
+
import { n as newRunId } from "./find-BKW-uGey.mjs";
|
|
3
|
+
import { a as write, c as openStore, i as style, o as writeErr, r as plain, t as confirm } from "./output-B1V6ZJSx.mjs";
|
|
4
|
+
import { i as runLimits, r as limitsRefusal } from "./limits-JCve2QpI.mjs";
|
|
5
|
+
import { c as toolExecutor, o as spawnTool, s as mcpCallExecutor } from "./src-C38BPzit.mjs";
|
|
6
|
+
import { t as acceptRefusal } from "./in-a-run-BPXX09Bs.mjs";
|
|
8
7
|
import path from "node:path";
|
|
9
8
|
import { mkdirSync } from "node:fs";
|
|
10
9
|
import { homedir } from "node:os";
|
|
10
|
+
import { defineCommand } from "citty";
|
|
11
11
|
const NO_HUMAN = {
|
|
12
12
|
permit: () => Promise.resolve({
|
|
13
13
|
allow: false,
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Cn as runDirOf, En as inside, bn as placeFiles, xn as placeFor } from "./src-DHvWU4tX.mjs";
|
|
2
|
+
import { i as builtinWorkflowFile, r as builtinProblem, s as isBuiltinArg } from "./decide-B81wCZQg.mjs";
|
|
3
|
+
import { c as openStore, l as storePath, o as writeErr } from "./output-B1V6ZJSx.mjs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
6
|
+
// src/project/place.ts
|
|
7
|
+
/**
|
|
8
|
+
* @file Which project a folder belongs to, from the projects the store has
|
|
9
|
+
* registered (ADR-035): an attached project's files are in
|
|
10
|
+
* `~/.ork/projects/<id>/`, an in-repo one's in its `.ork/`.
|
|
11
|
+
*/
|
|
12
|
+
/** The registered projects; none before Ork has a store (never creates one). */
|
|
13
|
+
const registeredProjects = (home) => {
|
|
14
|
+
const file = storePath(home);
|
|
15
|
+
if (!existsSync(file)) return [];
|
|
16
|
+
const store = openStore(file);
|
|
17
|
+
try {
|
|
18
|
+
return store.projects();
|
|
19
|
+
} finally {
|
|
20
|
+
store.close();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
/** The project `dir` is in: registered, or the in-repo one at its git root. */
|
|
24
|
+
const projectAt = (dir, home) => placeFor(dir, registeredProjects(home));
|
|
25
|
+
/** A path to show: relative to `cwd` when inside it, else as it is. */
|
|
26
|
+
const fromCwd = (target, cwd) => inside(cwd, target) ? path.relative(cwd, target) || "." : target;
|
|
27
|
+
// src/workflows.ts
|
|
28
|
+
/**
|
|
29
|
+
* @file Finding workflow files: the project's workflows folder by default
|
|
30
|
+
* (`.ork/workflows/`, or an attached project's), directories expanded, a
|
|
31
|
+
* workflow given by name, and one of Ork's own by its `ork:` name, written
|
|
32
|
+
* into the project's run folder.
|
|
33
|
+
*/
|
|
34
|
+
const WORKFLOW_DIR = path.join(".ork", "workflows");
|
|
35
|
+
const WORKFLOW_SUFFIX = ".ork.yaml";
|
|
36
|
+
/**
|
|
37
|
+
* Expands the given paths to workflow files: a directory contributes its
|
|
38
|
+
* `*.ork.yaml` files, and a name one in the project's workflows folder
|
|
39
|
+
* (`workflows`, default `.ork/workflows/` under `cwd`). With no paths, uses
|
|
40
|
+
* that folder.
|
|
41
|
+
*/
|
|
42
|
+
const findWorkflowFiles = (paths, cwd, workflows = path.join(cwd, WORKFLOW_DIR)) => {
|
|
43
|
+
return (paths.length > 0 ? paths : [fromCwd(workflows, cwd)]).flatMap((target) => {
|
|
44
|
+
const absolute = path.resolve(cwd, target);
|
|
45
|
+
if (existsSync(absolute) && statSync(absolute).isDirectory()) return readdirSync(absolute).filter((name) => name.endsWith(WORKFLOW_SUFFIX)).toSorted().map((name) => path.join(target, name));
|
|
46
|
+
return [resolveWorkflowArg(target, cwd, workflows)];
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* A workflow given by path, by name for one in the project's workflows folder
|
|
51
|
+
* (`hello` is `.ork/workflows/hello.ork.yaml` when no file `hello` exists), or
|
|
52
|
+
* by its `ork:` name for one of Ork's own. An unknown `ork:` name comes back as
|
|
53
|
+
* it is: commands refuse it first (`unknownBuiltin`).
|
|
54
|
+
*/
|
|
55
|
+
const resolveWorkflowArg = (arg, cwd, workflows = path.join(cwd, WORKFLOW_DIR)) => {
|
|
56
|
+
if (isBuiltinArg(arg)) {
|
|
57
|
+
const file = builtinWorkflowFile(projectAt(cwd), arg);
|
|
58
|
+
return file === void 0 ? arg : fromCwd(file, cwd);
|
|
59
|
+
}
|
|
60
|
+
if (existsSync(path.resolve(cwd, arg)) || arg.endsWith(".ork.yaml")) return arg;
|
|
61
|
+
const named = path.join(workflows, `${arg}${WORKFLOW_SUFFIX}`);
|
|
62
|
+
return existsSync(named) ? fromCwd(named, cwd) : arg;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Refuses an `ork:` name that isn't one of Ork's workflows, saying which are,
|
|
66
|
+
* or whose file's place in the project is a link; true when it did, so the
|
|
67
|
+
* command stops.
|
|
68
|
+
*/
|
|
69
|
+
const refusedBuiltin = (args, cwd) => {
|
|
70
|
+
const problems = args.filter((arg) => isBuiltinArg(arg)).flatMap((arg) => builtinProblem(projectAt(cwd), arg) ?? []);
|
|
71
|
+
for (const problem of problems) writeErr(problem);
|
|
72
|
+
if (problems.length > 0) process.exitCode = 1;
|
|
73
|
+
return problems.length > 0;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* A command's workflow argument, by path or by name in the project `cwd` is in,
|
|
77
|
+
* with the project the file belongs to and a run's folder there.
|
|
78
|
+
*/
|
|
79
|
+
const workflowArg = (arg, cwd) => {
|
|
80
|
+
const file = resolveWorkflowArg(arg, cwd, placeFiles(projectAt(cwd)).workflows);
|
|
81
|
+
const place = projectAt(path.dirname(path.resolve(cwd, file)));
|
|
82
|
+
return {
|
|
83
|
+
file,
|
|
84
|
+
place,
|
|
85
|
+
runDir: (runId) => runDirOf(place, runId)
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
export { resolveWorkflowArg as a, registeredProjects as c, refusedBuiltin as i, WORKFLOW_SUFFIX as n, workflowArg as o, findWorkflowFiles as r, projectAt as s, WORKFLOW_DIR as t };
|