unfazed 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -29
- package/dist/cli/login.d.ts +129 -0
- package/dist/cli/login.js +596 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/workflow.d.ts +31 -0
- package/dist/cli/workflow.js +157 -0
- package/dist/cli/workflow.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +173 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +26 -5
- package/dist/client.js +93 -48
- package/dist/client.js.map +1 -1
- package/dist/credentials.d.ts +123 -0
- package/dist/credentials.js +317 -0
- package/dist/credentials.js.map +1 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/remote/app.js +2 -1
- package/dist/remote/app.js.map +1 -1
- package/dist/remote/world.d.ts +2 -2
- package/dist/remote/world.js +32 -16
- package/dist/remote/world.js.map +1 -1
- package/dist/unfazed.d.ts +1 -1
- package/dist/unfazed.js +1 -1
- package/dist/unfazed.js.map +1 -1
- package/package.json +18 -6
package/README.md
CHANGED
|
@@ -20,8 +20,11 @@ SDK: one script. Three requests. You read the four lines you cared about.
|
|
|
20
20
|
- Zero runtime dependencies. Installing `unfazed` installs exactly one package.
|
|
21
21
|
(The optional [`unfazed/workflow`](#durable-workflows-unfazedworkflow) subpath
|
|
22
22
|
uses Workflow SDK packages your app installs; the root entry never loads them.)
|
|
23
|
-
- `npx
|
|
24
|
-
|
|
23
|
+
- `npx unfazed login` signs you in from a terminal in the browser; the SDK then
|
|
24
|
+
acts as you, with no token to copy ([details](#sign-in-from-a-terminal-npx-unfazed)).
|
|
25
|
+
- `npx unfazed workflow inspect runs --app <alias>`: Workflow SDK's CLI and web
|
|
26
|
+
UI read your app's runs through the platform, from any directory
|
|
27
|
+
([details](#inspect-runs-from-your-terminal)).
|
|
25
28
|
- Strict TypeScript, generated from the platform's own GraphQL schema.
|
|
26
29
|
- Bun-first, runs unchanged on Node >= 20 and Deno.
|
|
27
30
|
|
|
@@ -33,27 +36,79 @@ bun add unfazed # or: npm i unfazed
|
|
|
33
36
|
|
|
34
37
|
## Auth
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
**A person** signs in once from a terminal, in the browser
|
|
40
|
+
([`npx unfazed login`](#sign-in-from-a-terminal-npx-unfazed)); the SDK then acts
|
|
41
|
+
as them, with their apps, teams and claimed agents:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
npx unfazed login
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**An agent or a CI job** uses a bearer token instead: an agent registers itself
|
|
48
|
+
at <https://unfazed.dev/auth.md> (`POST https://unfazed.dev/agents/register`).
|
|
49
|
+
The SDK reads `UNFZ_API_TOKEN`, which wins over a stored login:
|
|
38
50
|
|
|
39
51
|
```sh
|
|
40
52
|
export UNFZ_API_TOKEN="..."
|
|
41
53
|
```
|
|
42
54
|
|
|
43
55
|
```ts
|
|
44
|
-
const unfazed = new Unfazed();
|
|
45
|
-
const unfazed = new Unfazed({ token });
|
|
56
|
+
const unfazed = new Unfazed(); // UNFZ_API_TOKEN, else your `npx unfazed login`
|
|
57
|
+
const unfazed = new Unfazed({ token }); // or pass a token explicitly
|
|
46
58
|
```
|
|
47
59
|
|
|
48
60
|
Both endpoint and token are configurable. `UNFZ_API_URL` overrides the endpoint
|
|
49
|
-
(default `https://unfazed.dev/query
|
|
50
|
-
self-hosted control planes where only the host differs.
|
|
61
|
+
(default `https://unfazed.dev/query`, or the stored login's); `baseUrl` is a
|
|
62
|
+
convenience for self-hosted control planes where only the host differs.
|
|
51
63
|
|
|
52
64
|
```ts
|
|
53
65
|
new Unfazed({ token, baseUrl: "https://staging.example.com" }); // → /query
|
|
54
66
|
new Unfazed({ token, endpoint: "https://example.com/graphql" }); // exact
|
|
55
67
|
```
|
|
56
68
|
|
|
69
|
+
## Sign in from a terminal: `npx unfazed`
|
|
70
|
+
|
|
71
|
+
The package's `unfazed` bin signs a person in, the way `vercel login` does. It
|
|
72
|
+
runs from any directory (`npx` fetches the package when the directory has no
|
|
73
|
+
`unfazed`), on Node >= 20.16 or Bun, with no dependencies.
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
npx unfazed login # opens the browser; you sign in and allow access
|
|
77
|
+
npx unfazed whoami # who the SDK acts as here
|
|
78
|
+
npx unfazed logout # revokes the login where the server offers it, and deletes it
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
$ npx unfazed login
|
|
83
|
+
Opening your browser to sign in to https://unfazed.dev.
|
|
84
|
+
…
|
|
85
|
+
Signed in to https://unfazed.dev as ada (user).
|
|
86
|
+
The login is stored in /home/ada/.config/unfazed/credentials.json.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- **How:** OAuth 2.1 with PKCE against the platform's own authorization server,
|
|
90
|
+
the one MCP clients sign in through. The CLI registers itself once as a
|
|
91
|
+
public client (cached in `oauth-clients.json` beside the login), listens on
|
|
92
|
+
`http://127.0.0.1:<random port>/callback` for the answer, and asks for
|
|
93
|
+
`unfz:read unfz:deploy` (`--scope` narrows it).
|
|
94
|
+
- **The login** is `${XDG_CONFIG_HOME:-~/.config}/unfazed/credentials.json`,
|
|
95
|
+
mode 0600: the access token (an hour long), the refresh token (30 days, rotated
|
|
96
|
+
on every refresh), the expiry, the endpoint and who signed in. The SDK uses it
|
|
97
|
+
whenever no token is passed and `UNFZ_API_TOKEN` is unset, refreshes the access
|
|
98
|
+
token before it expires (and once when the platform refuses it), and writes
|
|
99
|
+
the rotated pair back for the next process. It is only ever sent to the
|
|
100
|
+
endpoint it was issued by; another `UNFZ_API_URL` needs its own
|
|
101
|
+
`UNFZ_API_URL=… npx unfazed login`. `new UnfazedClient({ login: false })`
|
|
102
|
+
ignores it.
|
|
103
|
+
- **A browser on another machine** (an SSH session, a box): the URL is printed
|
|
104
|
+
instead of opened. Sign in there, then paste the address the browser ends up
|
|
105
|
+
on (`http://127.0.0.1:…/callback?code=…`, which does not load there) into the
|
|
106
|
+
terminal. `--no-browser` prints the URL on your own machine too.
|
|
107
|
+
- **Another control plane:** `--endpoint https://staging.example.com` (or
|
|
108
|
+
`UNFZ_API_URL`).
|
|
109
|
+
- `whoami --json` prints the same as JSON; nothing the CLI prints ever carries
|
|
110
|
+
a token.
|
|
111
|
+
|
|
57
112
|
## Code mode: deploy, wait, tail logs, in one script
|
|
58
113
|
|
|
59
114
|
```ts
|
|
@@ -499,8 +554,12 @@ production build the module:
|
|
|
499
554
|
A complete app — steps, a retried step, `sleep`, a webhook (plain
|
|
500
555
|
`createWebhook()` and `webhook.url`), a progress stream — is in
|
|
501
556
|
[`examples/workflow-nitro`](./examples/workflow-nitro). Once it is deployed,
|
|
502
|
-
`
|
|
557
|
+
`npx unfazed workflow inspect runs --app <alias>` lists its runs
|
|
503
558
|
([Inspect runs from your terminal](#inspect-runs-from-your-terminal)).
|
|
559
|
+
[`examples/birthday-card-generator`](./examples/birthday-card-generator) is
|
|
560
|
+
Vercel's Workflow launch demo on unfazed: a React UI built by Vite and served
|
|
561
|
+
from the same bundle, AI steps, webhooks resumed from emails, `sleep` until a
|
|
562
|
+
date, and a stream the UI reconnects to; it runs without any provider keys.
|
|
504
563
|
|
|
505
564
|
### Without Nitro
|
|
506
565
|
|
|
@@ -569,12 +628,11 @@ database connection afterwards; `wake` and `cancel` are Workflow SDK's own
|
|
|
569
628
|
### Inspect runs from your terminal
|
|
570
629
|
|
|
571
630
|
Workflow SDK's own CLI reads an app's runs through the platform, the way
|
|
572
|
-
`--backend vercel` reads a Vercel project. From
|
|
573
|
-
`unfazed` is installed; the CLI loads `createWorld()` from it):
|
|
631
|
+
`--backend vercel` reads a Vercel project. From any directory:
|
|
574
632
|
|
|
575
633
|
```sh
|
|
576
|
-
|
|
577
|
-
|
|
634
|
+
npx unfazed login # once
|
|
635
|
+
npx unfazed workflow inspect runs --app birthday-card
|
|
578
636
|
```
|
|
579
637
|
|
|
580
638
|
```
|
|
@@ -586,25 +644,38 @@ wrun_01KPBDAY00000000000000RN03 generateBirthdayCard failed 2026-10-15 09:
|
|
|
586
644
|
```
|
|
587
645
|
|
|
588
646
|
```sh
|
|
589
|
-
npx workflow inspect run <run-id> --
|
|
590
|
-
npx workflow inspect steps --runId <run-id> --
|
|
591
|
-
npx workflow inspect step <step-id> --runId <run-id> --
|
|
592
|
-
npx workflow inspect stream <stream-id> --runId <run-id> --
|
|
593
|
-
npx workflow cancel <run-id> --
|
|
594
|
-
npx workflow web --
|
|
595
|
-
|
|
647
|
+
npx unfazed workflow inspect run <run-id> --app birthday-card # input, output, error
|
|
648
|
+
npx unfazed workflow inspect steps --runId <run-id> --app birthday-card # also: events, hooks, sleeps, streams
|
|
649
|
+
npx unfazed workflow inspect step <step-id> --runId <run-id> --app birthday-card
|
|
650
|
+
npx unfazed workflow inspect stream <stream-id> --runId <run-id> --app birthday-card # follows it while the run writes
|
|
651
|
+
npx unfazed workflow cancel <run-id> --app birthday-card
|
|
652
|
+
npx unfazed workflow web --app birthday-card # the local web UI: trace, events, streams,
|
|
653
|
+
# Wake Up Sleep and Cancel
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
`npx unfazed workflow …` is Workflow SDK's CLI with this package as its
|
|
657
|
+
backend: everything after `workflow` but `--app` is passed to it, and its
|
|
658
|
+
output and exit code come back unchanged. It runs the project's own
|
|
659
|
+
`workflow` (major 5) when the directory has one, else `npx -y workflow@5`.
|
|
660
|
+
|
|
661
|
+
**Inside a project** that has `unfazed` installed, the CLI's native form works
|
|
662
|
+
too, since the CLI resolves `--backend unfazed` from the project:
|
|
663
|
+
|
|
664
|
+
```sh
|
|
665
|
+
UNFZ_APP=birthday-card npx workflow inspect runs --backend unfazed
|
|
596
666
|
```
|
|
597
667
|
|
|
598
|
-
|
|
668
|
+
`export WORKFLOW_TARGET_WORLD=unfazed` saves typing `--backend` there. The
|
|
669
|
+
usual options work (`--json`, `--limit`, `--cursor`, `--all`, `--status`,
|
|
599
670
|
`--workflowName`, `--interactive`); the exceptions are below.
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
- **The token**
|
|
606
|
-
|
|
607
|
-
operator).
|
|
671
|
+
|
|
672
|
+
- **Which app:** `--app` (`npx unfazed workflow` only), else `UNFZ_APP` (its
|
|
673
|
+
alias, the `<alias>` of `https://<alias>.unfz.to`), else the `alias` in
|
|
674
|
+
`./unfazed.json`, or its only `apps[]` entry. `UNFZ_API_URL` points at
|
|
675
|
+
another control plane.
|
|
676
|
+
- **The token** is your `npx unfazed login`, or `UNFZ_API_TOKEN` when set. It
|
|
677
|
+
needs `unfz:read`; cancel and Wake Up Sleep need `unfz:deploy` too, and the
|
|
678
|
+
caller must be allowed to restart the app (owner, team member, operator).
|
|
608
679
|
- **Every command is one platform call relayed to the app**, through the same
|
|
609
680
|
inspection route the dashboard reads; an `on_demand` app cold-starts for it,
|
|
610
681
|
as for a visitor.
|
|
@@ -726,6 +797,7 @@ WORKFLOW_POSTGRES_URL=$UNFAZED_TEST_DATABASE_URL bun run test:conformance
|
|
|
726
797
|
# delivery cold-started in a fresh process
|
|
727
798
|
bun run test:example # the example, bundled as unfazed builds it,
|
|
728
799
|
# one fresh process per delivery and request
|
|
800
|
+
bun run test:birthday # the same for examples/birthday-card-generator
|
|
729
801
|
bun run test:cli # Workflow SDK's CLI with --backend unfazed (and --web)
|
|
730
802
|
# against a fake of the platform's inspection API
|
|
731
803
|
```
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { type StoredCredentials } from "../credentials.js";
|
|
2
|
+
import { UnfazedError } from "../errors.js";
|
|
3
|
+
export declare const USER_AGENT = "unfazed-cli/0.6.1 (+https://unfazed.dev/auth.md)";
|
|
4
|
+
/** What a login asks for: reading and deploying, like an MCP client. */
|
|
5
|
+
export declare const DEFAULT_SCOPE = "unfz:read unfz:deploy";
|
|
6
|
+
export declare const CLIENT_NAME = "unfazed CLI";
|
|
7
|
+
export declare const CALLBACK_PATH = "/callback";
|
|
8
|
+
/** The redirect registered once; each login uses it with its own port. */
|
|
9
|
+
export declare const REGISTERED_REDIRECT_URI = "http://127.0.0.1/callback";
|
|
10
|
+
/** Registered clients, per issuer, beside the login. */
|
|
11
|
+
export declare const CLIENTS_FILE = "oauth-clients.json";
|
|
12
|
+
/** How long a login waits for the browser. */
|
|
13
|
+
export declare const LOGIN_TIMEOUT_MS: number;
|
|
14
|
+
export type Env = Record<string, string | undefined>;
|
|
15
|
+
/** The `fetch` the CLI needs; the global one fits. */
|
|
16
|
+
export type Fetch = (input: string, init: {
|
|
17
|
+
method: string;
|
|
18
|
+
headers: Record<string, string>;
|
|
19
|
+
body?: string;
|
|
20
|
+
redirect?: "manual" | "follow";
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
}) => Promise<{
|
|
23
|
+
ok: boolean;
|
|
24
|
+
status: number;
|
|
25
|
+
text(): Promise<string>;
|
|
26
|
+
}>;
|
|
27
|
+
/** A login that could not be completed; the message says why and what to do. */
|
|
28
|
+
export declare class UnfazedLoginError extends UnfazedError {
|
|
29
|
+
}
|
|
30
|
+
/** The GraphQL endpoint: given, else `UNFZ_API_URL`, else unfazed.dev's; a bare origin gets `/query`. */
|
|
31
|
+
export declare function resolveEndpoint(raw: string | undefined, env: Env): string;
|
|
32
|
+
/** Whether a URL may carry tokens: https, or plain http to this machine only. */
|
|
33
|
+
export declare function safeURL(raw: unknown): raw is string;
|
|
34
|
+
/** RFC 8414 authorization server metadata: the fields the CLI uses. */
|
|
35
|
+
export interface AuthServerMetadata {
|
|
36
|
+
issuer: string;
|
|
37
|
+
authorization_endpoint: string;
|
|
38
|
+
token_endpoint: string;
|
|
39
|
+
registration_endpoint?: string;
|
|
40
|
+
revocation_endpoint?: string;
|
|
41
|
+
code_challenge_methods_supported?: string[];
|
|
42
|
+
response_types_supported?: string[];
|
|
43
|
+
authorization_response_iss_parameter_supported?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** Reads and checks the metadata of the server at `origin` (e.g. https://unfazed.dev). */
|
|
46
|
+
export declare function discover(origin: string, fetch: Fetch): Promise<AuthServerMetadata>;
|
|
47
|
+
/** A PKCE verifier and its S256 challenge. */
|
|
48
|
+
export declare function pkcePair(): {
|
|
49
|
+
verifier: string;
|
|
50
|
+
challenge: string;
|
|
51
|
+
};
|
|
52
|
+
/** A client the CLI registered at one issuer. */
|
|
53
|
+
export interface ClientRecord {
|
|
54
|
+
client_id: string;
|
|
55
|
+
redirect_uri: string;
|
|
56
|
+
scope: string;
|
|
57
|
+
registered_at: string;
|
|
58
|
+
}
|
|
59
|
+
/** The cached client for `issuer` that may ask for `scope`, if any. */
|
|
60
|
+
export declare function cachedClient(env: Env, issuer: string, scope: string): ClientRecord | undefined;
|
|
61
|
+
export interface LoginOptions {
|
|
62
|
+
/** GraphQL endpoint or platform origin. Default: `UNFZ_API_URL`, else https://unfazed.dev. */
|
|
63
|
+
endpoint?: string;
|
|
64
|
+
/** Space-separated scopes. Default: `unfz:read unfz:deploy`. */
|
|
65
|
+
scope?: string;
|
|
66
|
+
/** Default `process.env`: where `UNFZ_API_URL` and the login's location come from. */
|
|
67
|
+
env?: Env;
|
|
68
|
+
fetch?: Fetch;
|
|
69
|
+
/** Opens the sign-in URL; resolves to whether a browser started. Default: print only. */
|
|
70
|
+
openBrowser?: (url: string) => boolean | Promise<boolean>;
|
|
71
|
+
/** Where progress goes (never a token). Default: stderr. */
|
|
72
|
+
log?: (line: string) => void;
|
|
73
|
+
/** Where a pasted answer is read from (a browser on another machine). Default: none. */
|
|
74
|
+
input?: NodeJS.ReadableStream;
|
|
75
|
+
/** How long to wait for the browser. Default {@link LOGIN_TIMEOUT_MS}. */
|
|
76
|
+
timeoutMs?: number;
|
|
77
|
+
}
|
|
78
|
+
/** Signs in through the browser and stores the login. Resolves to it and where it was stored. */
|
|
79
|
+
export declare function login(options?: LoginOptions): Promise<{
|
|
80
|
+
credentials: StoredCredentials;
|
|
81
|
+
path: string;
|
|
82
|
+
}>;
|
|
83
|
+
/** The fields `whoami` shows. */
|
|
84
|
+
export interface Actor {
|
|
85
|
+
kind: string;
|
|
86
|
+
userID: string;
|
|
87
|
+
name: string;
|
|
88
|
+
scopes: string[];
|
|
89
|
+
}
|
|
90
|
+
export interface WhoamiResult {
|
|
91
|
+
actor: Actor;
|
|
92
|
+
endpoint: string;
|
|
93
|
+
/** `token`: UNFZ_API_TOKEN; `login`: the stored login at `path`. */
|
|
94
|
+
source: "token" | "login";
|
|
95
|
+
path?: string;
|
|
96
|
+
}
|
|
97
|
+
/** Who the SDK acts as here: `UNFZ_API_TOKEN` when set, else the stored login (refreshed as needed). */
|
|
98
|
+
export declare function whoami(options?: {
|
|
99
|
+
env?: Env;
|
|
100
|
+
fetch?: Fetch;
|
|
101
|
+
}): Promise<WhoamiResult>;
|
|
102
|
+
export interface LogoutResult {
|
|
103
|
+
/** Whether there was a login to remove. */
|
|
104
|
+
signedOut: boolean;
|
|
105
|
+
issuer?: string;
|
|
106
|
+
/** Whether the server revoked the tokens. */
|
|
107
|
+
revoked: boolean;
|
|
108
|
+
/** Whether the server offers revocation (RFC 7009). */
|
|
109
|
+
revocationSupported: boolean;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Revokes the stored tokens when the server advertises revocation, then
|
|
113
|
+
* deletes the login. The endpoint is read from the server's metadata again,
|
|
114
|
+
* so one the server gained since the login is used.
|
|
115
|
+
*/
|
|
116
|
+
export declare function logout(options?: {
|
|
117
|
+
env?: Env;
|
|
118
|
+
fetch?: Fetch;
|
|
119
|
+
}): Promise<LogoutResult>;
|
|
120
|
+
export declare function accountName(credentials: StoredCredentials): string;
|
|
121
|
+
/**
|
|
122
|
+
* Whether a browser can be expected here. Over SSH, or on Linux without a
|
|
123
|
+
* display, the login prints the URL instead (and takes the pasted answer).
|
|
124
|
+
*/
|
|
125
|
+
export declare function canOpenBrowser(env: Env, platform?: NodeJS.Platform): boolean;
|
|
126
|
+
/** The command that opens `url` — `$BROWSER`, `open`, `xdg-open` or the Windows URL handler — as argv, never a shell line. */
|
|
127
|
+
export declare function browserCommand(url: string, env: Env, platform?: NodeJS.Platform): string[];
|
|
128
|
+
/** Starts the browser on `url`. Resolves to whether it could be started. */
|
|
129
|
+
export declare function openBrowser(url: string, env?: Env, platform?: NodeJS.Platform): Promise<boolean>;
|