@doist/cli-core 0.7.0 → 0.8.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +66 -13
  3. package/dist/auth/errors.d.ts +7 -0
  4. package/dist/auth/errors.d.ts.map +1 -0
  5. package/dist/auth/errors.js +2 -0
  6. package/dist/auth/errors.js.map +1 -0
  7. package/dist/auth/flow.d.ts +47 -0
  8. package/dist/auth/flow.d.ts.map +1 -0
  9. package/dist/auth/flow.js +320 -0
  10. package/dist/auth/flow.js.map +1 -0
  11. package/dist/auth/index.d.ts +9 -0
  12. package/dist/auth/index.d.ts.map +1 -0
  13. package/dist/auth/index.js +4 -0
  14. package/dist/auth/index.js.map +1 -0
  15. package/dist/auth/pkce.d.ts +29 -0
  16. package/dist/auth/pkce.d.ts.map +1 -0
  17. package/dist/auth/pkce.js +41 -0
  18. package/dist/auth/pkce.js.map +1 -0
  19. package/dist/auth/providers/pkce.d.ts +42 -0
  20. package/dist/auth/providers/pkce.d.ts.map +1 -0
  21. package/dist/auth/providers/pkce.js +114 -0
  22. package/dist/auth/providers/pkce.js.map +1 -0
  23. package/dist/auth/types.d.ts +81 -0
  24. package/dist/auth/types.d.ts.map +1 -0
  25. package/dist/auth/types.js +2 -0
  26. package/dist/auth/types.js.map +1 -0
  27. package/dist/commands/update.d.ts.map +1 -1
  28. package/dist/commands/update.js +15 -26
  29. package/dist/commands/update.js.map +1 -1
  30. package/dist/errors.d.ts +9 -1
  31. package/dist/errors.d.ts.map +1 -1
  32. package/dist/errors.js +9 -0
  33. package/dist/errors.js.map +1 -1
  34. package/dist/index.d.ts +2 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +2 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/options.d.ts +6 -0
  39. package/dist/options.d.ts.map +1 -1
  40. package/dist/options.js +18 -1
  41. package/dist/options.js.map +1 -1
  42. package/package.json +10 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [0.8.0](https://github.com/Doist/cli-core/compare/v0.7.1...v0.8.0) (2026-05-09)
2
+
3
+ ### Features
4
+
5
+ * **auth:** extract OAuth login runtime into ./auth subpath ([#12](https://github.com/Doist/cli-core/issues/12)) ([d402f02](https://github.com/Doist/cli-core/commit/d402f02d45237245259df753dbc8a97e0c7791e8))
6
+
7
+ ## [0.7.1](https://github.com/Doist/cli-core/compare/v0.7.0...v0.7.1) (2026-05-09)
8
+
9
+ ### Bug Fixes
10
+
11
+ * **update:** drop install-v1 Accept header on dist-tag fetch ([#10](https://github.com/Doist/cli-core/issues/10)) ([4a7b36f](https://github.com/Doist/cli-core/commit/4a7b36f0996479af5c080638dba65a6cd6dd7e56))
12
+
1
13
  ## [0.7.0](https://github.com/Doist/cli-core/compare/v0.6.0...v0.7.0) (2026-05-09)
2
14
 
3
15
  ### Features
package/README.md CHANGED
@@ -12,19 +12,20 @@ npm install @doist/cli-core
12
12
 
13
13
  ## What's in it
14
14
 
15
- | Module | Key exports | Purpose |
16
- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
17
- | `commands` (subpath) | `registerChangelogCommand`, `registerUpdateCommand` (+ semver helpers) | Commander wiring for cli-core's standard commands (e.g. `<cli> changelog`, `<cli> update`, `<cli> update switch`). **Requires** `commander` as an optional peer-dep. |
18
- | `config` | `getConfigPath`, `readConfig`, `readConfigStrict`, `writeConfig`, `updateConfig`, `CoreConfig`, `UpdateChannel` | Read / write a per-CLI JSON config file with typed error codes; `CoreConfig` is the shape of fields cli-core itself owns (extend it for per-CLI fields). |
19
- | `empty` | `printEmpty` | Print an empty-state message gated on `--json` / `--ndjson` so machine consumers never see human strings on stdout. |
20
- | `errors` | `CliError` | Typed CLI error class with `code` and exit-code mapping. |
21
- | `global-args` | `parseGlobalArgs`, `createGlobalArgsStore`, `createAccessibleGate`, `createSpinnerGate`, `getProgressJsonlPath`, `isProgressJsonlEnabled` | Parse well-known global flags (`--json`, `--ndjson`, `--quiet`, `--verbose`, `--accessible`, `--no-spinner`, `--progress-jsonl`) and derive predicates from them. |
22
- | `json` | `formatJson`, `formatNdjson` | Stable JSON / newline-delimited JSON formatting for stdout. |
23
- | `markdown` (subpath) | `preloadMarkdown`, `renderMarkdown`, `TerminalRendererOptions` | Lazy-init terminal markdown renderer. **Requires** `marked` and `marked-terminal-renderer` as peer-deps install only if your CLI uses this subpath. |
24
- | `options` | `ViewOptions` | Type contract for `{ json?, ndjson? }` per-command options that machine-output gates derive from. |
25
- | `spinner` | `createSpinner` | Loading spinner factory wrapping `yocto-spinner` with disable gates. |
26
- | `terminal` | `isCI`, `isStderrTTY`, `isStdinTTY`, `isStdoutTTY` | TTY / CI detection helpers. |
27
- | `testing` (subpath) | `describeEmptyMachineOutput` | Vitest helpers reusable by consuming CLIs (e.g. parametrised empty-state suite covering `--json` / `--ndjson` / human modes). |
15
+ | Module | Key exports | Purpose |
16
+ | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
17
+ | `auth` (subpath) | `registerAuthCommand`, `runOAuthFlow`, `startCallbackServer`, `createPkceProvider`, `createConfigTokenStore`, PKCE helpers, `AuthProvider` / `TokenStore` types | OAuth runtime plus the `registerAuthCommand` Commander registrar for `<cli> [auth] login`. Ships the standard public-client PKCE flow + single-user config-backed token store; the `AuthProvider` and `TokenStore` interfaces are the escape hatches for DCR, OS-keychain, multi-account, etc. `commander` (when using the registrar) and `open` (browser launch) are optional peer-deps. |
18
+ | `commands` (subpath) | `registerChangelogCommand`, `registerUpdateCommand` (+ semver helpers) | Commander wiring for cli-core's standard commands (e.g. `<cli> changelog`, `<cli> update`, `<cli> update switch`). **Requires** `commander` as an optional peer-dep. |
19
+ | `config` | `getConfigPath`, `readConfig`, `readConfigStrict`, `writeConfig`, `updateConfig`, `CoreConfig`, `UpdateChannel` | Read / write a per-CLI JSON config file with typed error codes; `CoreConfig` is the shape of fields cli-core itself owns (extend it for per-CLI fields). |
20
+ | `empty` | `printEmpty` | Print an empty-state message gated on `--json` / `--ndjson` so machine consumers never see human strings on stdout. |
21
+ | `errors` | `CliError` | Typed CLI error class with `code` and exit-code mapping. |
22
+ | `global-args` | `parseGlobalArgs`, `createGlobalArgsStore`, `createAccessibleGate`, `createSpinnerGate`, `getProgressJsonlPath`, `isProgressJsonlEnabled` | Parse well-known global flags (`--json`, `--ndjson`, `--quiet`, `--verbose`, `--accessible`, `--no-spinner`, `--progress-jsonl`) and derive predicates from them. |
23
+ | `json` | `formatJson`, `formatNdjson` | Stable JSON / newline-delimited JSON formatting for stdout. |
24
+ | `markdown` (subpath) | `preloadMarkdown`, `renderMarkdown`, `TerminalRendererOptions` | Lazy-init terminal markdown renderer. **Requires** `marked` and `marked-terminal-renderer` as peer-deps install only if your CLI uses this subpath. |
25
+ | `options` | `ViewOptions` | Type contract for `{ json?, ndjson? }` per-command options that machine-output gates derive from. |
26
+ | `spinner` | `createSpinner` | Loading spinner factory wrapping `yocto-spinner` with disable gates. |
27
+ | `terminal` | `isCI`, `isStderrTTY`, `isStdinTTY`, `isStdoutTTY` | TTY / CI detection helpers. |
28
+ | `testing` (subpath) | `describeEmptyMachineOutput` | Vitest helpers reusable by consuming CLIs (e.g. parametrised empty-state suite covering `--json` / `--ndjson` / human modes). |
28
29
 
29
30
  ## Usage
30
31
 
@@ -121,6 +122,58 @@ registerUpdateCommand(program, {
121
122
 
122
123
  The semver helpers (`parseVersion`, `compareVersions`, `isNewer`, `getInstallTag`, `fetchLatestVersion`, `getConfiguredUpdateChannel`) are also exported for ad-hoc use outside the registered command.
123
124
 
125
+ ### Auth (optional subpath)
126
+
127
+ Wire `<cli> [auth] login` and the supporting OAuth runtime in one call. cli-core ships the standard public-client PKCE flow (`createPkceProvider`) and a single-user config-backed `TokenStore` (`createConfigTokenStore`). Bespoke flows (Dynamic Client Registration, device code, magic link, username/password) implement the `AuthProvider` interface directly — no cli-core release needed.
128
+
129
+ `logout`, `status`, and `token` are intentionally **not** in this surface yet. They're short and currently CLI-specific in shape; each CLI keeps its own implementations until a concrete migration proves them worth sharing. OS-keychain-backed storage and multi-account stores are likewise out of scope for this first release — implement the `TokenStore` interface directly until the shared shape stabilises.
130
+
131
+ Install peer-deps in the consuming CLI:
132
+
133
+ ```bash
134
+ npm install commander open # `open` is optional
135
+ ```
136
+
137
+ Then:
138
+
139
+ ```ts
140
+ import { createSpinner, getConfigPath } from '@doist/cli-core'
141
+ import {
142
+ createConfigTokenStore,
143
+ createPkceProvider,
144
+ registerAuthCommand,
145
+ } from '@doist/cli-core/auth'
146
+
147
+ type Account = { id: string; label?: string; email: string }
148
+
149
+ const configPath = getConfigPath('outline-cli')
150
+ const store = createConfigTokenStore<Account>({ configPath })
151
+
152
+ const provider = createPkceProvider<Account>({
153
+ authorizeUrl: ({ handshake }) => `${handshake.baseUrl as string}/oauth/authorize`,
154
+ tokenUrl: ({ handshake }) => `${handshake.baseUrl as string}/oauth/token`,
155
+ clientId: ({ flags }) => flags.clientId as string,
156
+ scopes: ['read', 'write'],
157
+ validate: async ({ token, handshake }) => probeUser(token, handshake.baseUrl as string),
158
+ })
159
+
160
+ const { withSpinner } = createSpinner()
161
+ registerAuthCommand<Account>(program, {
162
+ displayName: 'Outline',
163
+ provider,
164
+ store,
165
+ resolveScopes: ({ readOnly }) => (readOnly ? ['read'] : ['read', 'write']),
166
+ callbackPort: { preferred: 54969, fallbackCount: 5 },
167
+ renderSuccess: (ctx) => `<html>...</html>`,
168
+ renderError: (ctx) => `<html>...</html>`,
169
+ withSpinner,
170
+ })
171
+ ```
172
+
173
+ The success / error HTML is a render hook — every CLI brings its own template (no shared layout enforced). Errors are `CliError` (`AUTH_OAUTH_FAILED`, `AUTH_STATE_MISMATCH`, `AUTH_CALLBACK_TIMEOUT`, `AUTH_PORT_BIND_FAILED`, `AUTH_TOKEN_EXCHANGE_FAILED`, `AUTH_STORE_WRITE_FAILED`); the consumer's top-level handler formats and exits.
174
+
175
+ For a lower-level integration that doesn't want the registrar, `runOAuthFlow` and `startCallbackServer` are exposed directly.
176
+
124
177
  ## Development
125
178
 
126
179
  ```bash
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Error codes thrown by `@doist/cli-core/auth`. Folded into the `CliErrorCode`
3
+ * aggregator in `../errors.ts` so consumers don't have to redeclare them in
4
+ * their own `TCode` union when catching.
5
+ */
6
+ export type AuthErrorCode = 'AUTH_OAUTH_FAILED' | 'AUTH_CALLBACK_TIMEOUT' | 'AUTH_PORT_BIND_FAILED' | 'AUTH_TOKEN_EXCHANGE_FAILED' | 'AUTH_STORE_WRITE_FAILED';
7
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/auth/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,aAAa,GACnB,mBAAmB,GACnB,uBAAuB,GACvB,uBAAuB,GACvB,4BAA4B,GAC5B,yBAAyB,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/auth/errors.ts"],"names":[],"mappings":""}
@@ -0,0 +1,47 @@
1
+ import type { AuthAccount, AuthProvider, TokenStore } from './types.js';
2
+ export type RunOAuthFlowOptions<TAccount extends AuthAccount = AuthAccount> = {
3
+ provider: AuthProvider<TAccount>;
4
+ store: TokenStore<TAccount>;
5
+ /** Resolved scope list to request. */
6
+ scopes: string[];
7
+ /** Was `--read-only` set? Threaded through to the provider. */
8
+ readOnly: boolean;
9
+ /** Per-CLI flags from the command line (Commander option object). */
10
+ flags: Record<string, unknown>;
11
+ /** Preferred local callback port. */
12
+ preferredPort: number;
13
+ /** Walk up this many sequential ports if `preferredPort` is busy. Default 5. */
14
+ portFallbackCount?: number;
15
+ /** Callback path the OAuth provider redirects to. Default `'/callback'`. */
16
+ callbackPath?: string;
17
+ /** Bind address. Default `'127.0.0.1'`. */
18
+ callbackHost?: string;
19
+ /** HTML returned to the browser on success. */
20
+ renderSuccess: () => string;
21
+ /** HTML returned to the browser on failure. Receives the OAuth error description. */
22
+ renderError: (message: string) => string;
23
+ /** Override the browser opener (tests). When omitted, dynamically imports `open`. */
24
+ openBrowser?: (url: string) => Promise<void>;
25
+ /** Print the authorize URL to stdout as a fallback when the browser can't open it. */
26
+ onAuthorizeUrl?: (url: string) => void;
27
+ /** Callback timeout in ms. Default 3 minutes. */
28
+ timeoutMs?: number;
29
+ /** Cancellation signal (Ctrl-C wiring). */
30
+ signal?: AbortSignal;
31
+ };
32
+ export type RunOAuthFlowResult<TAccount extends AuthAccount = AuthAccount> = {
33
+ token: string;
34
+ account: TAccount;
35
+ };
36
+ /**
37
+ * Drive the OAuth dance end-to-end and persist the resulting token.
38
+ *
39
+ * `prepare?` → bind callback server → `authorize` → open browser →
40
+ * wait for callback → `exchangeCode` → `validateToken` → `store.set`.
41
+ *
42
+ * The local HTTP callback server is an internal implementation detail; it
43
+ * is not a separately reusable module since OAuth login is its only
44
+ * consumer today.
45
+ */
46
+ export declare function runOAuthFlow<TAccount extends AuthAccount>(options: RunOAuthFlowOptions<TAccount>): Promise<RunOAuthFlowResult<TAccount>>;
47
+ //# sourceMappingURL=flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../../src/auth/flow.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEvE,MAAM,MAAM,mBAAmB,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW,IAAI;IAC1E,QAAQ,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;IAChC,KAAK,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAA;IAC3B,sCAAsC;IACtC,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,+DAA+D;IAC/D,QAAQ,EAAE,OAAO,CAAA;IACjB,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,qCAAqC;IACrC,aAAa,EAAE,MAAM,CAAA;IACrB,gFAAgF;IAChF,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2CAA2C;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,+CAA+C;IAC/C,aAAa,EAAE,MAAM,MAAM,CAAA;IAC3B,qFAAqF;IACrF,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;IACxC,qFAAqF;IACrF,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5C,sFAAsF;IACtF,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,WAAW,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,kBAAkB,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW,IAAI;IACzE,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,QAAQ,CAAA;CACpB,CAAA;AAOD;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,QAAQ,SAAS,WAAW,EAC3D,OAAO,EAAE,mBAAmB,CAAC,QAAQ,CAAC,GACvC,OAAO,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CA0GvC"}
@@ -0,0 +1,320 @@
1
+ import { createServer } from 'node:http';
2
+ import { CliError, getErrorMessage } from '../errors.js';
3
+ import { isStdoutTTY } from '../terminal.js';
4
+ import { generateState } from './pkce.js';
5
+ const DEFAULT_PORT_FALLBACK_COUNT = 5;
6
+ const DEFAULT_CALLBACK_TIMEOUT_MS = 3 * 60 * 1000;
7
+ const DEFAULT_CALLBACK_PATH = '/callback';
8
+ const DEFAULT_CALLBACK_HOST = '127.0.0.1';
9
+ /**
10
+ * Drive the OAuth dance end-to-end and persist the resulting token.
11
+ *
12
+ * `prepare?` → bind callback server → `authorize` → open browser →
13
+ * wait for callback → `exchangeCode` → `validateToken` → `store.set`.
14
+ *
15
+ * The local HTTP callback server is an internal implementation detail; it
16
+ * is not a separately reusable module since OAuth login is its only
17
+ * consumer today.
18
+ */
19
+ export async function runOAuthFlow(options) {
20
+ assertValidPort(options.preferredPort, 'preferredPort');
21
+ const state = generateState();
22
+ let prepareHandshake = {};
23
+ const server = await startCallbackServer({
24
+ preferredPort: options.preferredPort,
25
+ portFallbackCount: options.portFallbackCount ?? DEFAULT_PORT_FALLBACK_COUNT,
26
+ path: options.callbackPath ?? DEFAULT_CALLBACK_PATH,
27
+ host: options.callbackHost ?? DEFAULT_CALLBACK_HOST,
28
+ expectedState: state,
29
+ renderSuccess: options.renderSuccess,
30
+ renderError: options.renderError,
31
+ });
32
+ let abortListener = null;
33
+ if (options.signal) {
34
+ abortListener = () => {
35
+ void server.stop();
36
+ };
37
+ options.signal.addEventListener('abort', abortListener);
38
+ }
39
+ const checkAborted = () => {
40
+ if (options.signal?.aborted) {
41
+ throw new CliError('AUTH_OAUTH_FAILED', 'Authorization aborted.');
42
+ }
43
+ };
44
+ try {
45
+ checkAborted();
46
+ if (options.provider.prepare) {
47
+ const prepared = await options.provider.prepare({
48
+ redirectUri: server.redirectUri,
49
+ flags: options.flags,
50
+ });
51
+ prepareHandshake = prepared.handshake;
52
+ checkAborted();
53
+ }
54
+ const authorize = await options.provider.authorize({
55
+ redirectUri: server.redirectUri,
56
+ state,
57
+ scopes: options.scopes,
58
+ readOnly: options.readOnly,
59
+ flags: options.flags,
60
+ handshake: prepareHandshake,
61
+ });
62
+ checkAborted();
63
+ await openOrFallback(authorize.authorizeUrl, options);
64
+ const callback = await server.waitForCallback(options.timeoutMs ?? DEFAULT_CALLBACK_TIMEOUT_MS);
65
+ checkAborted();
66
+ // Merge prepareHandshake into the downstream handshake so prepare-time
67
+ // state survives even when a custom provider's authorize() forgets to
68
+ // forward it. Then fold the runtime `flags` and `readOnly` into the
69
+ // handshake so providers' `exchangeCode` / `validateToken` get the
70
+ // same view that `authorize` had — the typed input fields don't
71
+ // carry them, and stuffing them here keeps consumer providers from
72
+ // having to re-thread them manually.
73
+ const downstreamHandshake = {
74
+ ...prepareHandshake,
75
+ ...authorize.handshake,
76
+ flags: options.flags,
77
+ readOnly: options.readOnly,
78
+ };
79
+ const exchange = await options.provider.exchangeCode({
80
+ code: callback.code,
81
+ state: callback.state,
82
+ redirectUri: server.redirectUri,
83
+ handshake: downstreamHandshake,
84
+ });
85
+ checkAborted();
86
+ const account = exchange.account ??
87
+ (await options.provider.validateToken({
88
+ token: exchange.accessToken,
89
+ handshake: downstreamHandshake,
90
+ }));
91
+ checkAborted();
92
+ try {
93
+ await options.store.set(account, exchange.accessToken);
94
+ }
95
+ catch (error) {
96
+ if (error instanceof CliError)
97
+ throw error;
98
+ throw new CliError('AUTH_STORE_WRITE_FAILED', `Failed to persist token: ${getErrorMessage(error)}`);
99
+ }
100
+ return { token: exchange.accessToken, account };
101
+ }
102
+ finally {
103
+ if (options.signal && abortListener) {
104
+ options.signal.removeEventListener('abort', abortListener);
105
+ }
106
+ await server.stop();
107
+ }
108
+ }
109
+ async function startCallbackServer(options) {
110
+ let settle = null;
111
+ const outcomePromise = new Promise((resolve) => {
112
+ settle = resolve;
113
+ });
114
+ const server = createServer((req, res) => {
115
+ handleRequest(req, res, {
116
+ path: options.path,
117
+ expectedState: options.expectedState,
118
+ renderSuccess: options.renderSuccess,
119
+ renderError: options.renderError,
120
+ settle: (outcome) => settle?.(outcome),
121
+ });
122
+ });
123
+ const port = await listenWithFallback(server, options.host, options.preferredPort, options.portFallbackCount);
124
+ // Advertise as `localhost` when bound to the IPv4 loopback default —
125
+ // matches the redirect-URI allowlists OAuth apps typically register.
126
+ // IPv6 literals get bracket-wrapped per RFC 3986. Custom hostnames are
127
+ // advertised verbatim.
128
+ const redirectUri = `http://${formatHostForUrl(options.host)}:${port}${options.path}`;
129
+ let stopped = false;
130
+ return {
131
+ redirectUri,
132
+ async waitForCallback(timeoutMs) {
133
+ let timer;
134
+ const timeoutOutcome = new Promise((resolve) => {
135
+ timer = setTimeout(() => {
136
+ resolve({
137
+ ok: false,
138
+ error: new CliError('AUTH_CALLBACK_TIMEOUT', `Authorization timed out after ${Math.round(timeoutMs / 1000)}s.`, {
139
+ hints: ['Re-run the login command and complete the browser step.'],
140
+ }),
141
+ });
142
+ }, timeoutMs);
143
+ });
144
+ try {
145
+ const outcome = await Promise.race([outcomePromise, timeoutOutcome]);
146
+ if (!outcome.ok)
147
+ throw outcome.error;
148
+ return outcome.result;
149
+ }
150
+ finally {
151
+ if (timer)
152
+ clearTimeout(timer);
153
+ }
154
+ },
155
+ async stop() {
156
+ if (stopped)
157
+ return;
158
+ stopped = true;
159
+ // Settle the outcome so a still-pending `waitForCallback`
160
+ // (e.g. one cancelled via AbortSignal) doesn't hang forever.
161
+ settle?.({
162
+ ok: false,
163
+ error: new CliError('AUTH_OAUTH_FAILED', 'Callback server stopped before authorization completed.'),
164
+ });
165
+ // Browsers keep the success-page connection alive for several
166
+ // seconds after the redirect; closeAllConnections lets the CLI
167
+ // exit promptly instead of waiting for those sockets.
168
+ server.closeAllConnections();
169
+ await new Promise((resolve) => server.close(() => resolve()));
170
+ },
171
+ };
172
+ }
173
+ function handleRequest(req, res, ctx) {
174
+ const url = new URL(req.url ?? '/', 'http://localhost');
175
+ if (url.pathname !== ctx.path) {
176
+ res.statusCode = 404;
177
+ res.setHeader('Content-Type', 'text/plain; charset=utf-8');
178
+ res.end('Not found');
179
+ return;
180
+ }
181
+ const error = url.searchParams.get('error');
182
+ if (error) {
183
+ const description = url.searchParams.get('error_description') ?? error;
184
+ respondHtml(res, 400, ctx.renderError(description));
185
+ ctx.settle({
186
+ ok: false,
187
+ error: new CliError('AUTH_OAUTH_FAILED', `Authorization failed: ${description}`, {
188
+ hints: ['Check the browser tab for details and try again.'],
189
+ }),
190
+ });
191
+ return;
192
+ }
193
+ // Bad-shape callbacks (missing code/state, state mismatch) render a 400
194
+ // page but do *not* settle the wait — a browser-extension prefetch or
195
+ // accidental reload shouldn't kill an in-flight OAuth flow. Only a
196
+ // provider-driven `?error=` is treated as final. The wait still
197
+ // settles on timeout / `stop()` if a valid callback never arrives.
198
+ const code = url.searchParams.get('code');
199
+ const state = url.searchParams.get('state');
200
+ if (!code || !state) {
201
+ respondHtml(res, 400, ctx.renderError('Authorization callback missing code or state.'));
202
+ return;
203
+ }
204
+ if (state !== ctx.expectedState) {
205
+ respondHtml(res, 400, ctx.renderError('Authorization state did not match. Possible CSRF attempt.'));
206
+ return;
207
+ }
208
+ respondHtml(res, 200, ctx.renderSuccess());
209
+ ctx.settle({ ok: true, result: { code, state } });
210
+ }
211
+ function respondHtml(res, status, html) {
212
+ res.statusCode = status;
213
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
214
+ res.setHeader('Cache-Control', 'no-store');
215
+ res.end(html);
216
+ }
217
+ async function listenWithFallback(server, host, preferred, fallback) {
218
+ // Port 0 = OS-assigned ephemeral. The bind always succeeds and there's
219
+ // nothing meaningful to walk to from there.
220
+ if (preferred === 0) {
221
+ try {
222
+ await tryListen(server, host, 0);
223
+ }
224
+ catch (error) {
225
+ throw wrapBindError(error, host, 0);
226
+ }
227
+ const address = server.address();
228
+ if (!address || typeof address === 'string') {
229
+ throw new CliError('AUTH_PORT_BIND_FAILED', 'Could not resolve assigned port.');
230
+ }
231
+ return address.port;
232
+ }
233
+ let lastError = null;
234
+ for (let i = 0; i <= fallback; i++) {
235
+ const port = preferred + i;
236
+ // Stop walking past the valid port range; otherwise `server.listen`
237
+ // throws a raw `RangeError` outside the `CliError` envelope.
238
+ if (port > 65535)
239
+ break;
240
+ try {
241
+ await tryListen(server, host, port);
242
+ return port;
243
+ }
244
+ catch (error) {
245
+ const err = error;
246
+ // Surface non-EADDRINUSE failures (EACCES on privileged ports,
247
+ // an unreachable host, …) via the typed error envelope rather
248
+ // than letting Node's raw error escape.
249
+ if (err.code !== 'EADDRINUSE')
250
+ throw wrapBindError(err, host, port);
251
+ lastError = err;
252
+ }
253
+ }
254
+ throw new CliError('AUTH_PORT_BIND_FAILED', `Could not bind a local port in range ${preferred}..${preferred + fallback}.`, {
255
+ hints: [
256
+ 'Free a port in that range or pass a different preferred port.',
257
+ lastError?.message ?? '',
258
+ ].filter(Boolean),
259
+ });
260
+ }
261
+ function wrapBindError(error, host, port) {
262
+ return new CliError('AUTH_PORT_BIND_FAILED', `Could not bind callback server to ${host}:${port}: ${getErrorMessage(error)}`);
263
+ }
264
+ function tryListen(server, host, port) {
265
+ return new Promise((resolve, reject) => {
266
+ const onError = (err) => {
267
+ server.removeListener('listening', onListening);
268
+ reject(err);
269
+ };
270
+ const onListening = () => {
271
+ server.removeListener('error', onError);
272
+ resolve();
273
+ };
274
+ server.once('error', onError);
275
+ server.once('listening', onListening);
276
+ server.listen(port, host);
277
+ });
278
+ }
279
+ function formatHostForUrl(host) {
280
+ if (host === DEFAULT_CALLBACK_HOST)
281
+ return 'localhost';
282
+ if (host.includes(':'))
283
+ return `[${host}]`;
284
+ return host;
285
+ }
286
+ function assertValidPort(port, label) {
287
+ if (typeof port !== 'number' || !Number.isInteger(port) || port < 0 || port > 65535) {
288
+ throw new CliError('AUTH_PORT_BIND_FAILED', `Invalid ${label} '${String(port)}': expected an integer in [0..65535].`);
289
+ }
290
+ }
291
+ async function openOrFallback(url, options) {
292
+ const opener = options.openBrowser ?? (await loadDefaultOpener());
293
+ if (opener) {
294
+ try {
295
+ await opener(url);
296
+ return;
297
+ }
298
+ catch {
299
+ // Fall through to the URL print below.
300
+ }
301
+ }
302
+ // No opener available, or the opener threw. Surface the URL so the user
303
+ // can finish the flow manually.
304
+ if (options.onAuthorizeUrl)
305
+ options.onAuthorizeUrl(url);
306
+ else if (isStdoutTTY())
307
+ console.log(`Open this URL in your browser:\n ${url}`);
308
+ }
309
+ async function loadDefaultOpener() {
310
+ try {
311
+ const mod = (await import('open'));
312
+ return async (url) => {
313
+ await mod.default(url);
314
+ };
315
+ }
316
+ catch {
317
+ return null;
318
+ }
319
+ }
320
+ //# sourceMappingURL=flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow.js","sourceRoot":"","sources":["../../src/auth/flow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export type { AuthErrorCode } from './errors.js';
2
+ export { runOAuthFlow } from './flow.js';
3
+ export type { RunOAuthFlowOptions, RunOAuthFlowResult } from './flow.js';
4
+ export { DEFAULT_VERIFIER_ALPHABET, deriveChallenge, generateState, generateVerifier, } from './pkce.js';
5
+ export type { GenerateVerifierOptions } from './pkce.js';
6
+ export { createPkceProvider } from './providers/pkce.js';
7
+ export type { PkceLazyString, PkceProviderOptions } from './providers/pkce.js';
8
+ export type { AuthAccount, AuthorizeInput, AuthorizeResult, AuthProvider, ExchangeInput, ExchangeResult, PrepareInput, PrepareResult, TokenStore, ValidateInput, } from './types.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AACxE,OAAO,EACH,yBAAyB,EACzB,eAAe,EACf,aAAa,EACb,gBAAgB,GACnB,MAAM,WAAW,CAAA;AAClB,YAAY,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAC9E,YAAY,EACR,WAAW,EACX,cAAc,EACd,eAAe,EACf,YAAY,EACZ,aAAa,EACb,cAAc,EACd,YAAY,EACZ,aAAa,EACb,UAAU,EACV,aAAa,GAChB,MAAM,YAAY,CAAA"}
@@ -0,0 +1,4 @@
1
+ export { runOAuthFlow } from './flow.js';
2
+ export { DEFAULT_VERIFIER_ALPHABET, deriveChallenge, generateState, generateVerifier, } from './pkce.js';
3
+ export { createPkceProvider } from './providers/pkce.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAExC,OAAO,EACH,yBAAyB,EACzB,eAAe,EACf,aAAa,EACb,gBAAgB,GACnB,MAAM,WAAW,CAAA;AAElB,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Default RFC 7636 unreserved character set: `A-Z a-z 0-9 - . _ ~`. 66 chars.
3
+ *
4
+ * Some providers (Todoist) ship a 64-char subset that drops `.~` to keep the
5
+ * verifier alphanumeric-with-dashes-and-underscores; pass it via
6
+ * `generateVerifier({ alphabet })` if you need to match a specific server's
7
+ * canonicalisation.
8
+ */
9
+ export declare const DEFAULT_VERIFIER_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
10
+ export type GenerateVerifierOptions = {
11
+ /** Verifier length. RFC 7636 §4.1 mandates 43–128. Default: 64. */
12
+ length?: number;
13
+ /** Override character set (must contain only RFC 7636 unreserved chars). Default: `DEFAULT_VERIFIER_ALPHABET`. */
14
+ alphabet?: string;
15
+ };
16
+ /**
17
+ * Generate a PKCE `code_verifier`. Uses `crypto.randomInt` to map random bytes
18
+ * uniformly onto the alphabet — no modulo bias, no rejection sampling needed
19
+ * at the call site.
20
+ */
21
+ export declare function generateVerifier(options?: GenerateVerifierOptions): string;
22
+ /** Derive the S256 `code_challenge` from a verifier: base64url(sha256(verifier)). */
23
+ export declare function deriveChallenge(verifier: string): string;
24
+ /**
25
+ * Generate a CSRF `state` token. 16 random bytes (128 bits) hex-encoded —
26
+ * comfortably above the 32-bit floor recommended by OAuth 2 §10.12.
27
+ */
28
+ export declare function generateState(): string;
29
+ //# sourceMappingURL=pkce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../src/auth/pkce.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,uEACkC,CAAA;AAExE,MAAM,MAAM,uBAAuB,GAAG;IAClC,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kHAAkH;IAClH,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,MAAM,CAa9E;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}