unfazed 0.8.1 → 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.
@@ -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.8.1 (+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.8.1";
2
+ export declare const VERSION = "0.10.0";
3
3
  /**
4
4
  * Default GraphQL endpoint.
5
5
  *
@@ -153,3 +153,23 @@ export declare class UnfazedClient {
153
153
  toString(): string;
154
154
  }, variables?: unknown, options?: RequestOptions): Promise<TResult>;
155
155
  }
156
+ /**
157
+ * The platform origin an endpoint is served from:
158
+ * `https://unfazed.dev/query` → `https://unfazed.dev`. A path before `/query`
159
+ * (a control plane mounted under a prefix) is kept.
160
+ */
161
+ export declare function platformBaseUrl(endpoint: string): string;
162
+ /** The endpoint the options or `UNFZ_API_URL` name, or undefined for the default. */
163
+ export declare function configuredEndpoint(options: Pick<ClientOptions, "endpoint" | "baseUrl">, env: Record<string, string | undefined>): string | undefined;
164
+ export declare function processEnv(): Record<string, string | undefined>;
165
+ /**
166
+ * Settles with `promise`, or rejects the moment `signal` aborts.
167
+ *
168
+ * A global `fetch` honours the signal for headers and body alike, but an
169
+ * injected {@link FetchLike} — a proxy, an instrumented transport, a test
170
+ * double — need not. Racing the signal makes the timeout an upper bound on the
171
+ * whole request regardless of what the transport does with it. The loser's
172
+ * rejection is swallowed by the already-settled promise rather than surfacing
173
+ * as an unhandled rejection.
174
+ */
175
+ export declare function untilAborted<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T>;
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.8.1";
4
+ export const VERSION = "0.10.0";
5
5
  /**
6
6
  * Default GraphQL endpoint.
7
7
  *
@@ -198,8 +198,16 @@ export class UnfazedClient {
198
198
  }
199
199
  }
200
200
  }
201
+ /**
202
+ * The platform origin an endpoint is served from:
203
+ * `https://unfazed.dev/query` → `https://unfazed.dev`. A path before `/query`
204
+ * (a control plane mounted under a prefix) is kept.
205
+ */
206
+ export function platformBaseUrl(endpoint) {
207
+ return trimSlash(endpoint).replace(/\/(?:query|graphql)$/, "");
208
+ }
201
209
  /** The endpoint the options or `UNFZ_API_URL` name, or undefined for the default. */
202
- function configuredEndpoint(options, env) {
210
+ export function configuredEndpoint(options, env) {
203
211
  if (options.endpoint)
204
212
  return options.endpoint;
205
213
  if (options.baseUrl)
@@ -222,7 +230,7 @@ function noTokenMessage(endpoint, otherLogin) {
222
230
  return (`unfazed: not signed in. Run \`${LOGIN_COMMAND}\` to sign in with your account, ` +
223
231
  `or pass { token } / set ${TOKEN_ENV_VAR} (an agent token: ${DEFAULT_BASE_URL}/auth.md).`);
224
232
  }
225
- function processEnv() {
233
+ export function processEnv() {
226
234
  // Guarded: the SDK must also work in a browser or worker, where there is no
227
235
  // `process` at all.
228
236
  return globalThis.process?.env ?? {};
@@ -248,7 +256,7 @@ function abortError() {
248
256
  * rejection is swallowed by the already-settled promise rather than surfacing
249
257
  * as an unhandled rejection.
250
258
  */
251
- function untilAborted(promise, signal) {
259
+ export function untilAborted(promise, signal) {
252
260
  if (!signal)
253
261
  return promise;
254
262
  if (signal.aborted)
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { LOGIN_COMMAND, openStoredLogin, type StoredLogin } from \"./credentials.js\";\nimport {\n UnfazedAuthError,\n UnfazedConfigError,\n UnfazedGraphQLError,\n UnfazedHTTPError,\n UnfazedTimeoutError,\n type GraphQLResponseError,\n} from \"./errors.js\";\n\n/** SDK version, sent as the User-Agent. Keep in sync with package.json. */\nexport const VERSION = \"0.8.1\";\n\n/**\n * Default GraphQL endpoint.\n *\n * Note this is the FULL endpoint, not an origin: the platform's own documented\n * convention (`UNFZ_API_URL=\"https://unfazed.dev/query\"`, and cmd/showcase-deploy\n * in the unfazed repo) treats the /query URL as the configurable unit, so the\n * SDK matches it rather than inventing a base-URL convention.\n */\nexport const DEFAULT_ENDPOINT = \"https://unfazed.dev/query\";\n\n/** Platform origin, used to derive the endpoint when only `baseUrl` is given. */\nexport const DEFAULT_BASE_URL = \"https://unfazed.dev\";\n\n/** Path of the GraphQL endpoint under an origin. */\nexport const GRAPHQL_PATH = \"/query\";\n\n/**\n * Environment variable holding the bearer token.\n *\n * This is the platform's documented name (docs/agent-deployments.md,\n * internal/agentdocs/reference.md). It accepts either an operator API token or\n * an agent access token from POST /agents/register — /query takes both as\n * plain bearers.\n */\nexport const TOKEN_ENV_VAR = \"UNFZ_API_TOKEN\";\n\n/** Environment variable holding the full GraphQL endpoint URL. */\nexport const ENDPOINT_ENV_VAR = \"UNFZ_API_URL\";\n\n/** Minimal structural type of the `fetch` the transport needs. */\nexport type FetchLike = (\n input: string,\n init: {\n method: string;\n headers: Record<string, string>;\n body: string;\n signal?: AbortSignal;\n },\n) => Promise<{\n ok: boolean;\n status: number;\n text(): Promise<string>;\n}>;\n\nexport interface ClientOptions {\n /**\n * Bearer token. Falls back to `UNFZ_API_TOKEN`, then to the login\n * `npx unfazed login` stored (see {@link ClientOptions.login}). An agent gets\n * one from https://unfazed.dev/auth.md — the same token the MCP server uses.\n */\n token?: string;\n /**\n * Use the stored login of `npx unfazed login`\n * (`${XDG_CONFIG_HOME:-~/.config}/unfazed/credentials.json`) when no token\n * is passed and `UNFZ_API_TOKEN` is unset. Its access token is refreshed\n * before it expires and when the platform refuses it. Default `true`; it is\n * only used for the endpoint it was issued by, and is the endpoint when none\n * is configured.\n */\n login?: boolean;\n /**\n * Where `UNFZ_API_TOKEN`, `UNFZ_API_URL` and the stored login's location\n * (`XDG_CONFIG_HOME`, `HOME`) are read from. Default: `process.env`.\n */\n env?: Record<string, string | undefined>;\n /**\n * Full GraphQL endpoint URL. Wins over `baseUrl`. Falls back to\n * `UNFZ_API_URL`, then the stored login's endpoint, then\n * {@link DEFAULT_ENDPOINT}.\n */\n endpoint?: string;\n /**\n * Platform origin; `/query` is appended. Convenience for self-hosted or\n * staging control planes where only the host differs.\n */\n baseUrl?: string;\n /** Extra headers merged into every request. Authorization always wins. */\n headers?: Record<string, string>;\n /**\n * Per-request timeout in ms. Default 60_000. Pass 0 to disable.\n *\n * Bounds the WHOLE request — connect, headers and reading the response body —\n * so a proxy that answers with headers and then stalls still fails on time.\n */\n timeoutMs?: number;\n /** Injection point for tests and instrumented/proxied transports. */\n fetch?: FetchLike;\n /** Overrides the default `unfazed-sdk/<version>` User-Agent. */\n userAgent?: string;\n}\n\n/**\n * A GraphQL document.\n *\n * Structural rather than `string & {...}` on purpose: graphql-codegen emits\n * `TypedDocumentString`, a String SUBCLASS, which is not assignable to the\n * primitive `string`. Requiring only `toString()` plus the optional phantom\n * `__apiType` lets three things through with full inference:\n *\n * - generated `TypedDocumentString` documents (types inferred),\n * - documents assembled at runtime, like the batched writeFiles mutation,\n * - plain template-literal strings a caller wrote by hand.\n */\nexport interface GraphQLDocument<TResult, TVariables> {\n /** Phantom type carrier. Never called; present only to bind the generics. */\n __apiType?: (variables: TVariables) => TResult;\n toString(): string;\n}\n\n/** Result type carried by a typed document. */\nexport type ResultOf<T> = T extends GraphQLDocument<infer R, infer _V> ? R : unknown;\n\n/** Variables type carried by a typed document. */\nexport type VariablesOf<T> = T extends GraphQLDocument<infer _R, infer V> ? V : never;\n\n/** Per-request knobs. */\nexport interface RequestOptions {\n signal?: AbortSignal;\n timeoutMs?: number;\n operationName?: string;\n}\n\n/**\n * Layer 1 — a typed, dependency-free GraphQL transport over `fetch`.\n *\n * This is the SDK's entire networking surface. It understands auth, timeouts\n * and the GraphQL error envelope, and nothing about deployments. The workflow\n * layer ({@link import(\"./unfazed.js\").Unfazed}) is built strictly on top of\n * it, so you can always drop down here for any operation the workflow layer\n * does not wrap — the schema has ~660 fields and the workflow layer\n * deliberately covers only the agent-facing ones.\n *\n * ```ts\n * const client = new UnfazedClient(); // UNFZ_API_TOKEN, else `npx unfazed login`\n * const data = await client.query<{ whoami: { name: string } }, never>(\n * `query { whoami { name } }`,\n * );\n * ```\n */\nexport class UnfazedClient {\n /** Resolved GraphQL endpoint URL. */\n readonly endpoint: string;\n\n /** A fixed bearer token, or the stored login handing out refreshed ones. */\n readonly #auth: { token: string } | { login: StoredLogin };\n readonly #headers: Record<string, string>;\n readonly #timeoutMs: number;\n readonly #fetch: FetchLike;\n\n constructor(options: ClientOptions = {}) {\n const env = options.env ?? processEnv();\n\n const injected = options.fetch;\n if (injected) {\n this.#fetch = injected;\n } else {\n const globalFetch = (globalThis as { fetch?: unknown }).fetch;\n if (typeof globalFetch !== \"function\") {\n throw new UnfazedConfigError(\n \"unfazed: no global fetch. Use Bun, Node >= 20, or pass { fetch } to the client.\",\n );\n }\n this.#fetch = globalFetch.bind(globalThis) as FetchLike;\n }\n\n const ua = options.userAgent ?? `unfazed-sdk/${VERSION}`;\n\n const configured = configuredEndpoint(options, env);\n const token = options.token ?? env[TOKEN_ENV_VAR];\n if (token?.trim()) {\n this.#auth = { token: token.trim() };\n this.endpoint = configured ?? DEFAULT_ENDPOINT;\n } else {\n const stored =\n options.login === false\n ? {}\n : openStoredLogin({\n env,\n ...(configured ? { endpoint: configured } : {}),\n fetch: this.#fetch,\n // The edge refuses some generic agents; always name ourselves.\n userAgent: ua || `unfazed-sdk/${VERSION}`,\n });\n if (!stored.login) {\n throw new UnfazedAuthError(noTokenMessage(configured, stored.other?.endpoint));\n }\n this.#auth = { login: stored.login };\n this.endpoint = stored.login.endpoint;\n }\n\n this.#headers = { ...(options.headers ?? {}) };\n this.#timeoutMs = options.timeoutMs ?? 60_000;\n if (ua) this.#headers[\"User-Agent\"] = ua;\n }\n\n /**\n * Where the bearer token comes from: `\"token\"` (passed or\n * `UNFZ_API_TOKEN`) or `\"login\"` (the stored login of `npx unfazed login`).\n */\n get authSource(): \"token\" | \"login\" {\n return \"login\" in this.#auth ? \"login\" : \"token\";\n }\n\n /** Runs a query document. Identical wire behaviour to {@link mutate}. */\n query<TResult, TVariables>(\n document: GraphQLDocument<TResult, TVariables>,\n variables?: TVariables,\n options?: RequestOptions,\n ): Promise<TResult> {\n return this.request(document, variables, options);\n }\n\n /**\n * Runs a mutation document. Distinct from {@link query} for call-site\n * readability only — GraphQL over HTTP POST makes no distinction.\n */\n mutate<TResult, TVariables>(\n document: GraphQLDocument<TResult, TVariables>,\n variables?: TVariables,\n options?: RequestOptions,\n ): Promise<TResult> {\n return this.request(document, variables, options);\n }\n\n /**\n * The single code path behind {@link query} and {@link mutate}.\n *\n * Throws {@link UnfazedHTTPError} on non-2xx, {@link UnfazedGraphQLError}\n * when the body carries `errors`, {@link UnfazedTimeoutError} on timeout or\n * abort. Resolves to the `data` object on success.\n */\n async request<TResult = unknown>(\n document: string | { toString(): string },\n variables?: unknown,\n options: RequestOptions = {},\n ): Promise<TResult> {\n const timeoutMs = options.timeoutMs ?? this.#timeoutMs;\n\n // `document` may be a TypedDocumentString (a String object). JSON.stringify\n // would serialise that fine, but String(...) keeps the wire body a plain\n // primitive regardless of how the caller obtained the document.\n const body = JSON.stringify({\n query: String(document),\n ...(variables === undefined ? {} : { variables }),\n ...(options.operationName ? { operationName: options.operationName } : {}),\n });\n\n let token = await this.#token();\n let { response, text } = await this.#send(body, token, timeoutMs, options.signal);\n // A stored login's access token can be refused before its recorded\n // expiry (the clock, a sign-out elsewhere): refresh once and retry. A\n // fixed token has nothing to refresh.\n if (response.status === 401 && \"login\" in this.#auth) {\n token = await this.#auth.login.refresh(token);\n ({ response, text } = await this.#send(body, token, timeoutMs, options.signal));\n }\n\n if (!response.ok) {\n throw new UnfazedHTTPError(response.status, text, this.endpoint);\n }\n\n let parsed: { data?: TResult; errors?: GraphQLResponseError[] };\n try {\n parsed = JSON.parse(text) as typeof parsed;\n } catch {\n throw new UnfazedHTTPError(response.status, text, this.endpoint);\n }\n\n // A GraphQL 200 can still be a failure, and can be a PARTIAL failure: the\n // batched writeFiles document resolves each aliased field independently.\n // Attaching `data` to the error is what lets a caller see which writes\n // landed before the failing one.\n if (parsed.errors?.length) {\n throw new UnfazedGraphQLError(parsed.errors, parsed.data);\n }\n if (parsed.data === undefined || parsed.data === null) {\n throw new UnfazedGraphQLError(\n [{ message: \"response contained neither data nor errors\" }],\n undefined,\n );\n }\n return parsed.data;\n }\n\n #token(): Promise<string> {\n return \"login\" in this.#auth ? this.#auth.login.token() : Promise.resolve(this.#auth.token);\n }\n\n async #send(\n body: string,\n token: string,\n timeoutMs: number,\n external: AbortSignal | undefined,\n ): Promise<{ response: Awaited<ReturnType<FetchLike>>; text: string }> {\n const { signal, cleanup, didTimeout } = makeSignal(timeoutMs, external);\n // The deadline covers BOTH phases — headers AND body. A proxy that answers\n // with headers and then stalls the body is a real failure mode, and\n // stopping the clock at the end of `fetch()` would leave `response.text()`\n // hanging forever. `untilAborted` additionally races the signal itself, so\n // an injected transport that ignores `signal` still cannot outlive the\n // timeout.\n try {\n const response = await untilAborted(\n this.#fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n ...this.#headers,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body,\n ...(signal ? { signal } : {}),\n }),\n signal,\n );\n const text = await untilAborted(response.text(), signal);\n return { response, text };\n } catch (cause) {\n if (didTimeout()) {\n throw new UnfazedTimeoutError(`unfazed: request timed out after ${timeoutMs}ms`, { cause });\n }\n if (isAbort(cause)) {\n throw new UnfazedTimeoutError(\"unfazed: request aborted\", { cause });\n }\n throw cause;\n } finally {\n cleanup();\n }\n }\n}\n\n/** The endpoint the options or `UNFZ_API_URL` name, or undefined for the default. */\nfunction configuredEndpoint(options: ClientOptions, env: Record<string, string | undefined>): string | undefined {\n if (options.endpoint) return options.endpoint;\n if (options.baseUrl) return trimSlash(options.baseUrl) + GRAPHQL_PATH;\n const fromEnv = env[ENDPOINT_ENV_VAR];\n // UNFZ_API_URL is documented as the full /query URL and is used verbatim.\n // Tolerate a bare origin anyway, since that is the obvious mistake and\n // guessing right costs nothing.\n if (fromEnv) {\n const trimmed = trimSlash(fromEnv);\n return /\\/query$|\\/graphql$/.test(trimmed) ? trimmed : trimmed + GRAPHQL_PATH;\n }\n return undefined;\n}\n\nfunction noTokenMessage(endpoint: string | undefined, otherLogin: string | undefined): string {\n if (endpoint && otherLogin) {\n return (\n `unfazed: no API token for ${endpoint}: the stored login is for ${otherLogin}. ` +\n `Run \\`${ENDPOINT_ENV_VAR}=${endpoint} ${LOGIN_COMMAND}\\`, or pass { token } / set ${TOKEN_ENV_VAR}.`\n );\n }\n return (\n `unfazed: not signed in. Run \\`${LOGIN_COMMAND}\\` to sign in with your account, ` +\n `or pass { token } / set ${TOKEN_ENV_VAR} (an agent token: ${DEFAULT_BASE_URL}/auth.md).`\n );\n}\n\nfunction processEnv(): Record<string, string | undefined> {\n // Guarded: the SDK must also work in a browser or worker, where there is no\n // `process` at all.\n return (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env ?? {};\n}\n\nfunction trimSlash(u: string): string {\n return u.replace(/\\/+$/, \"\");\n}\n\nfunction isAbort(e: unknown): boolean {\n return typeof e === \"object\" && e !== null && (e as { name?: string }).name === \"AbortError\";\n}\n\nfunction abortError(): Error {\n const e = new Error(\"The operation was aborted.\");\n e.name = \"AbortError\";\n return e;\n}\n\n/**\n * Settles with `promise`, or rejects the moment `signal` aborts.\n *\n * A global `fetch` honours the signal for headers and body alike, but an\n * injected {@link FetchLike} — a proxy, an instrumented transport, a test\n * double — need not. Racing the signal makes the timeout an upper bound on the\n * whole request regardless of what the transport does with it. The loser's\n * rejection is swallowed by the already-settled promise rather than surfacing\n * as an unhandled rejection.\n */\nfunction untilAborted<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T> {\n if (!signal) return promise;\n if (signal.aborted) return Promise.reject(abortError());\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(abortError());\n signal.addEventListener(\"abort\", onAbort, { once: true });\n const done = () => signal.removeEventListener(\"abort\", onAbort);\n promise.then(\n (value) => {\n done();\n resolve(value);\n },\n (error: unknown) => {\n done();\n reject(error as Error);\n },\n );\n });\n}\n\n/**\n * Combines the request timeout with any caller-supplied signal.\n *\n * `didTimeout` is tracked separately from the abort so a caller's own\n * cancellation is not mislabelled as a timeout in the thrown message.\n */\nfunction makeSignal(\n timeoutMs: number,\n external?: AbortSignal,\n): { signal?: AbortSignal; cleanup: () => void; didTimeout: () => boolean } {\n if (!timeoutMs && !external) return { cleanup: () => {}, didTimeout: () => false };\n if (typeof AbortController !== \"function\") {\n return {\n ...(external ? { signal: external } : {}),\n cleanup: () => {},\n didTimeout: () => false,\n };\n }\n\n const controller = new AbortController();\n let timedOut = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n if (timeoutMs > 0) {\n timer = setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, timeoutMs);\n }\n\n const onExternalAbort = () => controller.abort();\n if (external) {\n if (external.aborted) controller.abort();\n else external.addEventListener(\"abort\", onExternalAbort, { once: true });\n }\n\n return {\n signal: controller.signal,\n cleanup: () => {\n if (timer !== undefined) clearTimeout(timer);\n external?.removeEventListener(\"abort\", onExternalAbort);\n },\n didTimeout: () => timedOut,\n };\n}\n"]}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { LOGIN_COMMAND, openStoredLogin, type StoredLogin } from \"./credentials.js\";\nimport {\n UnfazedAuthError,\n UnfazedConfigError,\n UnfazedGraphQLError,\n UnfazedHTTPError,\n UnfazedTimeoutError,\n type GraphQLResponseError,\n} from \"./errors.js\";\n\n/** SDK version, sent as the User-Agent. Keep in sync with package.json. */\nexport const VERSION = \"0.10.0\";\n\n/**\n * Default GraphQL endpoint.\n *\n * Note this is the FULL endpoint, not an origin: the platform's own documented\n * convention (`UNFZ_API_URL=\"https://unfazed.dev/query\"`, and cmd/showcase-deploy\n * in the unfazed repo) treats the /query URL as the configurable unit, so the\n * SDK matches it rather than inventing a base-URL convention.\n */\nexport const DEFAULT_ENDPOINT = \"https://unfazed.dev/query\";\n\n/** Platform origin, used to derive the endpoint when only `baseUrl` is given. */\nexport const DEFAULT_BASE_URL = \"https://unfazed.dev\";\n\n/** Path of the GraphQL endpoint under an origin. */\nexport const GRAPHQL_PATH = \"/query\";\n\n/**\n * Environment variable holding the bearer token.\n *\n * This is the platform's documented name (docs/agent-deployments.md,\n * internal/agentdocs/reference.md). It accepts either an operator API token or\n * an agent access token from POST /agents/register — /query takes both as\n * plain bearers.\n */\nexport const TOKEN_ENV_VAR = \"UNFZ_API_TOKEN\";\n\n/** Environment variable holding the full GraphQL endpoint URL. */\nexport const ENDPOINT_ENV_VAR = \"UNFZ_API_URL\";\n\n/** Minimal structural type of the `fetch` the transport needs. */\nexport type FetchLike = (\n input: string,\n init: {\n method: string;\n headers: Record<string, string>;\n body: string;\n signal?: AbortSignal;\n },\n) => Promise<{\n ok: boolean;\n status: number;\n text(): Promise<string>;\n}>;\n\nexport interface ClientOptions {\n /**\n * Bearer token. Falls back to `UNFZ_API_TOKEN`, then to the login\n * `npx unfazed login` stored (see {@link ClientOptions.login}). An agent gets\n * one from https://unfazed.dev/auth.md — the same token the MCP server uses.\n */\n token?: string;\n /**\n * Use the stored login of `npx unfazed login`\n * (`${XDG_CONFIG_HOME:-~/.config}/unfazed/credentials.json`) when no token\n * is passed and `UNFZ_API_TOKEN` is unset. Its access token is refreshed\n * before it expires and when the platform refuses it. Default `true`; it is\n * only used for the endpoint it was issued by, and is the endpoint when none\n * is configured.\n */\n login?: boolean;\n /**\n * Where `UNFZ_API_TOKEN`, `UNFZ_API_URL` and the stored login's location\n * (`XDG_CONFIG_HOME`, `HOME`) are read from. Default: `process.env`.\n */\n env?: Record<string, string | undefined>;\n /**\n * Full GraphQL endpoint URL. Wins over `baseUrl`. Falls back to\n * `UNFZ_API_URL`, then the stored login's endpoint, then\n * {@link DEFAULT_ENDPOINT}.\n */\n endpoint?: string;\n /**\n * Platform origin; `/query` is appended. Convenience for self-hosted or\n * staging control planes where only the host differs.\n */\n baseUrl?: string;\n /** Extra headers merged into every request. Authorization always wins. */\n headers?: Record<string, string>;\n /**\n * Per-request timeout in ms. Default 60_000. Pass 0 to disable.\n *\n * Bounds the WHOLE request — connect, headers and reading the response body —\n * so a proxy that answers with headers and then stalls still fails on time.\n */\n timeoutMs?: number;\n /** Injection point for tests and instrumented/proxied transports. */\n fetch?: FetchLike;\n /** Overrides the default `unfazed-sdk/<version>` User-Agent. */\n userAgent?: string;\n}\n\n/**\n * A GraphQL document.\n *\n * Structural rather than `string & {...}` on purpose: graphql-codegen emits\n * `TypedDocumentString`, a String SUBCLASS, which is not assignable to the\n * primitive `string`. Requiring only `toString()` plus the optional phantom\n * `__apiType` lets three things through with full inference:\n *\n * - generated `TypedDocumentString` documents (types inferred),\n * - documents assembled at runtime, like the batched writeFiles mutation,\n * - plain template-literal strings a caller wrote by hand.\n */\nexport interface GraphQLDocument<TResult, TVariables> {\n /** Phantom type carrier. Never called; present only to bind the generics. */\n __apiType?: (variables: TVariables) => TResult;\n toString(): string;\n}\n\n/** Result type carried by a typed document. */\nexport type ResultOf<T> = T extends GraphQLDocument<infer R, infer _V> ? R : unknown;\n\n/** Variables type carried by a typed document. */\nexport type VariablesOf<T> = T extends GraphQLDocument<infer _R, infer V> ? V : never;\n\n/** Per-request knobs. */\nexport interface RequestOptions {\n signal?: AbortSignal;\n timeoutMs?: number;\n operationName?: string;\n}\n\n/**\n * Layer 1 — a typed, dependency-free GraphQL transport over `fetch`.\n *\n * This is the SDK's entire networking surface. It understands auth, timeouts\n * and the GraphQL error envelope, and nothing about deployments. The workflow\n * layer ({@link import(\"./unfazed.js\").Unfazed}) is built strictly on top of\n * it, so you can always drop down here for any operation the workflow layer\n * does not wrap — the schema has ~660 fields and the workflow layer\n * deliberately covers only the agent-facing ones.\n *\n * ```ts\n * const client = new UnfazedClient(); // UNFZ_API_TOKEN, else `npx unfazed login`\n * const data = await client.query<{ whoami: { name: string } }, never>(\n * `query { whoami { name } }`,\n * );\n * ```\n */\nexport class UnfazedClient {\n /** Resolved GraphQL endpoint URL. */\n readonly endpoint: string;\n\n /** A fixed bearer token, or the stored login handing out refreshed ones. */\n readonly #auth: { token: string } | { login: StoredLogin };\n readonly #headers: Record<string, string>;\n readonly #timeoutMs: number;\n readonly #fetch: FetchLike;\n\n constructor(options: ClientOptions = {}) {\n const env = options.env ?? processEnv();\n\n const injected = options.fetch;\n if (injected) {\n this.#fetch = injected;\n } else {\n const globalFetch = (globalThis as { fetch?: unknown }).fetch;\n if (typeof globalFetch !== \"function\") {\n throw new UnfazedConfigError(\n \"unfazed: no global fetch. Use Bun, Node >= 20, or pass { fetch } to the client.\",\n );\n }\n this.#fetch = globalFetch.bind(globalThis) as FetchLike;\n }\n\n const ua = options.userAgent ?? `unfazed-sdk/${VERSION}`;\n\n const configured = configuredEndpoint(options, env);\n const token = options.token ?? env[TOKEN_ENV_VAR];\n if (token?.trim()) {\n this.#auth = { token: token.trim() };\n this.endpoint = configured ?? DEFAULT_ENDPOINT;\n } else {\n const stored =\n options.login === false\n ? {}\n : openStoredLogin({\n env,\n ...(configured ? { endpoint: configured } : {}),\n fetch: this.#fetch,\n // The edge refuses some generic agents; always name ourselves.\n userAgent: ua || `unfazed-sdk/${VERSION}`,\n });\n if (!stored.login) {\n throw new UnfazedAuthError(noTokenMessage(configured, stored.other?.endpoint));\n }\n this.#auth = { login: stored.login };\n this.endpoint = stored.login.endpoint;\n }\n\n this.#headers = { ...(options.headers ?? {}) };\n this.#timeoutMs = options.timeoutMs ?? 60_000;\n if (ua) this.#headers[\"User-Agent\"] = ua;\n }\n\n /**\n * Where the bearer token comes from: `\"token\"` (passed or\n * `UNFZ_API_TOKEN`) or `\"login\"` (the stored login of `npx unfazed login`).\n */\n get authSource(): \"token\" | \"login\" {\n return \"login\" in this.#auth ? \"login\" : \"token\";\n }\n\n /** Runs a query document. Identical wire behaviour to {@link mutate}. */\n query<TResult, TVariables>(\n document: GraphQLDocument<TResult, TVariables>,\n variables?: TVariables,\n options?: RequestOptions,\n ): Promise<TResult> {\n return this.request(document, variables, options);\n }\n\n /**\n * Runs a mutation document. Distinct from {@link query} for call-site\n * readability only — GraphQL over HTTP POST makes no distinction.\n */\n mutate<TResult, TVariables>(\n document: GraphQLDocument<TResult, TVariables>,\n variables?: TVariables,\n options?: RequestOptions,\n ): Promise<TResult> {\n return this.request(document, variables, options);\n }\n\n /**\n * The single code path behind {@link query} and {@link mutate}.\n *\n * Throws {@link UnfazedHTTPError} on non-2xx, {@link UnfazedGraphQLError}\n * when the body carries `errors`, {@link UnfazedTimeoutError} on timeout or\n * abort. Resolves to the `data` object on success.\n */\n async request<TResult = unknown>(\n document: string | { toString(): string },\n variables?: unknown,\n options: RequestOptions = {},\n ): Promise<TResult> {\n const timeoutMs = options.timeoutMs ?? this.#timeoutMs;\n\n // `document` may be a TypedDocumentString (a String object). JSON.stringify\n // would serialise that fine, but String(...) keeps the wire body a plain\n // primitive regardless of how the caller obtained the document.\n const body = JSON.stringify({\n query: String(document),\n ...(variables === undefined ? {} : { variables }),\n ...(options.operationName ? { operationName: options.operationName } : {}),\n });\n\n let token = await this.#token();\n let { response, text } = await this.#send(body, token, timeoutMs, options.signal);\n // A stored login's access token can be refused before its recorded\n // expiry (the clock, a sign-out elsewhere): refresh once and retry. A\n // fixed token has nothing to refresh.\n if (response.status === 401 && \"login\" in this.#auth) {\n token = await this.#auth.login.refresh(token);\n ({ response, text } = await this.#send(body, token, timeoutMs, options.signal));\n }\n\n if (!response.ok) {\n throw new UnfazedHTTPError(response.status, text, this.endpoint);\n }\n\n let parsed: { data?: TResult; errors?: GraphQLResponseError[] };\n try {\n parsed = JSON.parse(text) as typeof parsed;\n } catch {\n throw new UnfazedHTTPError(response.status, text, this.endpoint);\n }\n\n // A GraphQL 200 can still be a failure, and can be a PARTIAL failure: the\n // batched writeFiles document resolves each aliased field independently.\n // Attaching `data` to the error is what lets a caller see which writes\n // landed before the failing one.\n if (parsed.errors?.length) {\n throw new UnfazedGraphQLError(parsed.errors, parsed.data);\n }\n if (parsed.data === undefined || parsed.data === null) {\n throw new UnfazedGraphQLError(\n [{ message: \"response contained neither data nor errors\" }],\n undefined,\n );\n }\n return parsed.data;\n }\n\n #token(): Promise<string> {\n return \"login\" in this.#auth ? this.#auth.login.token() : Promise.resolve(this.#auth.token);\n }\n\n async #send(\n body: string,\n token: string,\n timeoutMs: number,\n external: AbortSignal | undefined,\n ): Promise<{ response: Awaited<ReturnType<FetchLike>>; text: string }> {\n const { signal, cleanup, didTimeout } = makeSignal(timeoutMs, external);\n // The deadline covers BOTH phases — headers AND body. A proxy that answers\n // with headers and then stalls the body is a real failure mode, and\n // stopping the clock at the end of `fetch()` would leave `response.text()`\n // hanging forever. `untilAborted` additionally races the signal itself, so\n // an injected transport that ignores `signal` still cannot outlive the\n // timeout.\n try {\n const response = await untilAborted(\n this.#fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n ...this.#headers,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body,\n ...(signal ? { signal } : {}),\n }),\n signal,\n );\n const text = await untilAborted(response.text(), signal);\n return { response, text };\n } catch (cause) {\n if (didTimeout()) {\n throw new UnfazedTimeoutError(`unfazed: request timed out after ${timeoutMs}ms`, { cause });\n }\n if (isAbort(cause)) {\n throw new UnfazedTimeoutError(\"unfazed: request aborted\", { cause });\n }\n throw cause;\n } finally {\n cleanup();\n }\n }\n}\n\n/**\n * The platform origin an endpoint is served from:\n * `https://unfazed.dev/query` → `https://unfazed.dev`. A path before `/query`\n * (a control plane mounted under a prefix) is kept.\n */\nexport function platformBaseUrl(endpoint: string): string {\n return trimSlash(endpoint).replace(/\\/(?:query|graphql)$/, \"\");\n}\n\n/** The endpoint the options or `UNFZ_API_URL` name, or undefined for the default. */\nexport function configuredEndpoint(\n options: Pick<ClientOptions, \"endpoint\" | \"baseUrl\">,\n env: Record<string, string | undefined>,\n): string | undefined {\n if (options.endpoint) return options.endpoint;\n if (options.baseUrl) return trimSlash(options.baseUrl) + GRAPHQL_PATH;\n const fromEnv = env[ENDPOINT_ENV_VAR];\n // UNFZ_API_URL is documented as the full /query URL and is used verbatim.\n // Tolerate a bare origin anyway, since that is the obvious mistake and\n // guessing right costs nothing.\n if (fromEnv) {\n const trimmed = trimSlash(fromEnv);\n return /\\/query$|\\/graphql$/.test(trimmed) ? trimmed : trimmed + GRAPHQL_PATH;\n }\n return undefined;\n}\n\nfunction noTokenMessage(endpoint: string | undefined, otherLogin: string | undefined): string {\n if (endpoint && otherLogin) {\n return (\n `unfazed: no API token for ${endpoint}: the stored login is for ${otherLogin}. ` +\n `Run \\`${ENDPOINT_ENV_VAR}=${endpoint} ${LOGIN_COMMAND}\\`, or pass { token } / set ${TOKEN_ENV_VAR}.`\n );\n }\n return (\n `unfazed: not signed in. Run \\`${LOGIN_COMMAND}\\` to sign in with your account, ` +\n `or pass { token } / set ${TOKEN_ENV_VAR} (an agent token: ${DEFAULT_BASE_URL}/auth.md).`\n );\n}\n\nexport function processEnv(): Record<string, string | undefined> {\n // Guarded: the SDK must also work in a browser or worker, where there is no\n // `process` at all.\n return (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env ?? {};\n}\n\nfunction trimSlash(u: string): string {\n return u.replace(/\\/+$/, \"\");\n}\n\nfunction isAbort(e: unknown): boolean {\n return typeof e === \"object\" && e !== null && (e as { name?: string }).name === \"AbortError\";\n}\n\nfunction abortError(): Error {\n const e = new Error(\"The operation was aborted.\");\n e.name = \"AbortError\";\n return e;\n}\n\n/**\n * Settles with `promise`, or rejects the moment `signal` aborts.\n *\n * A global `fetch` honours the signal for headers and body alike, but an\n * injected {@link FetchLike} — a proxy, an instrumented transport, a test\n * double — need not. Racing the signal makes the timeout an upper bound on the\n * whole request regardless of what the transport does with it. The loser's\n * rejection is swallowed by the already-settled promise rather than surfacing\n * as an unhandled rejection.\n */\nexport function untilAborted<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T> {\n if (!signal) return promise;\n if (signal.aborted) return Promise.reject(abortError());\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(abortError());\n signal.addEventListener(\"abort\", onAbort, { once: true });\n const done = () => signal.removeEventListener(\"abort\", onAbort);\n promise.then(\n (value) => {\n done();\n resolve(value);\n },\n (error: unknown) => {\n done();\n reject(error as Error);\n },\n );\n });\n}\n\n/**\n * Combines the request timeout with any caller-supplied signal.\n *\n * `didTimeout` is tracked separately from the abort so a caller's own\n * cancellation is not mislabelled as a timeout in the thrown message.\n */\nfunction makeSignal(\n timeoutMs: number,\n external?: AbortSignal,\n): { signal?: AbortSignal; cleanup: () => void; didTimeout: () => boolean } {\n if (!timeoutMs && !external) return { cleanup: () => {}, didTimeout: () => false };\n if (typeof AbortController !== \"function\") {\n return {\n ...(external ? { signal: external } : {}),\n cleanup: () => {},\n didTimeout: () => false,\n };\n }\n\n const controller = new AbortController();\n let timedOut = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n if (timeoutMs > 0) {\n timer = setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, timeoutMs);\n }\n\n const onExternalAbort = () => controller.abort();\n if (external) {\n if (external.aborted) controller.abort();\n else external.addEventListener(\"abort\", onExternalAbort, { once: true });\n }\n\n return {\n signal: controller.signal,\n cleanup: () => {\n if (timer !== undefined) clearTimeout(timer);\n external?.removeEventListener(\"abort\", onExternalAbort);\n },\n didTimeout: () => timedOut,\n };\n}\n"]}
package/dist/errors.d.ts CHANGED
@@ -154,6 +154,55 @@ export declare class UnfazedConflictError extends UnfazedError {
154
154
  /** The requested alias, deployment or release does not exist (or was pruned). */
155
155
  export declare class UnfazedNotFoundError extends UnfazedError {
156
156
  }
157
+ /**
158
+ * Why `oidc.token()` gave no token. The first six are the platform's own
159
+ * `error` codes (`POST /oidc/token`); the rest are the SDK's.
160
+ */
161
+ export type OIDCErrorCode =
162
+ /** 401: the platform refused the app's `UNFZ_STATE_TOKEN`. */
163
+ "invalid_token"
164
+ /** 400, or refused locally: not 1–256 printable ASCII characters without whitespace. */
165
+ | "invalid_audience"
166
+ /** 400, or refused locally: `ttlSeconds` is not a whole number from 60 to 3600. */
167
+ | "invalid_request"
168
+ /** 429, after the retries (or with a `Retry-After` longer than the SDK waits). */
169
+ | "rate_limited"
170
+ /** 404: the platform issues no OIDC tokens to this app. */
171
+ | "oidc_disabled"
172
+ /** 503 (or a gateway's 502/504), after the retries. */
173
+ | "temporarily_unavailable"
174
+ /** `UNFZ_STATE_TOKEN` is not set: not running inside an unfazed app. */
175
+ | "not_in_app"
176
+ /** Network failures (or attempts past `timeoutMs`) outlasted the retries. */
177
+ | "unreachable"
178
+ /** A 200 without a usable `token` and `expires_at`. */
179
+ | "invalid_response"
180
+ /** Any other status, without a code. */
181
+ | "http_error";
182
+ /**
183
+ * `oidc.token()` (or `tokenResponse`, or a `fetchWithToken` fetch) got no
184
+ * token. `code` is the platform's `error` (`invalid_audience`, `rate_limited`,
185
+ * …), passed through as sent, or one of the SDK's own ({@link OIDCErrorCode});
186
+ * `description` is the platform's `error_description`.
187
+ */
188
+ export declare class UnfazedOIDCError extends UnfazedError {
189
+ readonly code: OIDCErrorCode | (string & {});
190
+ /** HTTP status from the platform; undefined for a refusal made locally. */
191
+ readonly status: number | undefined;
192
+ /** The platform's `error_description`. */
193
+ readonly description: string | undefined;
194
+ /** Asking again later may succeed (`rate_limited`, `temporarily_unavailable`, `unreachable`). */
195
+ readonly retryable: boolean;
196
+ /** The platform's `Retry-After`, in ms, when it sent one. */
197
+ readonly retryAfterMs: number | undefined;
198
+ constructor(code: OIDCErrorCode | (string & {}), message: string, options?: {
199
+ status?: number;
200
+ description?: string;
201
+ retryable?: boolean;
202
+ retryAfterMs?: number;
203
+ cause?: unknown;
204
+ });
205
+ }
157
206
  /**
158
207
  * What the `unfazed` CLI backend's World (`createWorld()`) throws.
159
208
  *
package/dist/errors.js CHANGED
@@ -193,6 +193,31 @@ export class UnfazedConflictError extends UnfazedError {
193
193
  /** The requested alias, deployment or release does not exist (or was pruned). */
194
194
  export class UnfazedNotFoundError extends UnfazedError {
195
195
  }
196
+ /**
197
+ * `oidc.token()` (or `tokenResponse`, or a `fetchWithToken` fetch) got no
198
+ * token. `code` is the platform's `error` (`invalid_audience`, `rate_limited`,
199
+ * …), passed through as sent, or one of the SDK's own ({@link OIDCErrorCode});
200
+ * `description` is the platform's `error_description`.
201
+ */
202
+ export class UnfazedOIDCError extends UnfazedError {
203
+ code;
204
+ /** HTTP status from the platform; undefined for a refusal made locally. */
205
+ status;
206
+ /** The platform's `error_description`. */
207
+ description;
208
+ /** Asking again later may succeed (`rate_limited`, `temporarily_unavailable`, `unreachable`). */
209
+ retryable;
210
+ /** The platform's `Retry-After`, in ms, when it sent one. */
211
+ retryAfterMs;
212
+ constructor(code, message, options = {}) {
213
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
214
+ this.code = code;
215
+ this.status = options.status;
216
+ this.description = options.description;
217
+ this.retryable = options.retryable ?? false;
218
+ this.retryAfterMs = options.retryAfterMs;
219
+ }
220
+ }
196
221
  /**
197
222
  * What the `unfazed` CLI backend's World (`createWorld()`) throws.
198
223
  *
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Error taxonomy.\n *\n * Everything the SDK throws is an {@link UnfazedError}, so a code-mode script\n * can `catch (e) { if (e instanceof UnfazedError) ... }` and still\n * discriminate on the subclass to decide whether to retry.\n */\n\n/** A single entry from a GraphQL response's `errors` array. */\nexport interface GraphQLResponseError {\n message: string;\n path?: (string | number)[];\n extensions?: Record<string, unknown>;\n}\n\n/** Base class for everything this SDK throws. */\nexport class UnfazedError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = new.target.name;\n }\n}\n\n/** No token was passed and no token environment variable was set. */\nexport class UnfazedAuthError extends UnfazedError {}\n\n/** The runtime has no global `fetch` and none was injected. */\nexport class UnfazedConfigError extends UnfazedError {}\n\n/**\n * The endpoint answered with a non-2xx status. `status` is the HTTP status and\n * `body` the response text, which is where the platform reports non-GraphQL\n * failures such as an expired or wrong-scope bearer token.\n */\nexport class UnfazedHTTPError extends UnfazedError {\n readonly status: number;\n readonly body: string;\n\n constructor(status: number, body: string, url: string) {\n super(`unfazed: HTTP ${status} from ${url}${body ? `: ${truncate(body)}` : \"\"}`);\n this.status = status;\n this.body = body;\n }\n}\n\n/**\n * The endpoint answered 200 with a GraphQL `errors` array.\n *\n * Partial `data` is preserved deliberately: the batched writeFiles document\n * resolves each aliased field independently, so one path can fail while the\n * others commit, and the caller needs to see which landed.\n */\nexport class UnfazedGraphQLError extends UnfazedError {\n readonly errors: GraphQLResponseError[];\n readonly data: unknown;\n\n constructor(errors: GraphQLResponseError[], data: unknown) {\n const summary = errors.map((e) => e.message).join(\"; \") || \"unknown GraphQL error\";\n super(`unfazed: ${summary}`);\n this.errors = errors;\n this.data = data;\n }\n}\n\n/**\n * The server refused a delete that would take apps offline without the call\n * naming exactly those apps, and deleted nothing.\n *\n * `deleteWorkspace` with `withDeployments` takes down every app the workspace\n * serves, so its `confirmAliases` must equal the workspace's live apps — no\n * more, no fewer. A missing or extra alias, or an app that went live after the\n * check, is refused. The message is the server's, verbatim: it lists each live\n * app with its custom domains and says how to name them. Look the apps up\n * deliberately with `unfazed.liveApps(workspaceId)`, decide, and delete again,\n * or delete one app with `unfazed.deleteApp`.\n *\n * Still an {@link UnfazedGraphQLError}, so existing `catch` sites keep matching.\n */\nexport class UnfazedUnconfirmedAppsError extends UnfazedGraphQLError {\n /** The workspace whose delete was refused. */\n readonly workspaceId: string;\n /** The aliases the refused call named. */\n readonly confirmAliases: readonly string[];\n\n constructor(\n workspaceId: string,\n confirmAliases: readonly string[],\n errors: GraphQLResponseError[],\n data: unknown,\n ) {\n super(errors, data);\n this.workspaceId = workspaceId;\n this.confirmAliases = confirmAliases;\n }\n}\n\n/**\n * How the server opens every refusal of a delete whose named apps do not match\n * the live ones (usecases.UnconfirmedAppsError, and the race where an app went\n * live after the check). Anchored at the start, so a message that merely\n * mentions the phrase is not mistaken for one.\n */\nconst NOTHING_WAS_DELETED = /^nothing was deleted:/i;\n\n/** Did the server refuse a delete because the named apps are not exactly the live ones? */\nexport function isUnconfirmedAppsRefusal(error: unknown): error is UnfazedGraphQLError {\n return (\n error instanceof UnfazedGraphQLError &&\n error.errors.some((e) => NOTHING_WAS_DELETED.test(e.message.trim()))\n );\n}\n\n/** A request exceeded `timeoutMs`, or the caller's AbortSignal fired. */\nexport class UnfazedTimeoutError extends UnfazedError {}\n\n/**\n * `writeFiles` wrote some files and not others.\n *\n * Writes are not transactional, exactly as MCP `write_workspace_files`: the\n * files listed in `result.files` with `ok: true` ARE written (and, on an app\n * with platform git, committed), the others are not. `result` is the whole\n * answer, `failed` names the paths that did not land and why. Retry only\n * those paths. Pass `allowPartial: true` to get the result instead of this\n * error.\n */\nexport class UnfazedWriteError extends UnfazedError {\n /** What the call wrote: every file's outcome and the commit. */\n readonly result: unknown;\n /** The paths that were not written, with the server's reason. */\n readonly failed: { path: string; error: string }[];\n\n constructor(result: unknown, failed: { path: string; error: string }[]) {\n super(\n `unfazed: ${failed.length} file(s) were not written (the others were): ` +\n failed.map((f) => `${f.path}: ${f.error}`).join(\"; \"),\n );\n this.result = result;\n this.failed = failed;\n }\n}\n\n/**\n * Did the platform refuse a long wait because the caller (or the server) has\n * too many in progress? Nothing was waited on; polling is the way on.\n */\nexport function isTooManyWaits(error: unknown): boolean {\n return (\n error instanceof UnfazedGraphQLError &&\n error.errors.some((e) => /waits in progress|long wait per operation/i.test(e.message))\n );\n}\n\n/**\n * Did the server refuse the document because it does not know `field` (a\n * platform older than this SDK)? The SDK uses it to fall back to the older\n * way of doing the same thing, never to hide a real error.\n */\nexport function isUnsupportedField(error: unknown, field: string): boolean {\n if (!(error instanceof UnfazedGraphQLError)) return false;\n const quoted = `\"${field}\"`;\n return error.errors.some(\n (e) =>\n e.message.includes(quoted) &&\n /Cannot query field|is not defined by type|Unknown argument|Unknown type|not defined on/i.test(e.message),\n );\n}\n\n/**\n * Waiting on a deploy ended badly.\n *\n * `reason` separates the two outcomes because they need different handling:\n * FAILED means the build finished and is broken (read `deployment.error` and\n * fix the source — retrying will not help), TIMEOUT means it is still BUILDING\n * and polling longer may still succeed.\n */\nexport class UnfazedDeployError extends UnfazedError {\n readonly reason: \"FAILED\" | \"TIMEOUT\";\n readonly deploymentId: string;\n /** Last deployment snapshot observed before giving up. */\n readonly deployment: unknown;\n\n constructor(\n reason: \"FAILED\" | \"TIMEOUT\",\n deploymentId: string,\n deployment: unknown,\n message: string,\n ) {\n super(message);\n this.reason = reason;\n this.deploymentId = deploymentId;\n this.deployment = deployment;\n }\n}\n\n/**\n * Waiting on a run timed out.\n *\n * Unlike {@link UnfazedDeployError}, a run's FAILED status is never thrown\n * here: {@link import(\"./unfazed.js\").Unfazed.waitForRun} resolves on\n * SUCCEEDED, FAILED and CANCELLED alike, because a run that executed and\n * exited non-zero is a completed answer — read `run.error`/`run.exitCode` —\n * not a broken build the way a FAILED deployment is. TIMEOUT is the only\n * outcome still worth an exception: the run is, as far as the last poll\n * showed, still QUEUED or RUNNING, and waiting longer may yet succeed.\n */\nexport class UnfazedRunError extends UnfazedError {\n readonly reason: \"TIMEOUT\";\n readonly runId: string;\n /** Last run snapshot observed before giving up. */\n readonly run: unknown;\n\n constructor(reason: \"TIMEOUT\", runId: string, run: unknown, message: string) {\n super(message);\n this.reason = reason;\n this.runId = runId;\n this.run = run;\n }\n}\n\n/**\n * A rollback's compare-and-swap guard was rejected: the alias moved between\n * reading the current release and swapping it.\n *\n * Recovery is always \"re-list releases and rebuild the guard\" — never \"reuse\n * the old tuple\", because the server checks the holder id, epoch AND opaque\n * revision to close same-id ABA. `Unfazed.rollback` does that re-list for you\n * once by default.\n */\nexport class UnfazedConflictError extends UnfazedError {\n readonly errors: GraphQLResponseError[];\n\n constructor(message: string, errors: GraphQLResponseError[]) {\n super(message);\n this.errors = errors;\n }\n}\n\n/** The requested alias, deployment or release does not exist (or was pruned). */\nexport class UnfazedNotFoundError extends UnfazedError {}\n\n/**\n * What the `unfazed` CLI backend's World (`createWorld()`) throws.\n *\n * Its `name` is the Workflow SDK error a World would throw in its place\n * (`WorkflowWorldError`, `WorkflowRunNotFoundError`, `HookNotFoundError`),\n * because Workflow SDK's CLI and web UI pick their message by `name`,\n * `status` and `code`: a missing run reads as a 404 there, an argument the\n * backend cannot serve (`code: \"INVALID_ARGUMENT\"`) prints as given.\n * `instanceof UnfazedError` still holds.\n */\nexport class UnfazedInspectError extends UnfazedError {\n /** HTTP-like status: 404 for a missing run, step, event, hook or app; 401/403 for a refused token. */\n readonly status: number | undefined;\n /** `INVALID_ARGUMENT`, `READ_ONLY`, or undefined. */\n readonly code: string | undefined;\n /** The run, for `WorkflowRunNotFoundError`. */\n readonly runId: string | undefined;\n\n constructor(\n message: string,\n options: {\n name?: \"WorkflowWorldError\" | \"WorkflowRunNotFoundError\" | \"HookNotFoundError\";\n status?: number;\n code?: string;\n runId?: string;\n cause?: unknown;\n } = {},\n ) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = options.name ?? \"WorkflowWorldError\";\n this.status = options.status;\n this.code = options.code;\n this.runId = options.runId;\n }\n}\n\n/**\n * The server's exact \"no such workspace\" sentinel.\n *\n * A missing workspace and a workspace belonging to another tenant return the\n * SAME message on purpose, so the API cannot be used to probe for someone\n * else's ids. Treat it as \"not yours to see\", not strictly as \"absent\".\n */\nexport const WORKSPACE_NOT_FOUND = \"workspace was not found\";\n\n/** The server's exact \"no such deployment\" sentinel. See {@link WORKSPACE_NOT_FOUND}. */\nexport const DEPLOYMENT_NOT_FOUND = \"deployment was not found\";\n\n/**\n * Does this error carry EXACTLY the given not-found sentinel?\n *\n * Exact equality, deliberately. The tempting version — \"does any message\n * contain 'not found'\" — also swallows `release not found in history`,\n * `integration not found` and every future message that happens to use the\n * phrase, turning real failures into a silent `false` from a delete. These two\n * strings are stable authz sentinels with test coverage on the server side;\n * anything else is a genuine error and must keep throwing.\n */\nexport function isNotFoundSentinel(error: unknown, sentinel: string): boolean {\n return (\n error instanceof UnfazedGraphQLError &&\n error.errors.some((e) => e.message.trim() === sentinel)\n );\n}\n\n/**\n * The server's \"no such app here\" refusal of `deleteApp`:\n * `app \"<alias>\" was not found in workspace \"<workspace name>\"`. Anchored at\n * both ends, like the exact sentinels above.\n */\nconst APP_NOT_FOUND = /^app \"(.+)\" was not found in workspace \".*\"$/;\n\n/**\n * Does this error say that exactly `alias` is not an app of the workspace?\n *\n * The workspace name in the message varies, so this matches the shape, then\n * the alias itself: a refusal about a different alias still throws.\n */\nexport function isAppNotFound(error: unknown, alias: string): boolean {\n return (\n error instanceof UnfazedGraphQLError &&\n error.errors.some((e) => APP_NOT_FOUND.exec(e.message.trim())?.[1] === alias.trim())\n );\n}\n\n/**\n * Message shape of the server's per-owner duplicate workspace name error:\n * `a workspace named \"x\" already exists; reuse it (id <uuid>) or pick a\n * different name`.\n *\n * Used only to turn a lost `ensureWorkspace` race into a re-read. Requiring\n * both \"workspace\" and \"already exists\" in one clause keeps it off unrelated\n * conflicts (a duplicate ALIAS, say), and a false negative is safe: the\n * caller just gets the server's error verbatim.\n */\nconst DUPLICATE_WORKSPACE_NAME = /\\bworkspace\\b[^.;]{0,80}\\balready exists\\b/i;\n\n/** Did `createWorkspace` fail because the name is already taken by this owner? */\nexport function isDuplicateWorkspaceName(error: unknown): boolean {\n return (\n error instanceof UnfazedGraphQLError &&\n error.errors.some((e) => DUPLICATE_WORKSPACE_NAME.test(e.message))\n );\n}\n\nfunction truncate(s: string, max = 500): string {\n return s.length <= max ? s : `${s.slice(0, max)}…`;\n}\n\n/**\n * Message shapes that mean \"the alias moved under you\".\n *\n * The first two are the platform's ACTUAL strings. A rejected rollback guard\n * returns `store.ErrAliasMoved` verbatim — every guard point in the rollback\n * and adopt paths returns that one sentinel unwrapped — and the staged-service\n * rollback wraps the same guard with its own prefix. The rest are defensive:\n * the guard ARGUMENTS named back at you, or the alias-revision concept in any\n * spelling, or `alias` sitting in the same clause as a conflict verb, so a\n * rewording on the server side does not silently disable the retry.\n *\n * Deliberately NOT matched: a bare \"conflict\", \"changed\" or \"stale\" anywhere in\n * any message. Those fire on errors that have nothing to do with the alias\n * compare-and-swap, and a false positive is not free — it spends a pointless\n * re-list AND rewraps an unrelated failure as {@link UnfazedConflictError},\n * whose message would then blame concurrent deploys for, say, a permission\n * problem.\n */\nconst CAS_PATTERNS: RegExp[] = [\n /alias was repointed by another change/i,\n /became stale before alias change/i,\n /expected[\\s_-]*current/i,\n /alias[\\s_-]*revision/i,\n /\\balias\\b[^.;]{0,80}\\b(?:moved|repointed|changed|conflicts?|stale|mismatch)\\b/i,\n /\\b(?:moved|repointed|changed|conflicts?|stale|mismatch)\\b[^.;]{0,80}\\balias\\b/i,\n];\n\n/**\n * Heuristic: does this GraphQL error set look like a rejected compare-and-swap?\n *\n * The server carries no machine-readable code in `extensions` — its error\n * presenter returns gqlgen's default untouched — so this has to match message\n * text. It is used ONLY to decide whether to re-list and retry once: a false\n * negative costs a surfaced error the caller can retry by hand, a false\n * positive costs an extra read and a misleading error class. Neither can\n * promote the wrong release, because the retry rebuilds the guard from a fresh\n * read and the server re-validates it.\n */\nexport function looksLikeCASConflict(errors: GraphQLResponseError[]): boolean {\n return errors.some((e) => CAS_PATTERNS.some((pattern) => pattern.test(e.message)));\n}\n"]}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Error taxonomy.\n *\n * Everything the SDK throws is an {@link UnfazedError}, so a code-mode script\n * can `catch (e) { if (e instanceof UnfazedError) ... }` and still\n * discriminate on the subclass to decide whether to retry.\n */\n\n/** A single entry from a GraphQL response's `errors` array. */\nexport interface GraphQLResponseError {\n message: string;\n path?: (string | number)[];\n extensions?: Record<string, unknown>;\n}\n\n/** Base class for everything this SDK throws. */\nexport class UnfazedError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = new.target.name;\n }\n}\n\n/** No token was passed and no token environment variable was set. */\nexport class UnfazedAuthError extends UnfazedError {}\n\n/** The runtime has no global `fetch` and none was injected. */\nexport class UnfazedConfigError extends UnfazedError {}\n\n/**\n * The endpoint answered with a non-2xx status. `status` is the HTTP status and\n * `body` the response text, which is where the platform reports non-GraphQL\n * failures such as an expired or wrong-scope bearer token.\n */\nexport class UnfazedHTTPError extends UnfazedError {\n readonly status: number;\n readonly body: string;\n\n constructor(status: number, body: string, url: string) {\n super(`unfazed: HTTP ${status} from ${url}${body ? `: ${truncate(body)}` : \"\"}`);\n this.status = status;\n this.body = body;\n }\n}\n\n/**\n * The endpoint answered 200 with a GraphQL `errors` array.\n *\n * Partial `data` is preserved deliberately: the batched writeFiles document\n * resolves each aliased field independently, so one path can fail while the\n * others commit, and the caller needs to see which landed.\n */\nexport class UnfazedGraphQLError extends UnfazedError {\n readonly errors: GraphQLResponseError[];\n readonly data: unknown;\n\n constructor(errors: GraphQLResponseError[], data: unknown) {\n const summary = errors.map((e) => e.message).join(\"; \") || \"unknown GraphQL error\";\n super(`unfazed: ${summary}`);\n this.errors = errors;\n this.data = data;\n }\n}\n\n/**\n * The server refused a delete that would take apps offline without the call\n * naming exactly those apps, and deleted nothing.\n *\n * `deleteWorkspace` with `withDeployments` takes down every app the workspace\n * serves, so its `confirmAliases` must equal the workspace's live apps — no\n * more, no fewer. A missing or extra alias, or an app that went live after the\n * check, is refused. The message is the server's, verbatim: it lists each live\n * app with its custom domains and says how to name them. Look the apps up\n * deliberately with `unfazed.liveApps(workspaceId)`, decide, and delete again,\n * or delete one app with `unfazed.deleteApp`.\n *\n * Still an {@link UnfazedGraphQLError}, so existing `catch` sites keep matching.\n */\nexport class UnfazedUnconfirmedAppsError extends UnfazedGraphQLError {\n /** The workspace whose delete was refused. */\n readonly workspaceId: string;\n /** The aliases the refused call named. */\n readonly confirmAliases: readonly string[];\n\n constructor(\n workspaceId: string,\n confirmAliases: readonly string[],\n errors: GraphQLResponseError[],\n data: unknown,\n ) {\n super(errors, data);\n this.workspaceId = workspaceId;\n this.confirmAliases = confirmAliases;\n }\n}\n\n/**\n * How the server opens every refusal of a delete whose named apps do not match\n * the live ones (usecases.UnconfirmedAppsError, and the race where an app went\n * live after the check). Anchored at the start, so a message that merely\n * mentions the phrase is not mistaken for one.\n */\nconst NOTHING_WAS_DELETED = /^nothing was deleted:/i;\n\n/** Did the server refuse a delete because the named apps are not exactly the live ones? */\nexport function isUnconfirmedAppsRefusal(error: unknown): error is UnfazedGraphQLError {\n return (\n error instanceof UnfazedGraphQLError &&\n error.errors.some((e) => NOTHING_WAS_DELETED.test(e.message.trim()))\n );\n}\n\n/** A request exceeded `timeoutMs`, or the caller's AbortSignal fired. */\nexport class UnfazedTimeoutError extends UnfazedError {}\n\n/**\n * `writeFiles` wrote some files and not others.\n *\n * Writes are not transactional, exactly as MCP `write_workspace_files`: the\n * files listed in `result.files` with `ok: true` ARE written (and, on an app\n * with platform git, committed), the others are not. `result` is the whole\n * answer, `failed` names the paths that did not land and why. Retry only\n * those paths. Pass `allowPartial: true` to get the result instead of this\n * error.\n */\nexport class UnfazedWriteError extends UnfazedError {\n /** What the call wrote: every file's outcome and the commit. */\n readonly result: unknown;\n /** The paths that were not written, with the server's reason. */\n readonly failed: { path: string; error: string }[];\n\n constructor(result: unknown, failed: { path: string; error: string }[]) {\n super(\n `unfazed: ${failed.length} file(s) were not written (the others were): ` +\n failed.map((f) => `${f.path}: ${f.error}`).join(\"; \"),\n );\n this.result = result;\n this.failed = failed;\n }\n}\n\n/**\n * Did the platform refuse a long wait because the caller (or the server) has\n * too many in progress? Nothing was waited on; polling is the way on.\n */\nexport function isTooManyWaits(error: unknown): boolean {\n return (\n error instanceof UnfazedGraphQLError &&\n error.errors.some((e) => /waits in progress|long wait per operation/i.test(e.message))\n );\n}\n\n/**\n * Did the server refuse the document because it does not know `field` (a\n * platform older than this SDK)? The SDK uses it to fall back to the older\n * way of doing the same thing, never to hide a real error.\n */\nexport function isUnsupportedField(error: unknown, field: string): boolean {\n if (!(error instanceof UnfazedGraphQLError)) return false;\n const quoted = `\"${field}\"`;\n return error.errors.some(\n (e) =>\n e.message.includes(quoted) &&\n /Cannot query field|is not defined by type|Unknown argument|Unknown type|not defined on/i.test(e.message),\n );\n}\n\n/**\n * Waiting on a deploy ended badly.\n *\n * `reason` separates the two outcomes because they need different handling:\n * FAILED means the build finished and is broken (read `deployment.error` and\n * fix the source — retrying will not help), TIMEOUT means it is still BUILDING\n * and polling longer may still succeed.\n */\nexport class UnfazedDeployError extends UnfazedError {\n readonly reason: \"FAILED\" | \"TIMEOUT\";\n readonly deploymentId: string;\n /** Last deployment snapshot observed before giving up. */\n readonly deployment: unknown;\n\n constructor(\n reason: \"FAILED\" | \"TIMEOUT\",\n deploymentId: string,\n deployment: unknown,\n message: string,\n ) {\n super(message);\n this.reason = reason;\n this.deploymentId = deploymentId;\n this.deployment = deployment;\n }\n}\n\n/**\n * Waiting on a run timed out.\n *\n * Unlike {@link UnfazedDeployError}, a run's FAILED status is never thrown\n * here: {@link import(\"./unfazed.js\").Unfazed.waitForRun} resolves on\n * SUCCEEDED, FAILED and CANCELLED alike, because a run that executed and\n * exited non-zero is a completed answer — read `run.error`/`run.exitCode` —\n * not a broken build the way a FAILED deployment is. TIMEOUT is the only\n * outcome still worth an exception: the run is, as far as the last poll\n * showed, still QUEUED or RUNNING, and waiting longer may yet succeed.\n */\nexport class UnfazedRunError extends UnfazedError {\n readonly reason: \"TIMEOUT\";\n readonly runId: string;\n /** Last run snapshot observed before giving up. */\n readonly run: unknown;\n\n constructor(reason: \"TIMEOUT\", runId: string, run: unknown, message: string) {\n super(message);\n this.reason = reason;\n this.runId = runId;\n this.run = run;\n }\n}\n\n/**\n * A rollback's compare-and-swap guard was rejected: the alias moved between\n * reading the current release and swapping it.\n *\n * Recovery is always \"re-list releases and rebuild the guard\" — never \"reuse\n * the old tuple\", because the server checks the holder id, epoch AND opaque\n * revision to close same-id ABA. `Unfazed.rollback` does that re-list for you\n * once by default.\n */\nexport class UnfazedConflictError extends UnfazedError {\n readonly errors: GraphQLResponseError[];\n\n constructor(message: string, errors: GraphQLResponseError[]) {\n super(message);\n this.errors = errors;\n }\n}\n\n/** The requested alias, deployment or release does not exist (or was pruned). */\nexport class UnfazedNotFoundError extends UnfazedError {}\n\n/**\n * Why `oidc.token()` gave no token. The first six are the platform's own\n * `error` codes (`POST /oidc/token`); the rest are the SDK's.\n */\nexport type OIDCErrorCode =\n /** 401: the platform refused the app's `UNFZ_STATE_TOKEN`. */\n | \"invalid_token\"\n /** 400, or refused locally: not 1–256 printable ASCII characters without whitespace. */\n | \"invalid_audience\"\n /** 400, or refused locally: `ttlSeconds` is not a whole number from 60 to 3600. */\n | \"invalid_request\"\n /** 429, after the retries (or with a `Retry-After` longer than the SDK waits). */\n | \"rate_limited\"\n /** 404: the platform issues no OIDC tokens to this app. */\n | \"oidc_disabled\"\n /** 503 (or a gateway's 502/504), after the retries. */\n | \"temporarily_unavailable\"\n /** `UNFZ_STATE_TOKEN` is not set: not running inside an unfazed app. */\n | \"not_in_app\"\n /** Network failures (or attempts past `timeoutMs`) outlasted the retries. */\n | \"unreachable\"\n /** A 200 without a usable `token` and `expires_at`. */\n | \"invalid_response\"\n /** Any other status, without a code. */\n | \"http_error\";\n\n/**\n * `oidc.token()` (or `tokenResponse`, or a `fetchWithToken` fetch) got no\n * token. `code` is the platform's `error` (`invalid_audience`, `rate_limited`,\n * …), passed through as sent, or one of the SDK's own ({@link OIDCErrorCode});\n * `description` is the platform's `error_description`.\n */\nexport class UnfazedOIDCError extends UnfazedError {\n readonly code: OIDCErrorCode | (string & {});\n /** HTTP status from the platform; undefined for a refusal made locally. */\n readonly status: number | undefined;\n /** The platform's `error_description`. */\n readonly description: string | undefined;\n /** Asking again later may succeed (`rate_limited`, `temporarily_unavailable`, `unreachable`). */\n readonly retryable: boolean;\n /** The platform's `Retry-After`, in ms, when it sent one. */\n readonly retryAfterMs: number | undefined;\n\n constructor(\n code: OIDCErrorCode | (string & {}),\n message: string,\n options: {\n status?: number;\n description?: string;\n retryable?: boolean;\n retryAfterMs?: number;\n cause?: unknown;\n } = {},\n ) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.code = code;\n this.status = options.status;\n this.description = options.description;\n this.retryable = options.retryable ?? false;\n this.retryAfterMs = options.retryAfterMs;\n }\n}\n\n/**\n * What the `unfazed` CLI backend's World (`createWorld()`) throws.\n *\n * Its `name` is the Workflow SDK error a World would throw in its place\n * (`WorkflowWorldError`, `WorkflowRunNotFoundError`, `HookNotFoundError`),\n * because Workflow SDK's CLI and web UI pick their message by `name`,\n * `status` and `code`: a missing run reads as a 404 there, an argument the\n * backend cannot serve (`code: \"INVALID_ARGUMENT\"`) prints as given.\n * `instanceof UnfazedError` still holds.\n */\nexport class UnfazedInspectError extends UnfazedError {\n /** HTTP-like status: 404 for a missing run, step, event, hook or app; 401/403 for a refused token. */\n readonly status: number | undefined;\n /** `INVALID_ARGUMENT`, `READ_ONLY`, or undefined. */\n readonly code: string | undefined;\n /** The run, for `WorkflowRunNotFoundError`. */\n readonly runId: string | undefined;\n\n constructor(\n message: string,\n options: {\n name?: \"WorkflowWorldError\" | \"WorkflowRunNotFoundError\" | \"HookNotFoundError\";\n status?: number;\n code?: string;\n runId?: string;\n cause?: unknown;\n } = {},\n ) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = options.name ?? \"WorkflowWorldError\";\n this.status = options.status;\n this.code = options.code;\n this.runId = options.runId;\n }\n}\n\n/**\n * The server's exact \"no such workspace\" sentinel.\n *\n * A missing workspace and a workspace belonging to another tenant return the\n * SAME message on purpose, so the API cannot be used to probe for someone\n * else's ids. Treat it as \"not yours to see\", not strictly as \"absent\".\n */\nexport const WORKSPACE_NOT_FOUND = \"workspace was not found\";\n\n/** The server's exact \"no such deployment\" sentinel. See {@link WORKSPACE_NOT_FOUND}. */\nexport const DEPLOYMENT_NOT_FOUND = \"deployment was not found\";\n\n/**\n * Does this error carry EXACTLY the given not-found sentinel?\n *\n * Exact equality, deliberately. The tempting version — \"does any message\n * contain 'not found'\" — also swallows `release not found in history`,\n * `integration not found` and every future message that happens to use the\n * phrase, turning real failures into a silent `false` from a delete. These two\n * strings are stable authz sentinels with test coverage on the server side;\n * anything else is a genuine error and must keep throwing.\n */\nexport function isNotFoundSentinel(error: unknown, sentinel: string): boolean {\n return (\n error instanceof UnfazedGraphQLError &&\n error.errors.some((e) => e.message.trim() === sentinel)\n );\n}\n\n/**\n * The server's \"no such app here\" refusal of `deleteApp`:\n * `app \"<alias>\" was not found in workspace \"<workspace name>\"`. Anchored at\n * both ends, like the exact sentinels above.\n */\nconst APP_NOT_FOUND = /^app \"(.+)\" was not found in workspace \".*\"$/;\n\n/**\n * Does this error say that exactly `alias` is not an app of the workspace?\n *\n * The workspace name in the message varies, so this matches the shape, then\n * the alias itself: a refusal about a different alias still throws.\n */\nexport function isAppNotFound(error: unknown, alias: string): boolean {\n return (\n error instanceof UnfazedGraphQLError &&\n error.errors.some((e) => APP_NOT_FOUND.exec(e.message.trim())?.[1] === alias.trim())\n );\n}\n\n/**\n * Message shape of the server's per-owner duplicate workspace name error:\n * `a workspace named \"x\" already exists; reuse it (id <uuid>) or pick a\n * different name`.\n *\n * Used only to turn a lost `ensureWorkspace` race into a re-read. Requiring\n * both \"workspace\" and \"already exists\" in one clause keeps it off unrelated\n * conflicts (a duplicate ALIAS, say), and a false negative is safe: the\n * caller just gets the server's error verbatim.\n */\nconst DUPLICATE_WORKSPACE_NAME = /\\bworkspace\\b[^.;]{0,80}\\balready exists\\b/i;\n\n/** Did `createWorkspace` fail because the name is already taken by this owner? */\nexport function isDuplicateWorkspaceName(error: unknown): boolean {\n return (\n error instanceof UnfazedGraphQLError &&\n error.errors.some((e) => DUPLICATE_WORKSPACE_NAME.test(e.message))\n );\n}\n\nfunction truncate(s: string, max = 500): string {\n return s.length <= max ? s : `${s.slice(0, max)}…`;\n}\n\n/**\n * Message shapes that mean \"the alias moved under you\".\n *\n * The first two are the platform's ACTUAL strings. A rejected rollback guard\n * returns `store.ErrAliasMoved` verbatim — every guard point in the rollback\n * and adopt paths returns that one sentinel unwrapped — and the staged-service\n * rollback wraps the same guard with its own prefix. The rest are defensive:\n * the guard ARGUMENTS named back at you, or the alias-revision concept in any\n * spelling, or `alias` sitting in the same clause as a conflict verb, so a\n * rewording on the server side does not silently disable the retry.\n *\n * Deliberately NOT matched: a bare \"conflict\", \"changed\" or \"stale\" anywhere in\n * any message. Those fire on errors that have nothing to do with the alias\n * compare-and-swap, and a false positive is not free — it spends a pointless\n * re-list AND rewraps an unrelated failure as {@link UnfazedConflictError},\n * whose message would then blame concurrent deploys for, say, a permission\n * problem.\n */\nconst CAS_PATTERNS: RegExp[] = [\n /alias was repointed by another change/i,\n /became stale before alias change/i,\n /expected[\\s_-]*current/i,\n /alias[\\s_-]*revision/i,\n /\\balias\\b[^.;]{0,80}\\b(?:moved|repointed|changed|conflicts?|stale|mismatch)\\b/i,\n /\\b(?:moved|repointed|changed|conflicts?|stale|mismatch)\\b[^.;]{0,80}\\balias\\b/i,\n];\n\n/**\n * Heuristic: does this GraphQL error set look like a rejected compare-and-swap?\n *\n * The server carries no machine-readable code in `extensions` — its error\n * presenter returns gqlgen's default untouched — so this has to match message\n * text. It is used ONLY to decide whether to re-list and retry once: a false\n * negative costs a surfaced error the caller can retry by hand, a false\n * positive costs an extra read and a misleading error class. Neither can\n * promote the wrong release, because the retry rebuilds the guard from a fresh\n * read and the server re-validates it.\n */\nexport function looksLikeCASConflict(errors: GraphQLResponseError[]): boolean {\n return errors.some((e) => CAS_PATTERNS.some((pattern) => pattern.test(e.message)));\n}\n"]}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The eve sandbox daemon: runs as root inside an unfazed box and gives the
3
+ * apps it trusts sandboxes, one Unix user per eve session.
4
+ *
5
+ * It serves the box's :3000, which unfazed publishes at the box's URL, and
6
+ * accepts only workload identity tokens (OIDC, issuer
7
+ * `https://unfazed.dev/oidc`) whose audience is that URL and whose app is on
8
+ * its allow list. A session's commands and file operations run as that
9
+ * session's user, so the kernel keeps sessions apart; the daemon itself only
10
+ * creates and removes users and relays bytes.
11
+ *
12
+ * Started by a box boot hook (`/data/boot.d/50-eve-sandbox`, see setup.ts):
13
+ *
14
+ * node|bun <this file> serve [--config /data/eve-sandbox/config.json]
15
+ *
16
+ * and invoked as the session user for file operations:
17
+ *
18
+ * setpriv --reuid U --regid G --init-groups -- node|bun <this file> fileop
19
+ */
20
+ import { type IncomingMessage, type ServerResponse } from "node:http";
21
+ export interface DaemonConfig {
22
+ /** The audience every token must carry: the box's URL. */
23
+ readonly audience: string;
24
+ /** App ids (`app_id` claim) allowed to use this box; `owner:<id>` allows every app of an owner. */
25
+ readonly allow: readonly string[];
26
+ readonly issuer: string;
27
+ readonly port: number;
28
+ /** Where session homes live. */
29
+ readonly homeRoot: string;
30
+ /** Kill a session's processes after this long without a call; 0 never. */
31
+ readonly idleMinutes: number;
32
+ /** Delete a session (user, home, files) after this many days without a call; 0 never. */
33
+ readonly retentionDays: number;
34
+ /** Extra environment for every command (e.g. PLAYWRIGHT_BROWSERS_PATH). */
35
+ readonly env: Readonly<Record<string, string>>;
36
+ }
37
+ export declare function loadConfig(path?: string): DaemonConfig;
38
+ export declare class TokenVerifier {
39
+ private readonly config;
40
+ private readonly fetchJson;
41
+ private readonly now;
42
+ private keys;
43
+ private fetchedAt;
44
+ private refreshing;
45
+ constructor(config: Pick<DaemonConfig, "audience" | "issuer" | "allow">, fetchJson?: (url: string) => Promise<unknown>, now?: () => number);
46
+ /** The calling app's namespace (its app id) for a valid, allowed token. */
47
+ verify(authorization: string | undefined): Promise<string>;
48
+ private key;
49
+ private refresh;
50
+ }
51
+ /** The Unix name of an app's session: bounded, derived from the ids only. */
52
+ export declare function sessionName(app: string, sessionId: string): string;
53
+ /** The `fileop` subcommand: one request on stdin, run with the caller's own permissions. */
54
+ export declare function runFileOp(): never;
55
+ export declare function createDaemon(config: DaemonConfig, verifier?: TokenVerifier): {
56
+ server: import("http").Server<typeof IncomingMessage, typeof ServerResponse>;
57
+ listen(port?: number): Promise<void>;
58
+ close(): void;
59
+ };