@skrr-ai/auth-core 0.1.2

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 (147) hide show
  1. package/README.md +21 -0
  2. package/dist/cjs/aead.d.ts +24 -0
  3. package/dist/cjs/aead.js +65 -0
  4. package/dist/cjs/authExpiredDetector.d.ts +65 -0
  5. package/dist/cjs/authExpiredDetector.js +89 -0
  6. package/dist/cjs/authHelper.d.ts +51 -0
  7. package/dist/cjs/authHelper.js +321 -0
  8. package/dist/cjs/credentialEnvelope.d.ts +85 -0
  9. package/dist/cjs/credentialEnvelope.js +230 -0
  10. package/dist/cjs/credentialEnvelopeBridge.d.ts +116 -0
  11. package/dist/cjs/credentialEnvelopeBridge.js +637 -0
  12. package/dist/cjs/credentialResolver.d.ts +278 -0
  13. package/dist/cjs/credentialResolver.js +423 -0
  14. package/dist/cjs/daemonPair.d.ts +172 -0
  15. package/dist/cjs/daemonPair.js +495 -0
  16. package/dist/cjs/daemonScopes.d.ts +74 -0
  17. package/dist/cjs/daemonScopes.js +84 -0
  18. package/dist/cjs/deviceIdentityBridge.d.ts +113 -0
  19. package/dist/cjs/deviceIdentityBridge.js +591 -0
  20. package/dist/cjs/deviceKey.d.ts +167 -0
  21. package/dist/cjs/deviceKey.js +332 -0
  22. package/dist/cjs/fdToken.d.ts +17 -0
  23. package/dist/cjs/fdToken.js +238 -0
  24. package/dist/cjs/harnessTrust.d.ts +72 -0
  25. package/dist/cjs/harnessTrust.js +103 -0
  26. package/dist/cjs/headers-helper.d.ts +25 -0
  27. package/dist/cjs/headers-helper.js +323 -0
  28. package/dist/cjs/index.d.ts +37 -0
  29. package/dist/cjs/index.js +305 -0
  30. package/dist/cjs/jwtUtils.d.ts +35 -0
  31. package/dist/cjs/jwtUtils.js +61 -0
  32. package/dist/cjs/kek/index.d.ts +89 -0
  33. package/dist/cjs/kek/index.js +208 -0
  34. package/dist/cjs/kek/linux.d.ts +92 -0
  35. package/dist/cjs/kek/linux.js +596 -0
  36. package/dist/cjs/kek/macos.d.ts +112 -0
  37. package/dist/cjs/kek/macos.js +599 -0
  38. package/dist/cjs/kek/types.d.ts +111 -0
  39. package/dist/cjs/kek/types.js +43 -0
  40. package/dist/cjs/kek/windows.d.ts +71 -0
  41. package/dist/cjs/kek/windows.js +350 -0
  42. package/dist/cjs/kek/zeroize-registry.d.ts +44 -0
  43. package/dist/cjs/kek/zeroize-registry.js +63 -0
  44. package/dist/cjs/legacyStatePreflight.d.ts +67 -0
  45. package/dist/cjs/legacyStatePreflight.js +78 -0
  46. package/dist/cjs/localIdentity.d.ts +54 -0
  47. package/dist/cjs/localIdentity.js +57 -0
  48. package/dist/cjs/loginLocalhost.d.ts +115 -0
  49. package/dist/cjs/loginLocalhost.js +368 -0
  50. package/dist/cjs/loginWithLocalhost.d.ts +131 -0
  51. package/dist/cjs/loginWithLocalhost.js +359 -0
  52. package/dist/cjs/machineId.d.ts +35 -0
  53. package/dist/cjs/machineId.js +239 -0
  54. package/dist/cjs/messages.d.ts +9 -0
  55. package/dist/cjs/messages.js +44 -0
  56. package/dist/cjs/package.json +3 -0
  57. package/dist/cjs/pkce.d.ts +62 -0
  58. package/dist/cjs/pkce.js +158 -0
  59. package/dist/cjs/recoveryCode.d.ts +61 -0
  60. package/dist/cjs/recoveryCode.js +213 -0
  61. package/dist/cjs/refresh.d.ts +215 -0
  62. package/dist/cjs/refresh.js +877 -0
  63. package/dist/cjs/refreshClassification.d.ts +53 -0
  64. package/dist/cjs/refreshClassification.js +105 -0
  65. package/dist/cjs/refreshScheduler.d.ts +53 -0
  66. package/dist/cjs/refreshScheduler.js +332 -0
  67. package/dist/cjs/runtime.d.ts +149 -0
  68. package/dist/cjs/runtime.js +136 -0
  69. package/dist/cjs/spawnEnv.d.ts +76 -0
  70. package/dist/cjs/spawnEnv.js +153 -0
  71. package/dist/cjs/ttlParser.d.ts +54 -0
  72. package/dist/cjs/ttlParser.js +87 -0
  73. package/dist/cjs/types.d.ts +91 -0
  74. package/dist/cjs/types.js +26 -0
  75. package/dist/esm/aead.d.ts +24 -0
  76. package/dist/esm/aead.js +57 -0
  77. package/dist/esm/authExpiredDetector.d.ts +65 -0
  78. package/dist/esm/authExpiredDetector.js +85 -0
  79. package/dist/esm/authHelper.d.ts +51 -0
  80. package/dist/esm/authHelper.js +311 -0
  81. package/dist/esm/credentialEnvelope.d.ts +85 -0
  82. package/dist/esm/credentialEnvelope.js +213 -0
  83. package/dist/esm/credentialEnvelopeBridge.d.ts +116 -0
  84. package/dist/esm/credentialEnvelopeBridge.js +620 -0
  85. package/dist/esm/credentialResolver.d.ts +278 -0
  86. package/dist/esm/credentialResolver.js +414 -0
  87. package/dist/esm/daemonPair.d.ts +172 -0
  88. package/dist/esm/daemonPair.js +476 -0
  89. package/dist/esm/daemonScopes.d.ts +74 -0
  90. package/dist/esm/daemonScopes.js +80 -0
  91. package/dist/esm/deviceIdentityBridge.d.ts +113 -0
  92. package/dist/esm/deviceIdentityBridge.js +572 -0
  93. package/dist/esm/deviceKey.d.ts +167 -0
  94. package/dist/esm/deviceKey.js +320 -0
  95. package/dist/esm/fdToken.d.ts +17 -0
  96. package/dist/esm/fdToken.js +231 -0
  97. package/dist/esm/harnessTrust.d.ts +72 -0
  98. package/dist/esm/harnessTrust.js +97 -0
  99. package/dist/esm/headers-helper.d.ts +25 -0
  100. package/dist/esm/headers-helper.js +316 -0
  101. package/dist/esm/index.d.ts +37 -0
  102. package/dist/esm/index.js +144 -0
  103. package/dist/esm/jwtUtils.d.ts +35 -0
  104. package/dist/esm/jwtUtils.js +57 -0
  105. package/dist/esm/kek/index.d.ts +89 -0
  106. package/dist/esm/kek/index.js +162 -0
  107. package/dist/esm/kek/linux.d.ts +92 -0
  108. package/dist/esm/kek/linux.js +585 -0
  109. package/dist/esm/kek/macos.d.ts +112 -0
  110. package/dist/esm/kek/macos.js +586 -0
  111. package/dist/esm/kek/types.d.ts +111 -0
  112. package/dist/esm/kek/types.js +39 -0
  113. package/dist/esm/kek/windows.d.ts +71 -0
  114. package/dist/esm/kek/windows.js +344 -0
  115. package/dist/esm/kek/zeroize-registry.d.ts +44 -0
  116. package/dist/esm/kek/zeroize-registry.js +58 -0
  117. package/dist/esm/legacyStatePreflight.d.ts +67 -0
  118. package/dist/esm/legacyStatePreflight.js +71 -0
  119. package/dist/esm/localIdentity.d.ts +54 -0
  120. package/dist/esm/localIdentity.js +54 -0
  121. package/dist/esm/loginLocalhost.d.ts +115 -0
  122. package/dist/esm/loginLocalhost.js +355 -0
  123. package/dist/esm/loginWithLocalhost.d.ts +131 -0
  124. package/dist/esm/loginWithLocalhost.js +353 -0
  125. package/dist/esm/machineId.d.ts +35 -0
  126. package/dist/esm/machineId.js +231 -0
  127. package/dist/esm/messages.d.ts +9 -0
  128. package/dist/esm/messages.js +40 -0
  129. package/dist/esm/pkce.d.ts +62 -0
  130. package/dist/esm/pkce.js +148 -0
  131. package/dist/esm/recoveryCode.d.ts +61 -0
  132. package/dist/esm/recoveryCode.js +207 -0
  133. package/dist/esm/refresh.d.ts +215 -0
  134. package/dist/esm/refresh.js +863 -0
  135. package/dist/esm/refreshClassification.d.ts +53 -0
  136. package/dist/esm/refreshClassification.js +106 -0
  137. package/dist/esm/refreshScheduler.d.ts +53 -0
  138. package/dist/esm/refreshScheduler.js +329 -0
  139. package/dist/esm/runtime.d.ts +149 -0
  140. package/dist/esm/runtime.js +121 -0
  141. package/dist/esm/spawnEnv.d.ts +76 -0
  142. package/dist/esm/spawnEnv.js +149 -0
  143. package/dist/esm/ttlParser.d.ts +54 -0
  144. package/dist/esm/ttlParser.js +83 -0
  145. package/dist/esm/types.d.ts +91 -0
  146. package/dist/esm/types.js +21 -0
  147. package/package.json +82 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Detect state left by the pre-rename product on this machine.
