@doist/cli-core 0.7.1 → 0.9.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 (46) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +65 -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 +11 -0
  12. package/dist/auth/index.d.ts.map +1 -0
  13. package/dist/auth/index.js +5 -0
  14. package/dist/auth/index.js.map +1 -0
  15. package/dist/auth/login.d.ts +54 -0
  16. package/dist/auth/login.d.ts.map +1 -0
  17. package/dist/auth/login.js +61 -0
  18. package/dist/auth/login.js.map +1 -0
  19. package/dist/auth/pkce.d.ts +29 -0
  20. package/dist/auth/pkce.d.ts.map +1 -0
  21. package/dist/auth/pkce.js +41 -0
  22. package/dist/auth/pkce.js.map +1 -0
  23. package/dist/auth/providers/pkce.d.ts +42 -0
  24. package/dist/auth/providers/pkce.d.ts.map +1 -0
  25. package/dist/auth/providers/pkce.js +114 -0
  26. package/dist/auth/providers/pkce.js.map +1 -0
  27. package/dist/auth/types.d.ts +81 -0
  28. package/dist/auth/types.d.ts.map +1 -0
  29. package/dist/auth/types.js +2 -0
  30. package/dist/auth/types.js.map +1 -0
  31. package/dist/commands/update.d.ts.map +1 -1
  32. package/dist/commands/update.js +9 -21
  33. package/dist/commands/update.js.map +1 -1
  34. package/dist/errors.d.ts +9 -1
  35. package/dist/errors.d.ts.map +1 -1
  36. package/dist/errors.js +9 -0
  37. package/dist/errors.js.map +1 -1
  38. package/dist/index.d.ts +2 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +2 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/options.d.ts +6 -0
  43. package/dist/options.d.ts.map +1 -1
  44. package/dist/options.js +18 -1
  45. package/dist/options.js.map +1 -1
  46. package/package.json +10 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [0.9.0](https://github.com/Doist/cli-core/compare/v0.8.0...v0.9.0) (2026-05-09)
2
+
3
+ ### Features
4
+
5
+ * **auth:** add attachLoginCommand Commander helper ([#13](https://github.com/Doist/cli-core/issues/13)) ([bb921b8](https://github.com/Doist/cli-core/commit/bb921b83cee128c627bf7c318a016ef4a3b85582))
6
+
7
+ ## [0.8.0](https://github.com/Doist/cli-core/compare/v0.7.1...v0.8.0) (2026-05-09)
8
+
9
+ ### Features
10
+
11
+ * **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))
12
+
1
13
  ## [0.7.1](https://github.com/Doist/cli-core/compare/v0.7.0...v0.7.1) (2026-05-09)
2
14
 
3
15
  ### Bug Fixes
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) | `attachLoginCommand`, `runOAuthFlow`, `createPkceProvider`, PKCE helpers, `AuthProvider` / `TokenStore` types | OAuth runtime plus the `attachLoginCommand` Commander helper for `<cli> [auth] login`. Ships the standard public-client PKCE flow (`createPkceProvider`); `AuthProvider` and `TokenStore` are the escape hatches for DCR, OS-keychain, multi-account, etc. consumers implement `TokenStore` directly (a single-user config-file version is ~30 LoC). `commander` (when using `attachLoginCommand`) 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,57 @@ 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. cli-core ships the standard public-client PKCE flow (`createPkceProvider`) and the `attachLoginCommand` Commander helper that drives `runOAuthFlow` end-to-end. Bespoke flows (Dynamic Client Registration, device code, magic link, username/password) implement the `AuthProvider` interface directly — no cli-core release needed.
128
+
129
+ Token storage is a `TokenStore` the consumer provides — cli-core does not ship a default. The interface is small enough that a single-user config-file version is ~30 lines inline (use `getConfigPath` + `readConfig` / `writeConfig`). OS-keychain-backed storage, multi-account stores, and the sibling Commander helpers (`logout` / `status` / `token`) are all deferred until concrete consumer migrations prove the shared shape.
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 { createPkceProvider, attachLoginCommand } from '@doist/cli-core/auth'
141
+ import type { TokenStore } from '@doist/cli-core/auth'
142
+
143
+ type Account = { id: string; label?: string; email: string }
144
+
145
+ const store: TokenStore<Account> = createMyTokenStore() // consumer-supplied
146
+
147
+ const provider = createPkceProvider<Account>({
148
+ authorizeUrl: ({ handshake }) => `${handshake.baseUrl as string}/oauth/authorize`,
149
+ tokenUrl: ({ handshake }) => `${handshake.baseUrl as string}/oauth/token`,
150
+ clientId: ({ flags }) => flags.clientId as string,
151
+ scopes: ['read', 'write'],
152
+ validate: async ({ token, handshake }) => probeUser(token, handshake.baseUrl as string),
153
+ })
154
+
155
+ const auth = program.command('auth')
156
+ const login = attachLoginCommand<Account>(auth, {
157
+ provider,
158
+ store,
159
+ preferredPort: 54969,
160
+ portFallbackCount: 5,
161
+ resolveScopes: ({ readOnly }) => (readOnly ? ['read'] : ['read', 'write']),
162
+ renderSuccess: () => `<html>...</html>`,
163
+ renderError: (message) => `<html>${message}</html>`,
164
+ onSuccess: ({ account, view, flags }) => {
165
+ if (view.json) console.log(JSON.stringify({ account, flags }))
166
+ else console.log(`Signed in as ${account.label ?? account.id}`)
167
+ },
168
+ })
169
+ login.description('Authenticate via OAuth')
170
+ ```
171
+
172
+ `attachLoginCommand` wires `--read-only`, `--callback-port`, `--json`, `--ndjson` and returns the new `Command` so the consumer can chain `.description(...)` / `.option(...)` / `.addHelpText(...)`. Consumer-attached options land in the `flags` object passed to `resolveScopes` and (post-flow) to `onSuccess`. Under `--json` / `--ndjson` the authorize-URL fallback (printed when `open` is missing or fails to launch) is routed to stderr so the JSON envelope on stdout stays clean; pass `onAuthorizeUrl` to override. 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_CALLBACK_TIMEOUT`, `AUTH_PORT_BIND_FAILED`, `AUTH_TOKEN_EXCHANGE_FAILED`, `AUTH_STORE_WRITE_FAILED`); the consumer's top-level handler formats and exits.
173
+
174
+ For a lower-level integration that doesn't want the Commander helper, `runOAuthFlow` is exposed directly with the same option set.
175
+
124
176
  ## Development
