unfazed 0.9.0 → 0.10.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/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0
4
+
5
+ eve sandboxes on unfazed boxes: an eve app on unfazed gets a real sandbox (a
6
+ browser through `@agent-browser/eve`, `bash`, files) backed by a box, with no
7
+ stored secret.
8
+
9
+ **Added**
10
+
11
+ - `unfazed/eve-sandbox`: `UnfazedSandbox`, an eve sandbox provider
12
+ (`defineSandboxProvider`, eve 0.90 or newer, an optional peer).
13
+ `UnfazedSandbox.environment({ box: "<alias>" })` in `agent/sandbox.ts`.
14
+ - Every eve session gets its own Unix user, home and workspace on the box,
15
+ kept across restarts and redeploys until the session ends.
16
+ - The app authenticates with its workload identity token (audience: the
17
+ box's URL).
18
+ - Keyed MCP tool sessions keep their sandbox between calls.
19
+ - Build-time workspace and skill files travel in the eve artifact; a
20
+ `prepare` callback that runs commands at build time is refused (install
21
+ tools on the box instead).
22
+ - `npx unfazed eve-sandbox setup|allow|deny|status|serve`, run as root inside
23
+ a box.
24
+ - `setup` installs the sandbox daemon on the box's `:3000`, agent-browser
25
+ and Chromium, and a boot hook that starts the daemon on every boot.
26
+ - Without `--allow`, every app of the box's own owner may use the box.
27
+ - Idle sessions lose their processes after 20 minutes (`--idle-minutes`);
28
+ sessions nobody used for 14 days are deleted (`--retention-days`).
29
+ - `--proxy <url>` routes the browsers through a proxy.
30
+
3
31
  ## 0.9.0
4
32
 
5
33
  Workload identity: an app gets OIDC tokens from the platform with its own
package/README.md CHANGED
@@ -681,6 +681,63 @@ Errors are `UnfazedOIDCError`, with the platform's `error` as `code` and its
681
681
  | `not_in_app` | | `UNFZ_STATE_TOKEN` is not set |
682
682
  | `unreachable` | | network failures outlasted the retries |
683
683
 
684
+ ## eve sandboxes on a box: `unfazed/eve-sandbox`
685
+
686
+ [eve](https://eve.dev) runs browsers, `bash` and file tools only inside a
687
+ sandbox, and an unfazed service has no Docker. `unfazed/eve-sandbox` backs
688
+ eve sandboxes with an unfazed **box** (a persistent Linux VM): every eve
689
+ session gets its own Unix user, home and workspace there, which survive box
690
+ restarts and app redeploys until the session ends.
691
+
692
+ **1. Turn a box into a sandbox host.** Inside the box, as root (`ssh <box>@unfz.to`):
693
+
694
+ ```bash
695
+ npx -y unfazed@latest eve-sandbox setup
696
+ ```
697
+
698
+ This installs:
699
+
700
+ - the sandbox daemon on the box's `:3000`, which serves the box's URL;
701
+ - agent-browser and Chromium for every session;
702
+ - a boot hook that starts the daemon on every boot.
703
+
704
+ It then prints the line for your app. Who may use the box:
705
+
706
+ - Without `--allow`, every app of the box's own owner.
707
+ - `--allow <app-id>,owner:<id>` names apps explicitly, and `npx unfazed eve-sandbox allow|deny <id>` changes the list later.
708
+ - A refused app's id is in the daemon's 403 answer.
709
+
710
+ Other options:
711
+
712
+ - `--proxy <url>` routes the browsers through a proxy, for sites that block datacenter IPs.
713
+ - `--idle-minutes 20` sets how long an idle session keeps its processes.
714
+ - `--retention-days 14` sets when an unused session is deleted.
715
+ - `npx unfazed eve-sandbox status` shows the daemon, the allow list and the sessions.
716
+
717
+ **2. Point the eve app at it** (`eve` 0.90 or newer, an optional peer):
718
+
719
+ ```ts
720
+ // agent/sandbox.ts
721
+ import { defineSandbox } from "eve/sandbox";
722
+ import { UnfazedSandbox } from "unfazed/eve-sandbox";
723
+
724
+ export const environment = UnfazedSandbox.environment({ box: "my-sandbox-box" });
725
+ export default defineSandbox(() => environment.open());
726
+ ```
727
+
728
+ With `@agent-browser/eve` mounted (`agent/extensions/browser.ts`), the
729
+ agent's `browser__*` tools run in the box. The agent-browser that `setup`
730
+ installs is `0.41.1`. Pass `--agent-browser <version>` to match another
731
+ `@agent-browser/eve` minor.
732
+
733
+ How it behaves:
734
+
735
+ - **No stored secret.** The app authenticates with its workload identity token, audience the box's URL (see "Workload identity" above). The box checks the issuer, audience and allow list.
736
+ - **Sessions are users.** A session's commands (`bash -lc`, streamed) and file operations run as its own Unix user, so sessions cannot read each other's files or browser profiles.
737
+ - **Keyed MCP tool sessions** (`dev.eve/tool-sessions`) keep their sandbox between calls. A sandbox nobody used for the retention period is deleted.
738
+ - **Templates.** The agent's workspace and skill files are captured at `eve build` and seeded into each new session. A sandbox `prepare` callback that runs commands is refused at build time: install tools on the box instead (it is yours, with root).
739
+ - **Placement.** Put the box near the app (the same server or region): every sandbox call is an HTTPS round trip to the box.
740
+
684
741
  ## Durable workflows: `unfazed/workflow`
685
742
 
686
743
  [Workflow SDK](https://workflow-sdk.dev) runs durable functions: a
@@ -0,0 +1,3 @@
1
+ /** The agent-browser release `@agent-browser/eve` 0.41 drives; match the extension's minor. */
2
+ export declare const DEFAULT_AGENT_BROWSER = "0.41.1";
3
+ export declare function runEveSandbox(argv: readonly string[]): Promise<number>;
@@ -0,0 +1,268 @@
1
+ /// <reference types="node" />
2
+ /**
3
+ * `npx unfazed eve-sandbox <command>`, run as root inside an unfazed box:
4
+ * turns the box into a sandbox host for eve apps (`unfazed/eve-sandbox`).
5
+ *
6
+ * setup [--allow <app-id|owner:id>,…] [--no-browser] [--agent-browser <version>]
7
+ * [--proxy <url>] [--idle-minutes <n>] [--retention-days <n>]
8
+ * allow <app-id|owner:id> add to the allow list and restart the daemon
9
+ * deny <app-id|owner:id> remove from it and restart the daemon
10
+ * status the daemon's health, allow list and sessions
11
+ * serve run the daemon in the foreground
12
+ *
13
+ * `setup` copies the daemon to /data/eve-sandbox, writes its config and a box
14
+ * boot hook (/data/boot.d/50-eve-sandbox, run on every boot by the box's
15
+ * init), installs agent-browser and Chromium for every session, and (re)starts
16
+ * the daemon on :3000, which unfazed publishes at the box's URL. Without
17
+ * --allow, every app of the box's own owner may use it.
18
+ */
19
+ import { execFileSync, spawn } from "node:child_process";
20
+ import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, symlinkSync, writeFileSync, } from "node:fs";
21
+ import { dirname, join } from "node:path";
22
+ import { fileURLToPath } from "node:url";
23
+ import { parseArgs } from "node:util";
24
+ /** The agent-browser release `@agent-browser/eve` 0.41 drives; match the extension's minor. */
25
+ export const DEFAULT_AGENT_BROWSER = "0.41.1";
26
+ const ROOT = "/data/eve-sandbox";
27
+ const LIB = `${ROOT}/lib`;
28
+ const CONFIG = `${ROOT}/config.json`;
29
+ const HOOK = "/data/boot.d/50-eve-sandbox";
30
+ const BROWSERS = "/opt/agent-browser/browsers";
31
+ const HELP = `unfazed eve-sandbox — run eve sandboxes on this unfazed box (as root, inside the box)
32
+
33
+ Usage:
34
+ npx unfazed eve-sandbox setup [--allow <app-id|owner:id>,…] [--no-browser]
35
+ [--agent-browser <version>] [--proxy <url>]
36
+ [--idle-minutes <n>] [--retention-days <n>]
37
+ npx unfazed eve-sandbox allow <app-id|owner:id>
38
+ npx unfazed eve-sandbox deny <app-id|owner:id>
39
+ npx unfazed eve-sandbox status
40
+ npx unfazed eve-sandbox serve
41
+
42
+ setup installs the daemon (on :3000, the box's URL), agent-browser ${DEFAULT_AGENT_BROWSER}
43
+ and Chromium, and a boot hook that starts the daemon on every boot. Without
44
+ --allow, every app of this box's owner may use the box. An eve app then
45
+ points its agent/sandbox.ts at it with UnfazedSandbox (unfazed/eve-sandbox):
46
+
47
+ export const environment = UnfazedSandbox.environment({ box: "<this box's alias>" });
48
+ export default defineSandbox(() => environment.open());
49
+ `;
50
+ function readConfig() {
51
+ if (!existsSync(CONFIG))
52
+ return { allow: [] };
53
+ const parsed = JSON.parse(readFileSync(CONFIG, "utf8"));
54
+ return { ...parsed, allow: Array.isArray(parsed.allow) ? parsed.allow : [] };
55
+ }
56
+ function writeConfig(config) {
57
+ const temp = `${CONFIG}.tmp`;
58
+ writeFileSync(temp, JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
59
+ renameSync(temp, CONFIG);
60
+ }
61
+ function sh(file, args, options = {}) {
62
+ return execFileSync(file, args, { encoding: "utf8", stdio: options.quiet ? ["ignore", "pipe", "pipe"] : ["ignore", "pipe", "inherit"] });
63
+ }
64
+ function requireBox() {
65
+ if (process.getuid?.() !== 0)
66
+ throw new Error("unfazed eve-sandbox: run this as root inside the box");
67
+ if (!existsSync("/sbin/unfazed-box-init") || !process.env["UNFZ_PUBLIC_URL"]) {
68
+ throw new Error("unfazed eve-sandbox: this is not an unfazed box (no /sbin/unfazed-box-init or UNFZ_PUBLIC_URL)");
69
+ }
70
+ }
71
+ /** The box's own owner, from its workload identity token: the default allow entry. */
72
+ async function ownOwner() {
73
+ const state = process.env["UNFZ_STATE_TOKEN"];
74
+ const audience = process.env["UNFZ_PUBLIC_URL"];
75
+ if (!state)
76
+ throw new Error("unfazed eve-sandbox: UNFZ_STATE_TOKEN is not set; pass --allow");
77
+ const res = await fetch("https://unfazed.dev/oidc/token", {
78
+ method: "POST",
79
+ headers: { authorization: `Bearer ${state}`, "content-type": "application/json", "user-agent": "unfazed-eve-sandbox-setup" },
80
+ body: JSON.stringify({ audience }),
81
+ });
82
+ if (!res.ok)
83
+ throw new Error(`unfazed eve-sandbox: could not read the box's owner (${res.status}); pass --allow`);
84
+ const { token } = (await res.json());
85
+ const claims = JSON.parse(Buffer.from(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8"));
86
+ if (!claims.owner_id)
87
+ throw new Error("unfazed eve-sandbox: the box's token names no owner; pass --allow");
88
+ return `owner:${claims.owner_id}`;
89
+ }
90
+ function installDaemon() {
91
+ mkdirSync(LIB, { recursive: true });
92
+ chmodSync(ROOT, 0o755);
93
+ chmodSync(LIB, 0o755);
94
+ const source = join(dirname(fileURLToPath(import.meta.url)), "..", "eve-sandbox");
95
+ for (const file of ["daemon.js", "protocol.js"]) {
96
+ copyFileSync(join(source, file), join(LIB, file));
97
+ chmodSync(join(LIB, file), 0o644);
98
+ }
99
+ mkdirSync(dirname(HOOK), { recursive: true });
100
+ writeFileSync(HOOK, [
101
+ "#!/bin/sh",
102
+ "# eve sandbox daemon (unfazed/eve-sandbox) on :3000, restarted if it exits.",
103
+ `cd ${ROOT} || exit 1`,
104
+ "while true; do",
105
+ ` ${existsSync("/usr/local/bin/node") ? "/usr/local/bin/node" : process.execPath} ${LIB}/daemon.js serve --config ${CONFIG} >>${ROOT}/daemon.log 2>&1`,
106
+ " sleep 2",
107
+ "done",
108
+ "",
109
+ ].join("\n"), { mode: 0o755 });
110
+ }
111
+ /** Installs agent-browser and Chromium once for every session; returns the Chromium binary. */
112
+ function installBrowser(version) {
113
+ process.stderr.write(`installing agent-browser@${version} and Chromium (a few minutes the first time)…\n`);
114
+ const env = { ...process.env, DEBIAN_FRONTEND: "noninteractive" };
115
+ execFileSync("npm", ["install", "-g", "--silent", `agent-browser@${version}`], { stdio: "inherit", env });
116
+ // agent-browser keeps browsers in the installing user's home; sessions are
117
+ // other users. Root's browser directory becomes the shared one, so a
118
+ // browser already there is found and not downloaded again.
119
+ mkdirSync(BROWSERS, { recursive: true });
120
+ const own = join(process.env["HOME"] ?? "/root", ".agent-browser", "browsers");
121
+ mkdirSync(dirname(own), { recursive: true });
122
+ if (existsSync(own) && !lstatSync(own).isSymbolicLink()) {
123
+ for (const entry of readdirSync(own)) {
124
+ if (!existsSync(join(BROWSERS, entry)))
125
+ renameSync(join(own, entry), join(BROWSERS, entry));
126
+ }
127
+ rmSync(own, { recursive: true, force: true });
128
+ }
129
+ if (!existsSync(own))
130
+ symlinkSync(BROWSERS, own);
131
+ execFileSync("agent-browser", ["install", "--with-deps"], { stdio: "inherit", env });
132
+ sh("chmod", ["-R", "a+rX", "/opt/agent-browser"]);
133
+ const latest = readdirSync(BROWSERS)
134
+ .filter((e) => e.startsWith("chrome-"))
135
+ .sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
136
+ .at(-1);
137
+ const binary = latest ? join(BROWSERS, latest, "chrome") : "";
138
+ if (!binary || !existsSync(binary))
139
+ throw new Error("unfazed eve-sandbox: Chromium was not found after the install");
140
+ return binary;
141
+ }
142
+ function restartDaemon() {
143
+ // The boot hook's loop first (or it starts the old daemon again), then any
144
+ // daemon, wherever an earlier setup put it.
145
+ for (const pattern of ["[b]oot\\.d/50-eve-sandbox", "[e]ve-sandbox[^ ]*daemon[^ ]*\\.js serve"]) {
146
+ try {
147
+ sh("pkill", ["-f", pattern], { quiet: true });
148
+ }
149
+ catch {
150
+ // none running
151
+ }
152
+ }
153
+ execFileSync("sleep", ["1"]);
154
+ const child = spawn("setsid", [HOOK], { detached: true, stdio: "ignore" });
155
+ child.unref();
156
+ }
157
+ async function health() {
158
+ for (let i = 0; i < 20; i++) {
159
+ try {
160
+ const res = await fetch("http://127.0.0.1:3000/v1/health", { signal: AbortSignal.timeout(2000) });
161
+ if (res.ok)
162
+ return (await res.json());
163
+ }
164
+ catch {
165
+ // not up yet
166
+ }
167
+ await new Promise((resolve) => setTimeout(resolve, 500));
168
+ }
169
+ return { ok: false };
170
+ }
171
+ function sessionCount() {
172
+ return sh("getent", ["passwd"], { quiet: true })
173
+ .split("\n")
174
+ .filter((line) => /^eve[0-9a-f]{12}:/.test(line)).length;
175
+ }
176
+ export async function runEveSandbox(argv) {
177
+ const { values, positionals } = parseArgs({
178
+ args: [...argv],
179
+ allowPositionals: true,
180
+ options: {
181
+ allow: { type: "string" },
182
+ "no-browser": { type: "boolean" },
183
+ "agent-browser": { type: "string" },
184
+ proxy: { type: "string" },
185
+ "idle-minutes": { type: "string" },
186
+ "retention-days": { type: "string" },
187
+ config: { type: "string" },
188
+ help: { type: "boolean", short: "h" },
189
+ },
190
+ });
191
+ const [command, arg] = positionals;
192
+ if (values.help || command === undefined || command === "help") {
193
+ process.stdout.write(HELP);
194
+ return command === undefined ? 1 : 0;
195
+ }
196
+ if (command === "serve") {
197
+ const daemon = await import("../eve-sandbox/daemon.js");
198
+ const config = daemon.loadConfig(values.config ?? CONFIG);
199
+ await daemon.createDaemon(config).listen();
200
+ process.stdout.write(`eve sandbox daemon on :${config.port} for ${config.audience}\n`);
201
+ await new Promise(() => { });
202
+ }
203
+ requireBox();
204
+ const url = process.env["UNFZ_PUBLIC_URL"];
205
+ switch (command) {
206
+ case "setup": {
207
+ const config = readConfig();
208
+ const allow = values.allow
209
+ ? values.allow.split(",").map((s) => s.trim()).filter(Boolean)
210
+ : config.allow.length > 0
211
+ ? []
212
+ : [await ownOwner()];
213
+ config.allow = [...new Set([...config.allow, ...allow])];
214
+ const number = (flag, name) => {
215
+ if (flag === undefined)
216
+ return undefined;
217
+ const n = Number(flag);
218
+ if (!Number.isInteger(n) || n < 0)
219
+ throw new Error(`unfazed eve-sandbox: --${name} must be a whole number`);
220
+ return n;
221
+ };
222
+ config.idleMinutes = number(values["idle-minutes"], "idle-minutes") ?? config.idleMinutes ?? 20;
223
+ config.retentionDays = number(values["retention-days"], "retention-days") ?? config.retentionDays ?? 14;
224
+ config.env = { ...(config.env ?? {}) };
225
+ if (values.proxy)
226
+ config.env["AGENT_BROWSER_PROXY"] = values.proxy;
227
+ installDaemon();
228
+ if (!values["no-browser"])
229
+ config.env["AGENT_BROWSER_EXECUTABLE_PATH"] = installBrowser(values["agent-browser"] ?? DEFAULT_AGENT_BROWSER);
230
+ writeConfig(config);
231
+ restartDaemon();
232
+ const up = await health();
233
+ if (!up.ok)
234
+ throw new Error(`unfazed eve-sandbox: the daemon did not come up; see ${ROOT}/daemon.log`);
235
+ const alias = new URL(url).hostname.split(".")[0];
236
+ process.stdout.write(`eve sandbox ready at ${url} (daemon ${up.version}); allowed: ${config.allow.join(", ")}\n\n` +
237
+ `In the eve app:\n export const environment = UnfazedSandbox.environment({ box: "${alias}" });\n`);
238
+ return 0;
239
+ }
240
+ case "allow":
241
+ case "deny": {
242
+ if (!arg)
243
+ throw new Error(`unfazed eve-sandbox: ${command} needs an app id or owner:<id>`);
244
+ const config = readConfig();
245
+ config.allow = command === "allow" ? [...new Set([...config.allow, arg])] : config.allow.filter((entry) => entry !== arg);
246
+ if (config.allow.length === 0)
247
+ throw new Error("unfazed eve-sandbox: that would allow nothing; allow another app first");
248
+ writeConfig(config);
249
+ restartDaemon();
250
+ const up = await health();
251
+ process.stdout.write(`${up.ok ? "restarted" : "NOT restarted (see daemon.log)"}; allowed: ${config.allow.join(", ")}\n`);
252
+ return up.ok ? 0 : 1;
253
+ }
254
+ case "status": {
255
+ const config = readConfig();
256
+ const up = await health();
257
+ process.stdout.write(`${url}: ${up.ok ? `up (daemon ${up.version})` : "DOWN"}\n` +
258
+ `allowed: ${config.allow.join(", ") || "(none)"}\n` +
259
+ `sessions: ${sessionCount()}\n` +
260
+ `browser: ${config.env?.["AGENT_BROWSER_EXECUTABLE_PATH"] ?? "(not installed)"}\n`);
261
+ return up.ok ? 0 : 1;
262
+ }
263
+ default:
264
+ process.stderr.write(HELP);
265
+ return 1;
266
+ }
267
+ }
268
+ //# sourceMappingURL=eve-sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eve-sandbox.js","sourceRoot":"","sources":["../../src/cli/eve-sandbox.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference types=\"node\" />\n/**\n * `npx unfazed eve-sandbox <command>`, run as root inside an unfazed box:\n * turns the box into a sandbox host for eve apps (`unfazed/eve-sandbox`).\n *\n * setup [--allow <app-id|owner:id>,…] [--no-browser] [--agent-browser <version>]\n * [--proxy <url>] [--idle-minutes <n>] [--retention-days <n>]\n * allow <app-id|owner:id> add to the allow list and restart the daemon\n * deny <app-id|owner:id> remove from it and restart the daemon\n * status the daemon's health, allow list and sessions\n * serve run the daemon in the foreground\n *\n * `setup` copies the daemon to /data/eve-sandbox, writes its config and a box\n * boot hook (/data/boot.d/50-eve-sandbox, run on every boot by the box's\n * init), installs agent-browser and Chromium for every session, and (re)starts\n * the daemon on :3000, which unfazed publishes at the box's URL. Without\n * --allow, every app of the box's own owner may use it.\n */\nimport { execFileSync, spawn } from \"node:child_process\";\nimport {\n chmodSync,\n copyFileSync,\n existsSync,\n lstatSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n renameSync,\n rmSync,\n symlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { parseArgs } from \"node:util\";\n\n/** The agent-browser release `@agent-browser/eve` 0.41 drives; match the extension's minor. */\nexport const DEFAULT_AGENT_BROWSER = \"0.41.1\";\n\nconst ROOT = \"/data/eve-sandbox\";\nconst LIB = `${ROOT}/lib`;\nconst CONFIG = `${ROOT}/config.json`;\nconst HOOK = \"/data/boot.d/50-eve-sandbox\";\nconst BROWSERS = \"/opt/agent-browser/browsers\";\n\nconst HELP = `unfazed eve-sandbox — run eve sandboxes on this unfazed box (as root, inside the box)\n\nUsage:\n npx unfazed eve-sandbox setup [--allow <app-id|owner:id>,…] [--no-browser]\n [--agent-browser <version>] [--proxy <url>]\n [--idle-minutes <n>] [--retention-days <n>]\n npx unfazed eve-sandbox allow <app-id|owner:id>\n npx unfazed eve-sandbox deny <app-id|owner:id>\n npx unfazed eve-sandbox status\n npx unfazed eve-sandbox serve\n\nsetup installs the daemon (on :3000, the box's URL), agent-browser ${DEFAULT_AGENT_BROWSER}\nand Chromium, and a boot hook that starts the daemon on every boot. Without\n--allow, every app of this box's owner may use the box. An eve app then\npoints its agent/sandbox.ts at it with UnfazedSandbox (unfazed/eve-sandbox):\n\n export const environment = UnfazedSandbox.environment({ box: \"<this box's alias>\" });\n export default defineSandbox(() => environment.open());\n`;\n\ninterface Config {\n allow: string[];\n idleMinutes?: number;\n retentionDays?: number;\n env?: Record<string, string>;\n [key: string]: unknown;\n}\n\nfunction readConfig(): Config {\n if (!existsSync(CONFIG)) return { allow: [] };\n const parsed = JSON.parse(readFileSync(CONFIG, \"utf8\")) as Config;\n return { ...parsed, allow: Array.isArray(parsed.allow) ? parsed.allow : [] };\n}\n\nfunction writeConfig(config: Config): void {\n const temp = `${CONFIG}.tmp`;\n writeFileSync(temp, JSON.stringify(config, null, 2) + \"\\n\", { mode: 0o600 });\n renameSync(temp, CONFIG);\n}\n\nfunction sh(file: string, args: readonly string[], options: { quiet?: boolean } = {}): string {\n return execFileSync(file, args, { encoding: \"utf8\", stdio: options.quiet ? [\"ignore\", \"pipe\", \"pipe\"] : [\"ignore\", \"pipe\", \"inherit\"] });\n}\n\nfunction requireBox(): void {\n if (process.getuid?.() !== 0) throw new Error(\"unfazed eve-sandbox: run this as root inside the box\");\n if (!existsSync(\"/sbin/unfazed-box-init\") || !process.env[\"UNFZ_PUBLIC_URL\"]) {\n throw new Error(\"unfazed eve-sandbox: this is not an unfazed box (no /sbin/unfazed-box-init or UNFZ_PUBLIC_URL)\");\n }\n}\n\n/** The box's own owner, from its workload identity token: the default allow entry. */\nasync function ownOwner(): Promise<string> {\n const state = process.env[\"UNFZ_STATE_TOKEN\"];\n const audience = process.env[\"UNFZ_PUBLIC_URL\"]!;\n if (!state) throw new Error(\"unfazed eve-sandbox: UNFZ_STATE_TOKEN is not set; pass --allow\");\n const res = await fetch(\"https://unfazed.dev/oidc/token\", {\n method: \"POST\",\n headers: { authorization: `Bearer ${state}`, \"content-type\": \"application/json\", \"user-agent\": \"unfazed-eve-sandbox-setup\" },\n body: JSON.stringify({ audience }),\n });\n if (!res.ok) throw new Error(`unfazed eve-sandbox: could not read the box's owner (${res.status}); pass --allow`);\n const { token } = (await res.json()) as { token: string };\n const claims = JSON.parse(Buffer.from(token.split(\".\")[1]!.replace(/-/g, \"+\").replace(/_/g, \"/\"), \"base64\").toString(\"utf8\")) as {\n owner_id?: string;\n };\n if (!claims.owner_id) throw new Error(\"unfazed eve-sandbox: the box's token names no owner; pass --allow\");\n return `owner:${claims.owner_id}`;\n}\n\nfunction installDaemon(): void {\n mkdirSync(LIB, { recursive: true });\n chmodSync(ROOT, 0o755);\n chmodSync(LIB, 0o755);\n const source = join(dirname(fileURLToPath(import.meta.url)), \"..\", \"eve-sandbox\");\n for (const file of [\"daemon.js\", \"protocol.js\"]) {\n copyFileSync(join(source, file), join(LIB, file));\n chmodSync(join(LIB, file), 0o644);\n }\n mkdirSync(dirname(HOOK), { recursive: true });\n writeFileSync(\n HOOK,\n [\n \"#!/bin/sh\",\n \"# eve sandbox daemon (unfazed/eve-sandbox) on :3000, restarted if it exits.\",\n `cd ${ROOT} || exit 1`,\n \"while true; do\",\n ` ${existsSync(\"/usr/local/bin/node\") ? \"/usr/local/bin/node\" : process.execPath} ${LIB}/daemon.js serve --config ${CONFIG} >>${ROOT}/daemon.log 2>&1`,\n \" sleep 2\",\n \"done\",\n \"\",\n ].join(\"\\n\"),\n { mode: 0o755 },\n );\n}\n\n/** Installs agent-browser and Chromium once for every session; returns the Chromium binary. */\nfunction installBrowser(version: string): string {\n process.stderr.write(`installing agent-browser@${version} and Chromium (a few minutes the first time)…\\n`);\n const env = { ...process.env, DEBIAN_FRONTEND: \"noninteractive\" };\n execFileSync(\"npm\", [\"install\", \"-g\", \"--silent\", `agent-browser@${version}`], { stdio: \"inherit\", env });\n // agent-browser keeps browsers in the installing user's home; sessions are\n // other users. Root's browser directory becomes the shared one, so a\n // browser already there is found and not downloaded again.\n mkdirSync(BROWSERS, { recursive: true });\n const own = join(process.env[\"HOME\"] ?? \"/root\", \".agent-browser\", \"browsers\");\n mkdirSync(dirname(own), { recursive: true });\n if (existsSync(own) && !lstatSync(own).isSymbolicLink()) {\n for (const entry of readdirSync(own)) {\n if (!existsSync(join(BROWSERS, entry))) renameSync(join(own, entry), join(BROWSERS, entry));\n }\n rmSync(own, { recursive: true, force: true });\n }\n if (!existsSync(own)) symlinkSync(BROWSERS, own);\n execFileSync(\"agent-browser\", [\"install\", \"--with-deps\"], { stdio: \"inherit\", env });\n sh(\"chmod\", [\"-R\", \"a+rX\", \"/opt/agent-browser\"]);\n const latest = readdirSync(BROWSERS)\n .filter((e) => e.startsWith(\"chrome-\"))\n .sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))\n .at(-1);\n const binary = latest ? join(BROWSERS, latest, \"chrome\") : \"\";\n if (!binary || !existsSync(binary)) throw new Error(\"unfazed eve-sandbox: Chromium was not found after the install\");\n return binary;\n}\n\nfunction restartDaemon(): void {\n // The boot hook's loop first (or it starts the old daemon again), then any\n // daemon, wherever an earlier setup put it.\n for (const pattern of [\"[b]oot\\\\.d/50-eve-sandbox\", \"[e]ve-sandbox[^ ]*daemon[^ ]*\\\\.js serve\"]) {\n try {\n sh(\"pkill\", [\"-f\", pattern], { quiet: true });\n } catch {\n // none running\n }\n }\n execFileSync(\"sleep\", [\"1\"]);\n const child = spawn(\"setsid\", [HOOK], { detached: true, stdio: \"ignore\" });\n child.unref();\n}\n\nasync function health(): Promise<{ ok: boolean; version?: string }> {\n for (let i = 0; i < 20; i++) {\n try {\n const res = await fetch(\"http://127.0.0.1:3000/v1/health\", { signal: AbortSignal.timeout(2000) });\n if (res.ok) return (await res.json()) as { ok: boolean; version?: string };\n } catch {\n // not up yet\n }\n await new Promise((resolve) => setTimeout(resolve, 500));\n }\n return { ok: false };\n}\n\nfunction sessionCount(): number {\n return sh(\"getent\", [\"passwd\"], { quiet: true })\n .split(\"\\n\")\n .filter((line) => /^eve[0-9a-f]{12}:/.test(line)).length;\n}\n\nexport async function runEveSandbox(argv: readonly string[]): Promise<number> {\n const { values, positionals } = parseArgs({\n args: [...argv],\n allowPositionals: true,\n options: {\n allow: { type: \"string\" },\n \"no-browser\": { type: \"boolean\" },\n \"agent-browser\": { type: \"string\" },\n proxy: { type: \"string\" },\n \"idle-minutes\": { type: \"string\" },\n \"retention-days\": { type: \"string\" },\n config: { type: \"string\" },\n help: { type: \"boolean\", short: \"h\" },\n },\n });\n const [command, arg] = positionals;\n if (values.help || command === undefined || command === \"help\") {\n process.stdout.write(HELP);\n return command === undefined ? 1 : 0;\n }\n if (command === \"serve\") {\n const daemon = await import(\"../eve-sandbox/daemon.js\");\n const config = daemon.loadConfig(values.config ?? CONFIG);\n await daemon.createDaemon(config).listen();\n process.stdout.write(`eve sandbox daemon on :${config.port} for ${config.audience}\\n`);\n await new Promise(() => {});\n }\n requireBox();\n const url = process.env[\"UNFZ_PUBLIC_URL\"]!;\n switch (command) {\n case \"setup\": {\n const config = readConfig();\n const allow = values.allow\n ? values.allow.split(\",\").map((s) => s.trim()).filter(Boolean)\n : config.allow.length > 0\n ? []\n : [await ownOwner()];\n config.allow = [...new Set([...config.allow, ...allow])];\n const number = (flag: string | undefined, name: string) => {\n if (flag === undefined) return undefined;\n const n = Number(flag);\n if (!Number.isInteger(n) || n < 0) throw new Error(`unfazed eve-sandbox: --${name} must be a whole number`);\n return n;\n };\n config.idleMinutes = number(values[\"idle-minutes\"], \"idle-minutes\") ?? config.idleMinutes ?? 20;\n config.retentionDays = number(values[\"retention-days\"], \"retention-days\") ?? config.retentionDays ?? 14;\n config.env = { ...(config.env ?? {}) };\n if (values.proxy) config.env[\"AGENT_BROWSER_PROXY\"] = values.proxy;\n installDaemon();\n if (!values[\"no-browser\"]) config.env[\"AGENT_BROWSER_EXECUTABLE_PATH\"] = installBrowser(values[\"agent-browser\"] ?? DEFAULT_AGENT_BROWSER);\n writeConfig(config);\n restartDaemon();\n const up = await health();\n if (!up.ok) throw new Error(`unfazed eve-sandbox: the daemon did not come up; see ${ROOT}/daemon.log`);\n const alias = new URL(url).hostname.split(\".\")[0];\n process.stdout.write(\n `eve sandbox ready at ${url} (daemon ${up.version}); allowed: ${config.allow.join(\", \")}\\n\\n` +\n `In the eve app:\\n export const environment = UnfazedSandbox.environment({ box: \"${alias}\" });\\n`,\n );\n return 0;\n }\n case \"allow\":\n case \"deny\": {\n if (!arg) throw new Error(`unfazed eve-sandbox: ${command} needs an app id or owner:<id>`);\n const config = readConfig();\n config.allow = command === \"allow\" ? [...new Set([...config.allow, arg])] : config.allow.filter((entry) => entry !== arg);\n if (config.allow.length === 0) throw new Error(\"unfazed eve-sandbox: that would allow nothing; allow another app first\");\n writeConfig(config);\n restartDaemon();\n const up = await health();\n process.stdout.write(`${up.ok ? \"restarted\" : \"NOT restarted (see daemon.log)\"}; allowed: ${config.allow.join(\", \")}\\n`);\n return up.ok ? 0 : 1;\n }\n case \"status\": {\n const config = readConfig();\n const up = await health();\n process.stdout.write(\n `${url}: ${up.ok ? `up (daemon ${up.version})` : \"DOWN\"}\\n` +\n `allowed: ${config.allow.join(\", \") || \"(none)\"}\\n` +\n `sessions: ${sessionCount()}\\n` +\n `browser: ${config.env?.[\"AGENT_BROWSER_EXECUTABLE_PATH\"] ?? \"(not installed)\"}\\n`,\n );\n return up.ok ? 0 : 1;\n }\n default:\n process.stderr.write(HELP);\n return 1;\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { type StoredCredentials } from "../credentials.js";
2
2
  import { UnfazedError } from "../errors.js";
3
- export declare const USER_AGENT = "unfazed-cli/0.9.0 (+https://unfazed.dev/auth.md)";
3
+ export declare const USER_AGENT = "unfazed-cli/0.10.0 (+https://unfazed.dev/auth.md)";
4
4
  /** What a login asks for: reading and deploying, like an MCP client. */
5
5
  export declare const DEFAULT_SCOPE = "unfz:read unfz:deploy";
6
6
  export declare const CLIENT_NAME = "unfazed CLI";
package/dist/cli.js CHANGED
@@ -9,12 +9,15 @@
9
9
  * npx unfazed logout revoke (when the server offers it) and forget the login
10
10
  * npx unfazed workflow inspect runs --app <alias>
11
11
  * Workflow SDK's CLI over an app, from any directory
12
+ * npx unfazed eve-sandbox setup
13
+ * inside a box: host eve sandboxes there
12
14
  *
13
15
  * Nothing imports this file: `import "unfazed"` never loads the CLI.
14
16
  */
15
17
  import { parseArgs } from "node:util";
16
18
  import { VERSION } from "./client.js";
17
19
  import { DEFAULT_SCOPE, accountName, canOpenBrowser, login, logout, openBrowser, whoami } from "./cli/login.js";
20
+ import { runEveSandbox } from "./cli/eve-sandbox.js";
18
21
  import { runWorkflow } from "./cli/workflow.js";
19
22
  const HELP = `unfazed ${VERSION} — sign in to unfazed from a terminal
20
23
 
@@ -23,6 +26,7 @@ Usage:
23
26
  npx unfazed whoami [--json]
24
27
  npx unfazed logout
25
28
  npx unfazed workflow <Workflow CLI arguments> [--app <alias>]
29
+ npx unfazed eve-sandbox setup|allow|deny|status (inside a box, as root)
26
30
 
27
31
  Commands:
28
32
  login Sign in to unfazed in the browser. The login is stored in
@@ -40,6 +44,9 @@ Commands:
40
44
  The app is --app, else UNFZ_APP, else ./unfazed.json; the token is
41
45
  the login (or UNFZ_API_TOKEN). Runs the project's own \`workflow\`
42
46
  (major 5) when installed, else \`npx -y workflow@5\`.
47
+ eve-sandbox Inside an unfazed box, as root: host eve sandboxes there for
48
+ \`unfazed/eve-sandbox\` (agent-browser + Chromium included). See
49
+ \`npx unfazed eve-sandbox --help\`.
43
50
 
44
51
  Options:
45
52
  --endpoint <url> The platform: its origin or GraphQL URL
@@ -79,6 +86,10 @@ async function main(argv) {
79
86
  process.exitCode = await runWorkflow(argv.slice(1));
80
87
  return;
81
88
  }
89
+ if (argv[0] === "eve-sandbox") {
90
+ process.exitCode = await runEveSandbox(argv.slice(1));
91
+ return;
92
+ }
82
93
  let parsed;
83
94
  const parse = (args) => parseArgs({
84
95
  args,
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/// <reference types=\"node\" />\n/**\n * The `unfazed` bin:\n *\n * npx unfazed login sign in in the browser; the SDK and\n * `npx workflow … --backend unfazed` then use the login\n * npx unfazed whoami who the SDK acts as here\n * npx unfazed logout revoke (when the server offers it) and forget the login\n * npx unfazed workflow inspect runs --app <alias>\n * Workflow SDK's CLI over an app, from any directory\n *\n * Nothing imports this file: `import \"unfazed\"` never loads the CLI.\n */\nimport { parseArgs } from \"node:util\";\nimport { VERSION } from \"./client.js\";\nimport { DEFAULT_SCOPE, accountName, canOpenBrowser, login, logout, openBrowser, whoami } from \"./cli/login.js\";\nimport { runWorkflow } from \"./cli/workflow.js\";\n\nconst HELP = `unfazed ${VERSION} — sign in to unfazed from a terminal\n\nUsage:\n npx unfazed login [--endpoint <url>] [--scope \"<scopes>\"] [--no-browser]\n npx unfazed whoami [--json]\n npx unfazed logout\n npx unfazed workflow <Workflow CLI arguments> [--app <alias>]\n\nCommands:\n login Sign in to unfazed in the browser. The login is stored in\n \\${XDG_CONFIG_HOME:-~/.config}/unfazed/credentials.json (mode 0600);\n the unfazed SDK and \\`npx workflow … --backend unfazed\\` use it\n whenever UNFZ_API_TOKEN is unset, refreshing it as it expires.\n whoami Show who the SDK acts as here: UNFZ_API_TOKEN when set, else the login.\n logout Revoke the login's tokens when the server offers revocation, and\n delete the stored login.\n workflow Workflow SDK's CLI over an unfazed app, from any directory:\n npx unfazed workflow inspect runs --app birthday-card\n npx unfazed workflow inspect run <run-id> --app birthday-card\n npx unfazed workflow cancel <run-id> --app birthday-card\n npx unfazed workflow web --app birthday-card\n The app is --app, else UNFZ_APP, else ./unfazed.json; the token is\n the login (or UNFZ_API_TOKEN). Runs the project's own \\`workflow\\`\n (major 5) when installed, else \\`npx -y workflow@5\\`.\n\nOptions:\n --endpoint <url> The platform: its origin or GraphQL URL\n (default: UNFZ_API_URL, else https://unfazed.dev)\n --scope <scopes> Scopes to ask for (default: \"${DEFAULT_SCOPE}\")\n --no-browser Print the sign-in URL instead of opening a browser\n --json whoami: print JSON\n -h, --help Show this help\n -v, --version Show the version\n`;\n\n/** Unwinds `main` once {@link fail} has scheduled the exit. */\nclass Exit extends Error {}\n\n/**\n * Exits once stdout and stderr are flushed (pipes are asynchronous on macOS),\n * so nothing a command started — a browser's keep-alive socket — holds the\n * terminal, and no output is cut.\n */\nfunction exit(code: number): void {\n process.exitCode = code;\n let pending = 2;\n const done = () => {\n if (--pending === 0) process.exit(code);\n };\n process.stdout.write(\"\", done);\n process.stderr.write(\"\", done);\n}\n\nfunction fail(message: string): never {\n process.stderr.write(message.replace(/\\s+$/, \"\") + \"\\n\");\n exit(1);\n throw new Exit();\n}\n\nasync function main(argv: string[]): Promise<void> {\n // Everything after `workflow` is the Workflow CLI's (but --app).\n if (argv[0] === \"workflow\") {\n process.exitCode = await runWorkflow(argv.slice(1));\n return;\n }\n let parsed: ReturnType<typeof parse>;\n const parse = (args: string[]) =>\n parseArgs({\n args,\n allowPositionals: true,\n options: {\n endpoint: { type: \"string\" },\n scope: { type: \"string\" },\n \"no-browser\": { type: \"boolean\" },\n json: { type: \"boolean\" },\n help: { type: \"boolean\", short: \"h\" },\n version: { type: \"boolean\", short: \"v\" },\n },\n });\n try {\n parsed = parse(argv);\n } catch (error) {\n fail(`unfazed: ${(error as Error).message}\\n\\n${HELP}`);\n }\n const { values, positionals } = parsed;\n if (values.version) {\n process.stdout.write(`${VERSION}\\n`);\n return;\n }\n const [command, ...rest] = positionals;\n if (values.help || command === \"help\") {\n process.stdout.write(HELP);\n return;\n }\n if (command === undefined) fail(HELP);\n if (rest.length > 0) fail(`unfazed: unexpected argument ${JSON.stringify(rest[0])}\\n\\n${HELP}`);\n\n switch (command) {\n case \"login\": {\n const browser = !values[\"no-browser\"] && canOpenBrowser(process.env);\n const { credentials, path } = await login({\n ...(values.endpoint ? { endpoint: values.endpoint } : {}),\n ...(values.scope ? { scope: values.scope } : {}),\n ...(browser ? { openBrowser: (url: string) => openBrowser(url) } : {}),\n // A browser on another machine: the person pastes where it ended up.\n ...(process.stdin.isTTY ? { input: process.stdin } : {}),\n });\n const kind = credentials.account?.kind ? ` (${credentials.account.kind.toLowerCase()})` : \"\";\n process.stderr.write(\n `\\nSigned in to ${credentials.issuer} as ${accountName(credentials)}${kind}.\\n` +\n `The login is stored in ${path}.\\n` +\n `The unfazed SDK and \\`npx workflow … --backend unfazed\\` use it when UNFZ_API_TOKEN is unset.\\n`,\n );\n if (process.env[\"UNFZ_API_TOKEN\"]) process.stderr.write(\"UNFZ_API_TOKEN is set in this shell and takes precedence; unset it to use the login.\\n\");\n return;\n }\n case \"whoami\": {\n const result = await whoami();\n if (values.json) {\n process.stdout.write(JSON.stringify(result, null, 2) + \"\\n\");\n return;\n }\n const { actor } = result;\n process.stdout.write(\n `${actor.name} (${actor.kind.toLowerCase()} ${actor.userID})\\n` +\n ` endpoint ${result.endpoint}\\n` +\n ` scopes ${actor.scopes.join(\" \") || \"(none)\"}\\n` +\n ` via ${result.source === \"token\" ? \"UNFZ_API_TOKEN\" : `the stored login (${result.path})`}\\n`,\n );\n return;\n }\n case \"logout\": {\n const result = await logout();\n if (!result.signedOut) {\n process.stderr.write(\"Not signed in.\\n\");\n return;\n }\n const revocation = result.revoked\n ? \"The tokens were revoked.\"\n : result.revocationSupported\n ? \"Revoking the tokens failed; they stop working when they expire.\"\n : \"The server offers no token revocation; the deleted tokens stop working when they expire.\";\n process.stderr.write(`Signed out of ${result.issuer}. ${revocation}\\n`);\n if (process.env[\"UNFZ_API_TOKEN\"]) process.stderr.write(\"UNFZ_API_TOKEN is still set in this shell.\\n\");\n return;\n }\n default:\n fail(`unfazed: unknown command ${JSON.stringify(command)}\\n\\n${HELP}`);\n }\n}\n\nmain(process.argv.slice(2)).then(\n () => exit(typeof process.exitCode === \"number\" ? process.exitCode : 0),\n (error: unknown) => {\n if (error instanceof Exit) return;\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write((message.startsWith(\"unfazed\") ? message : `unfazed: ${message}`) + \"\\n\");\n exit(1);\n },\n);\n"]}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/// <reference types=\"node\" />\n/**\n * The `unfazed` bin:\n *\n * npx unfazed login sign in in the browser; the SDK and\n * `npx workflow … --backend unfazed` then use the login\n * npx unfazed whoami who the SDK acts as here\n * npx unfazed logout revoke (when the server offers it) and forget the login\n * npx unfazed workflow inspect runs --app <alias>\n * Workflow SDK's CLI over an app, from any directory\n * npx unfazed eve-sandbox setup\n * inside a box: host eve sandboxes there\n *\n * Nothing imports this file: `import \"unfazed\"` never loads the CLI.\n */\nimport { parseArgs } from \"node:util\";\nimport { VERSION } from \"./client.js\";\nimport { DEFAULT_SCOPE, accountName, canOpenBrowser, login, logout, openBrowser, whoami } from \"./cli/login.js\";\nimport { runEveSandbox } from \"./cli/eve-sandbox.js\";\nimport { runWorkflow } from \"./cli/workflow.js\";\n\nconst HELP = `unfazed ${VERSION} — sign in to unfazed from a terminal\n\nUsage:\n npx unfazed login [--endpoint <url>] [--scope \"<scopes>\"] [--no-browser]\n npx unfazed whoami [--json]\n npx unfazed logout\n npx unfazed workflow <Workflow CLI arguments> [--app <alias>]\n npx unfazed eve-sandbox setup|allow|deny|status (inside a box, as root)\n\nCommands:\n login Sign in to unfazed in the browser. The login is stored in\n \\${XDG_CONFIG_HOME:-~/.config}/unfazed/credentials.json (mode 0600);\n the unfazed SDK and \\`npx workflow … --backend unfazed\\` use it\n whenever UNFZ_API_TOKEN is unset, refreshing it as it expires.\n whoami Show who the SDK acts as here: UNFZ_API_TOKEN when set, else the login.\n logout Revoke the login's tokens when the server offers revocation, and\n delete the stored login.\n workflow Workflow SDK's CLI over an unfazed app, from any directory:\n npx unfazed workflow inspect runs --app birthday-card\n npx unfazed workflow inspect run <run-id> --app birthday-card\n npx unfazed workflow cancel <run-id> --app birthday-card\n npx unfazed workflow web --app birthday-card\n The app is --app, else UNFZ_APP, else ./unfazed.json; the token is\n the login (or UNFZ_API_TOKEN). Runs the project's own \\`workflow\\`\n (major 5) when installed, else \\`npx -y workflow@5\\`.\n eve-sandbox Inside an unfazed box, as root: host eve sandboxes there for\n \\`unfazed/eve-sandbox\\` (agent-browser + Chromium included). See\n \\`npx unfazed eve-sandbox --help\\`.\n\nOptions:\n --endpoint <url> The platform: its origin or GraphQL URL\n (default: UNFZ_API_URL, else https://unfazed.dev)\n --scope <scopes> Scopes to ask for (default: \"${DEFAULT_SCOPE}\")\n --no-browser Print the sign-in URL instead of opening a browser\n --json whoami: print JSON\n -h, --help Show this help\n -v, --version Show the version\n`;\n\n/** Unwinds `main` once {@link fail} has scheduled the exit. */\nclass Exit extends Error {}\n\n/**\n * Exits once stdout and stderr are flushed (pipes are asynchronous on macOS),\n * so nothing a command started — a browser's keep-alive socket — holds the\n * terminal, and no output is cut.\n */\nfunction exit(code: number): void {\n process.exitCode = code;\n let pending = 2;\n const done = () => {\n if (--pending === 0) process.exit(code);\n };\n process.stdout.write(\"\", done);\n process.stderr.write(\"\", done);\n}\n\nfunction fail(message: string): never {\n process.stderr.write(message.replace(/\\s+$/, \"\") + \"\\n\");\n exit(1);\n throw new Exit();\n}\n\nasync function main(argv: string[]): Promise<void> {\n // Everything after `workflow` is the Workflow CLI's (but --app).\n if (argv[0] === \"workflow\") {\n process.exitCode = await runWorkflow(argv.slice(1));\n return;\n }\n if (argv[0] === \"eve-sandbox\") {\n process.exitCode = await runEveSandbox(argv.slice(1));\n return;\n }\n let parsed: ReturnType<typeof parse>;\n const parse = (args: string[]) =>\n parseArgs({\n args,\n allowPositionals: true,\n options: {\n endpoint: { type: \"string\" },\n scope: { type: \"string\" },\n \"no-browser\": { type: \"boolean\" },\n json: { type: \"boolean\" },\n help: { type: \"boolean\", short: \"h\" },\n version: { type: \"boolean\", short: \"v\" },\n },\n });\n try {\n parsed = parse(argv);\n } catch (error) {\n fail(`unfazed: ${(error as Error).message}\\n\\n${HELP}`);\n }\n const { values, positionals } = parsed;\n if (values.version) {\n process.stdout.write(`${VERSION}\\n`);\n return;\n }\n const [command, ...rest] = positionals;\n if (values.help || command === \"help\") {\n process.stdout.write(HELP);\n return;\n }\n if (command === undefined) fail(HELP);\n if (rest.length > 0) fail(`unfazed: unexpected argument ${JSON.stringify(rest[0])}\\n\\n${HELP}`);\n\n switch (command) {\n case \"login\": {\n const browser = !values[\"no-browser\"] && canOpenBrowser(process.env);\n const { credentials, path } = await login({\n ...(values.endpoint ? { endpoint: values.endpoint } : {}),\n ...(values.scope ? { scope: values.scope } : {}),\n ...(browser ? { openBrowser: (url: string) => openBrowser(url) } : {}),\n // A browser on another machine: the person pastes where it ended up.\n ...(process.stdin.isTTY ? { input: process.stdin } : {}),\n });\n const kind = credentials.account?.kind ? ` (${credentials.account.kind.toLowerCase()})` : \"\";\n process.stderr.write(\n `\\nSigned in to ${credentials.issuer} as ${accountName(credentials)}${kind}.\\n` +\n `The login is stored in ${path}.\\n` +\n `The unfazed SDK and \\`npx workflow … --backend unfazed\\` use it when UNFZ_API_TOKEN is unset.\\n`,\n );\n if (process.env[\"UNFZ_API_TOKEN\"]) process.stderr.write(\"UNFZ_API_TOKEN is set in this shell and takes precedence; unset it to use the login.\\n\");\n return;\n }\n case \"whoami\": {\n const result = await whoami();\n if (values.json) {\n process.stdout.write(JSON.stringify(result, null, 2) + \"\\n\");\n return;\n }\n const { actor } = result;\n process.stdout.write(\n `${actor.name} (${actor.kind.toLowerCase()} ${actor.userID})\\n` +\n ` endpoint ${result.endpoint}\\n` +\n ` scopes ${actor.scopes.join(\" \") || \"(none)\"}\\n` +\n ` via ${result.source === \"token\" ? \"UNFZ_API_TOKEN\" : `the stored login (${result.path})`}\\n`,\n );\n return;\n }\n case \"logout\": {\n const result = await logout();\n if (!result.signedOut) {\n process.stderr.write(\"Not signed in.\\n\");\n return;\n }\n const revocation = result.revoked\n ? \"The tokens were revoked.\"\n : result.revocationSupported\n ? \"Revoking the tokens failed; they stop working when they expire.\"\n : \"The server offers no token revocation; the deleted tokens stop working when they expire.\";\n process.stderr.write(`Signed out of ${result.issuer}. ${revocation}\\n`);\n if (process.env[\"UNFZ_API_TOKEN\"]) process.stderr.write(\"UNFZ_API_TOKEN is still set in this shell.\\n\");\n return;\n }\n default:\n fail(`unfazed: unknown command ${JSON.stringify(command)}\\n\\n${HELP}`);\n }\n}\n\nmain(process.argv.slice(2)).then(\n () => exit(typeof process.exitCode === \"number\" ? process.exitCode : 0),\n (error: unknown) => {\n if (error instanceof Exit) return;\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write((message.startsWith(\"unfazed\") ? message : `unfazed: ${message}`) + \"\\n\");\n exit(1);\n },\n);\n"]}
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** SDK version, sent as the User-Agent. Keep in sync with package.json. */
2
- export declare const VERSION = "0.9.0";
2
+ export declare const VERSION = "0.10.0";
3
3
  /**
4
4
  * Default GraphQL endpoint.
5
5
  *
package/dist/client.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { LOGIN_COMMAND, openStoredLogin } from "./credentials.js";
2
2
  import { UnfazedAuthError, UnfazedConfigError, UnfazedGraphQLError, UnfazedHTTPError, UnfazedTimeoutError, } from "./errors.js";
3
3
  /** SDK version, sent as the User-Agent. Keep in sync with package.json. */
4
- export const VERSION = "0.9.0";
4
+ export const VERSION = "0.10.0";
5
5
  /**
6
6
  * Default GraphQL endpoint.
7
7
  *