3
+ *
4
+ * WHY THIS EXISTS
5
+ * ---------------
6
+ * §5.5 of the rename scope doc states the invariant plainly: the same machine
7
+ * must never have an old and a new runtime live at once. §10 names the failure
8
+ * it prevents — "dual daemon race", two binaries under two different service
9
+ * labels, each believing it holds the single-instance lock because neither can
10
+ * see the other's label.
11
+ *
12
+ * A rename cannot prevent that on its own. Changing `~/.oversky` to `~/.skrr`
13
+ * does not remove the old directory or unregister the old service; it just
14
+ * stops looking at them. So the new runtime has to LOOK, and refuse.
15
+ *
16
+ * DETECT AND REFUSE, NEVER MIGRATE AND NEVER DELETE
17
+ * -------------------------------------------------
18
+ * §0.1 was confirmed on 2026-08-31: there are no external users, and the team
19
+ * accepts re-login and reinstall. §3.2 therefore makes RESET the documented
20
+ * default, so this deliberately does not read, copy or repair old state:
21
+ *
22
+ * - Copying it forward would contradict the clean break, and §7.2 forbids
23
+ * copying a refresh token into a new location at all.
24
+ * - Deleting it would be worse. §8.4: never delete local state, Keychain
25
+ * entries or feeds to force a migration. The operator decides, from the
26
+ * cutover runbook, after taking the backup that runbook specifies.
27
+ *
28
+ * The only correct behaviour is to say precisely what was found and stop.
29
+ *
30
+ * BEST EFFORT BY CONSTRUCTION
31
+ * ---------------------------
32
+ * An unreadable home directory, a permissions error or an exotic platform must
33
+ * never be the reason an install cannot proceed — a preflight that blocks a
34
+ * clean machine is worse than one that misses a dirty one. Every probe is
35
+ * wrapped, and anything it cannot determine reads as "not found".
36
+ */
37
+ export interface LegacyStateFinding {
38
+ /** Absolute path that still exists. */
39
+ readonly path: string;
40
+ /** The directory name that identified it, e.g. `.oversky`. */
41
+ readonly root: string;
42
+ /** True when the directory holds a profile tree, i.e. a runtime actually ran. */
43
+ readonly hasProfiles: boolean;
44
+ }
45
+ export interface LegacyStateReport {
46
+ readonly findings: readonly LegacyStateFinding[];
47
+ /** True when anything was found. Callers refuse on this. */
48
+ readonly hasLegacyState: boolean;
49
+ }
50
+ /**
51
+ * Look for pre-rename state roots under `home`.
52
+ *
53
+ * `home` is injectable so the test suite can point it at a fixture rather than
54
+ * at the developer's actual home directory — a preflight test that reads the
55
+ * real `~` passes or fails based on who runs it.
56
+ */
57
+ export declare function findLegacyLocalState(home?: string): LegacyStateReport;
58
+ /**
59
+ * The message shown when preflight refuses.
60
+ *
61
+ * Written for someone who has just been stopped and wants to know why and what
62
+ * to do — so it names the exact paths, says explicitly that nothing was touched,
63
+ * and points at the runbook rather than improvising a fix. It does NOT offer a
64
+ * `--force`: the operator's next step is the backup the runbook specifies, and
65
+ * a flag that skips a safety check is the flag everyone learns to paste.
66
+ */
67
+ export declare function describeLegacyState(report: LegacyStateReport, binaryName: string): string;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.findLegacyLocalState = findLegacyLocalState;
7
+ exports.describeLegacyState = describeLegacyState;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_os_1 = __importDefault(require("node:os"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const localIdentity_js_1 = require("./localIdentity.js");
12
+ function safeExists(p) {
13
+ try {
14
+ return node_fs_1.default.existsSync(p);
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ function safeHasProfiles(dir) {
21
+ try {
22
+ const profiles = node_path_1.default.join(dir, 'profiles');
23
+ return node_fs_1.default.existsSync(profiles) && node_fs_1.default.readdirSync(profiles).length > 0;
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
29
+ /**
30
+ * Look for pre-rename state roots under `home`.
31
+ *
32
+ * `home` is injectable so the test suite can point it at a fixture rather than
33
+ * at the developer's actual home directory — a preflight test that reads the
34
+ * real `~` passes or fails based on who runs it.
35
+ */
36
+ function findLegacyLocalState(home = node_os_1.default.homedir()) {
37
+ const findings = [];
38
+ // No guard against a legacy name equalling the CURRENT root: `CONFIG_DIR_NAME`
39
+ // and `LEGACY_CONFIG_DIR_NAMES` are both literal-typed, so TypeScript proves
40
+ // they cannot overlap and rejects the comparison as unreachable. A runtime
41
+ // check here would be dead code pretending to be a safety net. The
42
+ // disjointness is asserted in the test instead, where it can catch a future
43
+ // edit to the constants.
44
+ for (const root of localIdentity_js_1.LEGACY_CONFIG_DIR_NAMES) {
45
+ const dir = node_path_1.default.join(home, root);
46
+ if (!safeExists(dir))
47
+ continue;
48
+ findings.push({ path: dir, root, hasProfiles: safeHasProfiles(dir) });
49
+ }
50
+ return { findings, hasLegacyState: findings.length > 0 };
51
+ }
52
+ /**
53
+ * The message shown when preflight refuses.
54
+ *
55
+ * Written for someone who has just been stopped and wants to know why and what
56
+ * to do — so it names the exact paths, says explicitly that nothing was touched,
57
+ * and points at the runbook rather than improvising a fix. It does NOT offer a
58
+ * `--force`: the operator's next step is the backup the runbook specifies, and
59
+ * a flag that skips a safety check is the flag everyone learns to paste.
60
+ */
61
+ function describeLegacyState(report, binaryName) {
62
+ const lines = [];
63
+ lines.push('Found state from the previous product identity on this machine:');
64
+ lines.push('');
65
+ for (const f of report.findings) {
66
+ lines.push(` ${f.path}${f.hasProfiles ? ' (a runtime has run from here)' : ''}`);
67
+ }
68
+ lines.push('');
69
+ lines.push('Installing beside it risks two runtimes holding two different service');
70
+ lines.push('labels, each believing it owns the single-instance lock.');
71
+ lines.push('');
72
+ lines.push('Nothing above has been read, copied or removed. Follow the team cutover');
73
+ lines.push('runbook — it takes a backup first — and then run this again:');
74
+ lines.push('');
75
+ lines.push(` ${binaryName} uninstall # stop and deregister the old service`);
76
+ lines.push(' # then remove the directories listed above, per the runbook');
77
+ return lines.join('\n');
78
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The names this product uses for state it leaves on a machine.
3
+ *
4
+ * WHY THESE LIVE IN ONE PLACE NOW
5
+ * ------------------------------
6
+ * Before the OverSky → skrr rename, `'.oversky'` was written as a literal in 47
7
+ * places across `auth-core`, `cli`, `daemon` and `desktop`, and the Keychain
8
+ * service prefix in about as many. Nothing related them, so nothing could tell
9
+ * you whether a given path was the machine root, a profile directory, or a
10
+ * coincidence — and changing the value meant finding all 47 and hoping.
11
+ *
12
+ * `auth-core` is the right home because it is the one module all three runtimes
13
+ * already import: the CLI (42 files), the daemon (35) and Desktop (5).
14
+ *
15
+ * WHAT MAKES THESE DIFFERENT FROM COPY
16
+ * ------------------------------------
17
+ * A name here ADDRESSES state that already exists on a real machine. Changing
18
+ * `CONFIG_DIR_NAME` does not move `~/.oversky` to `~/.skrr` — it makes the old
19
+ * directory unreachable and starts a new, empty one. The same is true of the
20
+ * Keychain service: an item is addressed BY service name, so a rename orphans
21
+ * the wrapped credential rather than migrating it.
22
+ *
23
+ * That is the intended behaviour under the pre-launch clean break (§0.1
24
+ * confirmed 2026-08-31: no external users, and the team accepts re-login). It is
25
+ * only safe because of the LEGACY_ values below, which exist so install
26
+ * preflight can NAME the old state in order to detect and refuse it. Preflight
27
+ * cannot refuse what it cannot name.
28
+ */
29
+ /** Machine-scoped state root, relative to the user's home directory. */
30
+ export declare const CONFIG_DIR_NAME = ".skrr";
31
+ /**
32
+ * Roots this product used before, newest first.
33
+ *
34
+ * NOT a compatibility path — nothing reads state from these. They exist so
35
+ * install preflight and the team cutover runbook can detect a machine that
36
+ * still carries old state and refuse to run beside it (§5.5: the same machine
37
+ * must never have an old and a new runtime live at once).
38
+ *
39
+ * `.sky` predates `.oversky` and was already handled as a legacy path before
40
+ * this rename, so it is carried forward rather than forgotten.
41
+ */
42
+ export declare const LEGACY_CONFIG_DIR_NAMES: readonly [".oversky", ".sky"];
43
+ /** Prefix for OS service labels and Keychain services (`ai.skrr.daemon`, …). */
44
+ export declare const NATIVE_ID_PREFIX = "ai.skrr";
45
+ /**
46
+ * The previous native prefix, for the same detect-and-refuse reason.
47
+ *
48
+ * Note the KEK identifier used `com.oversky.daemon.kek` while everything else
49
+ * used `ai.oversky.*`. Both are listed, because preflight has to recognise a
50
+ * machine carrying either.
51
+ */
52
+ export declare const LEGACY_NATIVE_ID_PREFIXES: readonly ["ai.oversky", "com.oversky"];
53
+ /** The user-facing binary name used in reauth messages. Overridden per runtime. */
54
+ export declare const DEFAULT_BINARY_NAME = "skrrd";
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ /**
3
+ * The names this product uses for state it leaves on a machine.
4
+ *
5
+ * WHY THESE LIVE IN ONE PLACE NOW
6
+ * ------------------------------
7
+ * Before the OverSky → skrr rename, `'.oversky'` was written as a literal in 47
8
+ * places across `auth-core`, `cli`, `daemon` and `desktop`, and the Keychain
9
+ * service prefix in about as many. Nothing related them, so nothing could tell
10
+ * you whether a given path was the machine root, a profile directory, or a
11
+ * coincidence — and changing the value meant finding all 47 and hoping.
12
+ *
13
+ * `auth-core` is the right home because it is the one module all three runtimes
14
+ * already import: the CLI (42 files), the daemon (35) and Desktop (5).
15
+ *
16
+ * WHAT MAKES THESE DIFFERENT FROM COPY
17
+ * ------------------------------------
18
+ * A name here ADDRESSES state that already exists on a real machine. Changing
19
+ * `CONFIG_DIR_NAME` does not move `~/.oversky` to `~/.skrr` — it makes the old
20
+ * directory unreachable and starts a new, empty one. The same is true of the
21
+ * Keychain service: an item is addressed BY service name, so a rename orphans
22
+ * the wrapped credential rather than migrating it.
23
+ *
24
+ * That is the intended behaviour under the pre-launch clean break (§0.1
25
+ * confirmed 2026-08-31: no external users, and the team accepts re-login). It is
26
+ * only safe because of the LEGACY_ values below, which exist so install
27
+ * preflight can NAME the old state in order to detect and refuse it. Preflight
28
+ * cannot refuse what it cannot name.
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.DEFAULT_BINARY_NAME = exports.LEGACY_NATIVE_ID_PREFIXES = exports.NATIVE_ID_PREFIX = exports.LEGACY_CONFIG_DIR_NAMES = exports.CONFIG_DIR_NAME = void 0;
32
+ /** Machine-scoped state root, relative to the user's home directory. */
33
+ exports.CONFIG_DIR_NAME = '.skrr';
34
+ /**
35
+ * Roots this product used before, newest first.
36
+ *
37
+ * NOT a compatibility path — nothing reads state from these. They exist so
38
+ * install preflight and the team cutover runbook can detect a machine that
39
+ * still carries old state and refuse to run beside it (§5.5: the same machine
40
+ * must never have an old and a new runtime live at once).
41
+ *
42
+ * `.sky` predates `.oversky` and was already handled as a legacy path before
43
+ * this rename, so it is carried forward rather than forgotten.
44
+ */
45
+ exports.LEGACY_CONFIG_DIR_NAMES = ['.oversky', '.sky'];
46
+ /** Prefix for OS service labels and Keychain services (`ai.skrr.daemon`, …). */
47
+ exports.NATIVE_ID_PREFIX = 'ai.skrr';
48
+ /**
49
+ * The previous native prefix, for the same detect-and-refuse reason.
50
+ *
51
+ * Note the KEK identifier used `com.oversky.daemon.kek` while everything else
52
+ * used `ai.oversky.*`. Both are listed, because preflight has to recognise a
53
+ * machine carrying either.
54
+ */
55
+ exports.LEGACY_NATIVE_ID_PREFIXES = ['ai.oversky', 'com.oversky'];
56
+ /** The user-facing binary name used in reauth messages. Overridden per runtime. */
57
+ exports.DEFAULT_BINARY_NAME = 'skrrd';
@@ -0,0 +1,115 @@
1
+ /** Default total wait (ms) for the callback. Matches consent-page UX. */
2
+ export declare const DEFAULT_LOGIN_TIMEOUT_MS: number;
3
+ export interface StartLocalCallbackOptions {
4
+ /**
5
+ * PKCE state parameter — must match the value sent to the consent
6
+ * page in `/api/auth/cli-init`. Generate via `pkce.generateState()`.
7
+ */
8
+ state: string;
9
+ /**
10
+ * Port to bind. 0 (default) requests an ephemeral kernel-assigned
11
+ * port. Pin a port (via `--port` or `OVERSKY_LOGIN_PORT`) when the
12
+ * user is in a firewall-allowlisted environment that pre-opened a
13
+ * single localhost port for OAuth flows.
14
+ */
15
+ port?: number;
16
+ /**
17
+ * Total time the server waits for a callback before timing out.
18
+ * Default 2 minutes. After this elapses the server closes and the
19
+ * callback promise rejects with `LocalCallbackTimeoutError`.
20
+ */
21
+ timeoutMs?: number;
22
+ /**
23
+ * Multica-convergence (P1 #5) — override the host that appears in
24
+ * `redirectUri` AND the listen address. Defaults to `'127.0.0.1'`
25
+ * and SHOULD stay that way for every local-browser flow.
26
+ *
27
+ * The override exists for one legitimate case: the CLI is on a
28
+ * different machine than the browser (e.g. a LAN-only dev box a
29
+ * teammate's browser needs to reach). In that scenario the user
30
+ * passes `--callback-host <lan-ip>` and the server binds on that
31
+ * interface so the browser can reach it.
32
+ *
33
+ * Binding a non-loopback host widens the blast radius — any device
34
+ * on that network segment can race the browser to the callback.
35
+ * Callers SHOULD only enable this after confirming with the user
36
+ * that the LAN is trusted. A warning is emitted on non-loopback
37
+ * binds.
38
+ *
39
+ * SSH port-forward users DO NOT need this — forwarding
40
+ * `-L 1455:127.0.0.1:1455` makes the remote's 127.0.0.1 reachable
41
+ * from the local browser without touching `host`.
42
+ */
43
+ host?: string;
44
+ /**
45
+ * Optional split between bind address and URL host. When provided,
46
+ * the server listens on `bindAddr` and the `redirectUri` is built
47
+ * from `host`. Use sparingly — e.g. `bindAddr='0.0.0.0'` with
48
+ * `host='10.0.0.5'` lets a browser on a teammate's machine reach
49
+ * a CLI on the LAN without hardcoding the LAN IP into the bind.
50
+ *
51
+ * Defaults to `host` when omitted.
52
+ */
53
+ bindAddr?: string;
54
+ }
55
+ export interface LocalCallbackResult {
56
+ /** Authorization code returned by the server (single-use). */
57
+ code: string;
58
+ /** State echoed back, post-validation. */
59
+ state: string;
60
+ /**
61
+ * The redirect URI this server bound to. Caller MUST pass this same
62
+ * string to /cli-init AND /cli-token-exchange (RFC 6749 §4.1.3
63
+ * redirect_uri-binding requirement).
64
+ */
65
+ redirectUri: string;
66
+ }
67
+ export interface LocalCallbackHandle {
68
+ /**
69
+ * Bound redirect URI in the form `http://127.0.0.1:<port>/callback`.
70
+ * Available synchronously after `startLocalCallback()` resolves.
71
+ */
72
+ redirectUri: string;
73
+ /**
74
+ * Resolves with the callback result on the first valid callback.
75
+ * Rejects on timeout, denial, state mismatch, malformed callback,
76
+ * post-bind server error, or caller cancellation. Safe to call
77
+ * multiple times — returns the same promise.
78
+ */
79
+ waitForCallback(): Promise<LocalCallbackResult>;
80
+ /** Cancel the wait and tear down the server. Idempotent. */
81
+ close(): void;
82
+ }
83
+ export declare class LocalCallbackError extends Error {
84
+ readonly code: string;
85
+ constructor(code: string, message: string);
86
+ }
87
+ export declare class LocalCallbackBindError extends LocalCallbackError {
88
+ readonly port: number;
89
+ readonly cause?: Error;
90
+ constructor(port: number, cause?: Error);
91
+ }
92
+ export declare class LocalCallbackTimeoutError extends LocalCallbackError {
93
+ readonly timeoutMs: number;
94
+ constructor(timeoutMs: number);
95
+ }
96
+ export declare class LocalCallbackStateMismatchError extends LocalCallbackError {
97
+ constructor();
98
+ }
99
+ export declare class LocalCallbackDeniedError extends LocalCallbackError {
100
+ readonly serverError: string;
101
+ constructor(serverError: string, description?: string);
102
+ }
103
+ export declare class LocalCallbackMalformedError extends LocalCallbackError {
104
+ constructor(reason: string);
105
+ }
106
+ /**
107
+ * Start the local 127.0.0.1 callback server. Binds synchronously
108
+ * (await'd via the listen handshake) and returns a handle whose
109
+ * `redirectUri` is immediately usable. The caller drives the rest of
110
+ * the flow and awaits `waitForCallback()` when ready.
111
+ *
112
+ * Throws `LocalCallbackBindError` if the bind fails (caller decides
113
+ * whether to retry on a different port or fall back to device-code).
114
+ */
115
+ export declare function startLocalCallback(opts: StartLocalCallbackOptions): Promise<LocalCallbackHandle>;