125
177
 
126
178
  ```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,11 @@
1
+ export type { AuthErrorCode } from './errors.js';
2
+ export { runOAuthFlow } from './flow.js';
3
+ export type { RunOAuthFlowOptions, RunOAuthFlowResult } from './flow.js';
4
+ export { attachLoginCommand } from './login.js';
5
+ export type { AttachLoginCommandOptions, AttachLoginContext } from './login.js';
6
+ export { DEFAULT_VERIFIER_ALPHABET, deriveChallenge, generateState, generateVerifier, } from './pkce.js';
7
+ export type { GenerateVerifierOptions } from './pkce.js';
8
+ export { createPkceProvider } from './providers/pkce.js';
9
+ export type { PkceLazyString, PkceProviderOptions } from './providers/pkce.js';
10
+ export type { AuthAccount, AuthorizeInput, AuthorizeResult, AuthProvider, ExchangeInput, ExchangeResult, PrepareInput, PrepareResult, TokenStore, ValidateInput, } from './types.js';
11
+ //# 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,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,YAAY,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/E,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,5 @@
1
+ export { runOAuthFlow } from './flow.js';
2
+ export { attachLoginCommand } from './login.js';
3
+ export { DEFAULT_VERIFIER_ALPHABET, deriveChallenge, generateState, generateVerifier, } from './pkce.js';
4
+ export { createPkceProvider } from './providers/pkce.js';
5
+ //# 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,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE/C,OAAO,EACH,yBAAyB,EACzB,eAAe,EACf,aAAa,EACb,gBAAgB,GACnB,MAAM,WAAW,CAAA;AAElB,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA"}
@@ -0,0 +1,54 @@
1
+ import type { Command } from 'commander';
2
+ import type { ViewOptions } from '../options.js';
3
+ import type { AuthAccount, AuthProvider, TokenStore } from './types.js';
4
+ export type AttachLoginContext<TAccount extends AuthAccount> = {
5
+ account: TAccount;
6
+ /** `--json` / `--ndjson` flag values, both present (defaulted to `false`). */
7
+ view: Required<ViewOptions>;
8
+ /**
9
+ * Stripped per-CLI flags — the parsed options object with the standard
10
+ * registrar flags (`--read-only`, `--callback-port`, `--json`, `--ndjson`)
11
+ * removed. Same view `resolveScopes` saw at flow start.
12
+ */
13
+ flags: Record<string, unknown>;
14
+ };
15
+ export type AttachLoginCommandOptions<TAccount extends AuthAccount = AuthAccount> = {
16
+ provider: AuthProvider<TAccount>;
17
+ store: TokenStore<TAccount>;
18
+ /** Default local OAuth callback port. Overridable per-invocation via `--callback-port`. */
19
+ preferredPort: number;
20
+ /** Walk up this many sequential ports if `preferredPort` is busy. Default: 5. */
21
+ portFallbackCount?: number;
22
+ /** Resolve the scope list to request from the runtime flags + read-only state. */
23
+ resolveScopes(ctx: {
24
+ readOnly: boolean;
25
+ flags: Record<string, unknown>;
26
+ }): string[];
27
+ renderSuccess(): string;
28
+ renderError(message: string): string;
29
+ /** Override the browser opener (tests). When omitted, `runOAuthFlow` imports `open`. */
30
+ openBrowser?(url: string): Promise<void>;
31
+ /**
32
+ * Override the authorize-URL fallback callback that fires when the browser
33
+ * can't be opened (no `open` peer / opener throws). When omitted, the URL
34
+ * is written to stderr in machine-output mode (so the JSON / NDJSON
35
+ * envelope on stdout stays clean) and to stdout via `runOAuthFlow`'s
36
+ * default in human mode.
37
+ */
38
+ onAuthorizeUrl?(url: string): void;
39
+ /** Called after the token is persisted. */
40
+ onSuccess(ctx: AttachLoginContext<TAccount>): void | Promise<void>;
41
+ };
42
+ /**
43
+ * Attach `login` as a subcommand of `parent`. Wires the standard flag set
44
+ * (`--read-only`, `--callback-port`, `--json`, `--ndjson`) and drives
45
+ * `runOAuthFlow`. Returns the new `Command` so the consumer can chain
46
+ * `.description(...)` / `.option(...)` / `.addHelpText(...)` for additional
47
+ * flags or help text.
48
+ *
49
+ * Additional Commander options the consumer attaches to the returned command
50
+ * land on the same parsed options object Commander hands to the action, so
51
+ * `resolveScopes` and `onSuccess` see them via `flags`.
52
+ */
53
+ export declare function attachLoginCommand<TAccount extends AuthAccount>(parent: Command, options: AttachLoginCommandOptions<TAccount>): Command;
54
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/auth/login.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAExC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAEhD,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEvE,MAAM,MAAM,kBAAkB,CAAC,QAAQ,SAAS,WAAW,IAAI;IAC3D,OAAO,EAAE,QAAQ,CAAA;IACjB,8EAA8E;IAC9E,IAAI,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAA;IAC3B;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACjC,CAAA;AAED,MAAM,MAAM,yBAAyB,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW,IAAI;IAChF,QAAQ,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;IAChC,KAAK,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAA;IAC3B,2FAA2F;IAC3F,aAAa,EAAE,MAAM,CAAA;IACrB,iFAAiF;IACjF,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,kFAAkF;IAClF,aAAa,CAAC,GAAG,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,MAAM,EAAE,CAAA;IACnF,aAAa,IAAI,MAAM,CAAA;IACvB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;IACpC,wFAAwF;IACxF,WAAW,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC;;;;;;OAMG;IACH,cAAc,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,2CAA2C;IAC3C,SAAS,CAAC,GAAG,EAAE,kBAAkB,CAAC,QAAQ,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrE,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,SAAS,WAAW,EAC3D,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,GAC7C,OAAO,CAwCT"}