sleev 0.0.12 → 0.0.13

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 (143) hide show
  1. package/README.md +19 -39
  2. package/dist/auth/browser.d.ts +12 -0
  3. package/dist/auth/browser.js +33 -24
  4. package/dist/auth/browser.js.map +1 -0
  5. package/dist/auth/firebase-tokens.d.ts +39 -0
  6. package/dist/auth/firebase-tokens.js +173 -0
  7. package/dist/auth/firebase-tokens.js.map +1 -0
  8. package/dist/auth/firebase.d.ts +17 -0
  9. package/dist/auth/firebase.js +55 -0
  10. package/dist/auth/firebase.js.map +1 -0
  11. package/dist/auth/login.d.ts +22 -0
  12. package/dist/auth/login.js +31 -28
  13. package/dist/auth/login.js.map +1 -0
  14. package/dist/auth/loopback.d.ts +19 -0
  15. package/dist/auth/loopback.js +114 -44
  16. package/dist/auth/loopback.js.map +1 -0
  17. package/dist/auth/schema.d.ts +26 -0
  18. package/dist/auth/schema.js +59 -17
  19. package/dist/auth/schema.js.map +1 -0
  20. package/dist/auth/token.d.ts +19 -0
  21. package/dist/auth/token.js +53 -0
  22. package/dist/auth/token.js.map +1 -0
  23. package/dist/auth/verify.d.ts +6 -0
  24. package/dist/auth/verify.js +22 -0
  25. package/dist/auth/verify.js.map +1 -0
  26. package/dist/cli.d.ts +8 -0
  27. package/dist/cli.js +245 -59
  28. package/dist/cli.js.map +1 -0
  29. package/dist/config/paths.d.ts +12 -0
  30. package/dist/config/paths.js +34 -0
  31. package/dist/config/paths.js.map +1 -0
  32. package/dist/config/store.d.ts +63 -0
  33. package/dist/config/store.js +270 -0
  34. package/dist/config/store.js.map +1 -0
  35. package/dist/control/local-installations.d.ts +13 -0
  36. package/dist/control/local-installations.js +37 -0
  37. package/dist/control/local-installations.js.map +1 -0
  38. package/dist/daemon/config.d.ts +10 -0
  39. package/dist/daemon/config.js +52 -0
  40. package/dist/daemon/config.js.map +1 -0
  41. package/dist/daemon/controller.d.ts +29 -0
  42. package/dist/daemon/controller.js +34 -0
  43. package/dist/daemon/controller.js.map +1 -0
  44. package/dist/daemon/health.d.ts +8 -0
  45. package/dist/daemon/health.js +47 -0
  46. package/dist/daemon/health.js.map +1 -0
  47. package/dist/daemon/start.d.ts +5 -0
  48. package/dist/daemon/start.js +24 -0
  49. package/dist/daemon/start.js.map +1 -0
  50. package/dist/daemon/systemd.d.ts +35 -0
  51. package/dist/daemon/systemd.js +181 -0
  52. package/dist/daemon/systemd.js.map +1 -0
  53. package/dist/gateway/acquire.d.ts +14 -0
  54. package/dist/gateway/acquire.js +112 -0
  55. package/dist/gateway/acquire.js.map +1 -0
  56. package/dist/gateway/manifest.d.ts +26 -0
  57. package/dist/gateway/manifest.js +98 -0
  58. package/dist/gateway/manifest.js.map +1 -0
  59. package/dist/gateway/platform.d.ts +5 -0
  60. package/dist/gateway/platform.js +27 -0
  61. package/dist/gateway/platform.js.map +1 -0
  62. package/dist/harness/claude.d.ts +8 -0
  63. package/dist/{apps → harness}/claude.js +28 -28
  64. package/dist/harness/claude.js.map +1 -0
  65. package/dist/harness/codex.d.ts +10 -0
  66. package/dist/{apps → harness}/codex.js +50 -45
  67. package/dist/harness/codex.js.map +1 -0
  68. package/dist/harness/index.d.ts +13 -0
  69. package/dist/harness/index.js +63 -0
  70. package/dist/harness/index.js.map +1 -0
  71. package/dist/harness/opencode-config.d.ts +8 -0
  72. package/dist/{apps → harness}/opencode-config.js +116 -83
  73. package/dist/harness/opencode-config.js.map +1 -0
  74. package/dist/harness/opencode.d.ts +14 -0
  75. package/dist/harness/opencode.js +50 -0
  76. package/dist/harness/opencode.js.map +1 -0
  77. package/dist/harness/providers.d.ts +8 -0
  78. package/dist/harness/providers.js +30 -0
  79. package/dist/harness/providers.js.map +1 -0
  80. package/dist/harness/types.d.ts +14 -0
  81. package/dist/harness/types.js +2 -0
  82. package/dist/harness/types.js.map +1 -0
  83. package/dist/product.d.ts +12 -0
  84. package/dist/product.js +53 -10
  85. package/dist/product.js.map +1 -0
  86. package/dist/tui/clipboard.d.ts +1 -0
  87. package/dist/tui/clipboard.js +48 -0
  88. package/dist/tui/clipboard.js.map +1 -0
  89. package/dist/tui/openTuiApp.d.ts +1 -0
  90. package/dist/tui/openTuiApp.js +1436 -0
  91. package/dist/tui/openTuiApp.js.map +1 -0
  92. package/dist/tui/pages/flow-screen.d.ts +50 -0
  93. package/dist/tui/pages/flow-screen.js +435 -0
  94. package/dist/tui/pages/flow-screen.js.map +1 -0
  95. package/dist/tui/pages/footer.d.ts +7 -0
  96. package/dist/tui/pages/footer.js +14 -0
  97. package/dist/tui/pages/footer.js.map +1 -0
  98. package/dist/tui/pages/home-screen.d.ts +19 -0
  99. package/dist/tui/pages/home-screen.js +89 -0
  100. package/dist/tui/pages/home-screen.js.map +1 -0
  101. package/dist/tui/pages/index.d.ts +17 -0
  102. package/dist/tui/pages/index.js +11 -0
  103. package/dist/tui/pages/index.js.map +1 -0
  104. package/dist/tui/pages/local-setup-screen.d.ts +23 -0
  105. package/dist/tui/pages/local-setup-screen.js +134 -0
  106. package/dist/tui/pages/local-setup-screen.js.map +1 -0
  107. package/dist/tui/pages/location-card.d.ts +19 -0
  108. package/dist/tui/pages/location-card.js +116 -0
  109. package/dist/tui/pages/location-card.js.map +1 -0
  110. package/dist/tui/pages/menu-screen.d.ts +24 -0
  111. package/dist/tui/pages/menu-screen.js +117 -0
  112. package/dist/tui/pages/menu-screen.js.map +1 -0
  113. package/dist/tui/pages/session-grid.d.ts +23 -0
  114. package/dist/tui/pages/session-grid.js +552 -0
  115. package/dist/tui/pages/session-grid.js.map +1 -0
  116. package/dist/tui/pages/setup-screen.d.ts +15 -0
  117. package/dist/tui/pages/setup-screen.js +80 -0
  118. package/dist/tui/pages/setup-screen.js.map +1 -0
  119. package/dist/tui/pages/terminal-log.d.ts +14 -0
  120. package/dist/tui/pages/terminal-log.js +104 -0
  121. package/dist/tui/pages/terminal-log.js.map +1 -0
  122. package/dist/tui/pages/welcome-screen.d.ts +16 -0
  123. package/dist/tui/pages/welcome-screen.js +80 -0
  124. package/dist/tui/pages/welcome-screen.js.map +1 -0
  125. package/dist/tui/saved-tokens.d.ts +10 -0
  126. package/dist/tui/saved-tokens.js +104 -0
  127. package/dist/tui/saved-tokens.js.map +1 -0
  128. package/dist/tui/theme.d.ts +25 -0
  129. package/dist/tui/theme.js +27 -0
  130. package/dist/tui/theme.js.map +1 -0
  131. package/package.json +55 -38
  132. package/dist/apps/opencode.js +0 -158
  133. package/dist/apps.js +0 -148
  134. package/dist/auth/command.js +0 -109
  135. package/dist/auth/index.js +0 -2
  136. package/dist/auth/validation.js +0 -27
  137. package/dist/credentials/index.js +0 -3
  138. package/dist/credentials/schema.js +0 -63
  139. package/dist/credentials/store.js +0 -52
  140. package/dist/harnesses.js +0 -66
  141. package/dist/index.js +0 -3
  142. package/dist/providers.js +0 -30
  143. package/dist/terminal.js +0 -55
package/README.md CHANGED
@@ -1,49 +1,29 @@
1
- # sleev
1
+ # sleeved
2
2
 
3
- The Sleev command-line tool.
3
+ Local Sleeve proxy daemon and setup TUI.
4
4
 
5
- Use it to sign in to Sleev from your terminal and connect supported local apps.
5
+ This repository is intentionally early. The first milestone is the local
6
+ installer shell:
6
7
 
7
- ## Login
8
+ - launch a TUI
9
+ - persist local config under platform-standard user directories
10
+ - prepare daemon lifecycle commands
11
+ - prepare harness setup commands
8
12
 
9
- Run:
13
+ OpenTUI currently runs on Bun. The package is still structured for npm
14
+ publication, with non-TUI commands kept Node-compatible while the interactive
15
+ TUI entrypoint is isolated.
10
16
 
11
17
  ```bash
12
- npx sleev auth login
18
+ npm install
19
+ npm run check
20
+ npm test
21
+ npm run build
13
22
  ```
14
23
 
15
- Sleev will:
16
-
17
- 1. Open your browser.
18
- 2. Ask you to sign in or create an account.
19
- 3. Return you to the terminal after login.
20
- 4. Ask which supported local apps you want to use with Sleev.
21
-
22
- After that, your local apps can use Sleev without logging in again.
23
-
24
- ## Logout
25
-
26
- Run:
27
-
28
24
  ```bash
29
- npx sleev auth logout
25
+ sleeved
26
+ sleeved paths
27
+ sleeved status
28
+ sleeved daemon status
30
29
  ```
31
-
32
- This removes your local Sleev login from this machine.
33
-
34
- ## Already Logged In
35
-
36
- If you need to replace your current local login, run:
37
-
38
- ```bash
39
- npx sleev auth login --force
40
- ```
41
-
42
- ## Requirements
43
-
44
- - Node.js 20 or newer
45
- - A browser you can sign in with
46
-
47
- ## Notes
48
-
49
- Your login is stored locally on your machine. Do not share your Sleev auth file.
@@ -0,0 +1,12 @@
1
+ type Command = {
2
+ command: string;
3
+ args: string[];
4
+ };
5
+ type Context = {
6
+ platform?: NodeJS.Platform;
7
+ env?: NodeJS.ProcessEnv;
8
+ release?: string;
9
+ };
10
+ export declare function openBrowser(url: string): Promise<boolean>;
11
+ export declare function browserCommands(url: string, context?: Context): Command[];
12
+ export {};
@@ -1,6 +1,6 @@
1
- // Cross-platform browser opener used by the login flow.
2
- import { spawn } from 'node:child_process';
3
- import { existsSync, readFileSync } from 'node:fs';
1
+ // Cross-platform browser opener used by the login handoff, including WSL fallbacks.
2
+ import { spawn } from "node:child_process";
3
+ import { existsSync, readFileSync } from "node:fs";
4
4
  export async function openBrowser(url) {
5
5
  const commands = browserCommands(url);
6
6
  for (const command of commands) {
@@ -13,7 +13,7 @@ export async function openBrowser(url) {
13
13
  function open(command) {
14
14
  return new Promise((resolve) => {
15
15
  const child = spawn(command.command, command.args, {
16
- stdio: 'ignore',
16
+ stdio: "ignore",
17
17
  windowsHide: true,
18
18
  });
19
19
  const timer = setTimeout(() => finish(true), 3000);
@@ -23,44 +23,51 @@ function open(command) {
23
23
  return;
24
24
  done = true;
25
25
  clearTimeout(timer);
26
- child.removeAllListeners('error');
27
- child.removeAllListeners('close');
26
+ child.removeAllListeners("error");
27
+ child.removeAllListeners("close");
28
28
  if (ok)
29
29
  child.unref();
30
30
  resolve(ok);
31
31
  };
32
- child.once('error', () => finish(false));
33
- child.once('close', (code) => finish(code === 0));
32
+ child.once("error", () => finish(false));
33
+ child.once("close", (code) => finish(code === 0));
34
34
  });
35
35
  }
36
36
  export function browserCommands(url, context = {}) {
37
37
  const platform = context.platform ?? process.platform;
38
- if (platform === 'darwin')
39
- return [{ command: 'open', args: [url] }];
40
- if (platform === 'win32') {
38
+ if (platform === "darwin")
39
+ return [{ command: "open", args: [url] }];
40
+ if (platform === "win32") {
41
41
  return [
42
- { command: 'rundll32', args: ['url.dll,FileProtocolHandler', url] },
43
- { command: 'powershell', args: powershellArgs(url) },
42
+ { command: "rundll32", args: ["url.dll,FileProtocolHandler", url] },
43
+ { command: "powershell", args: powershellArgs(url) },
44
44
  ];
45
45
  }
46
- if (platform === 'linux' && isWsl(context)) {
46
+ if (platform === "linux" && isWsl(context)) {
47
47
  return [
48
48
  ...linuxCommands(url),
49
- { command: 'wslview', args: [url] },
50
- { command: 'powershell.exe', args: powershellArgs(url) },
49
+ { command: "wslview", args: [url] },
50
+ { command: "powershell.exe", args: powershellArgs(url) },
51
51
  ];
52
52
  }
53
53
  return linuxCommands(url);
54
54
  }
55
55
  function linuxCommands(url) {
56
56
  return [
57
- { command: 'sensible-browser', args: [url] },
58
- { command: 'gio', args: ['open', url] },
59
- { command: 'xdg-open', args: [url] },
57
+ { command: "sensible-browser", args: [url] },
58
+ { command: "gio", args: ["open", url] },
59
+ { command: "xdg-open", args: [url] },
60
60
  ];
61
61
  }
62
62
  function powershellArgs(url) {
63
- return ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', `Start-Process '${ps(url)}'`];
63
+ return [
64
+ "-NoProfile",
65
+ "-NonInteractive",
66
+ "-ExecutionPolicy",
67
+ "Bypass",
68
+ "-Command",
69
+ `Start-Process '${ps(url)}'`,
70
+ ];
64
71
  }
65
72
  function ps(value) {
66
73
  return value.replace(/'/g, "''");
@@ -70,11 +77,13 @@ function isWsl(context) {
70
77
  if (env.WSL_DISTRO_NAME || env.WSL_INTEROP)
71
78
  return true;
72
79
  const release = context.release ?? kernelRelease();
73
- return release.toLowerCase().includes('microsoft') || release.toLowerCase().includes('wsl');
80
+ return (release.toLowerCase().includes("microsoft") ||
81
+ release.toLowerCase().includes("wsl"));
74
82
  }
75
83
  function kernelRelease() {
76
- const path = '/proc/sys/kernel/osrelease';
84
+ const path = "/proc/sys/kernel/osrelease";
77
85
  if (!existsSync(path))
78
- return '';
79
- return readFileSync(path, 'utf8');
86
+ return "";
87
+ return readFileSync(path, "utf8");
80
88
  }
89
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/auth/browser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { FirebaseLoginResult } from "./firebase.js";
2
+ export type StoredFirebaseAuth = {
3
+ version: 1;
4
+ refreshToken: string;
5
+ accountId: string;
6
+ email: string | null;
7
+ label: string | null;
8
+ updatedAt: string;
9
+ };
10
+ export declare class FirebaseAuthRequiredError extends Error {
11
+ constructor(message?: string);
12
+ }
13
+ export declare class FirebaseTokenError extends Error {
14
+ constructor(message: string);
15
+ }
16
+ export declare function readStoredFirebaseAuth(file?: string): Promise<StoredFirebaseAuth | null>;
17
+ export declare function writeStoredFirebaseAuth(auth: StoredFirebaseAuth, file?: string): Promise<void>;
18
+ export declare function clearStoredFirebaseAuth(file?: string): Promise<void>;
19
+ export declare function saveFirebaseLogin(result: FirebaseLoginResult, now?: () => Date): Promise<StoredFirebaseAuth>;
20
+ export type FirebaseTokenManagerDeps = {
21
+ apiKey?: string;
22
+ fetchImpl?: typeof fetch;
23
+ now?: () => number;
24
+ load?: () => Promise<StoredFirebaseAuth | null>;
25
+ save?: (auth: StoredFirebaseAuth) => Promise<void>;
26
+ clear?: () => Promise<void>;
27
+ };
28
+ export declare class FirebaseTokenManager {
29
+ private readonly apiKey;
30
+ private readonly fetchImpl;
31
+ private readonly now;
32
+ private readonly load;
33
+ private readonly save;
34
+ private readonly clear;
35
+ private cache;
36
+ constructor(deps?: FirebaseTokenManagerDeps);
37
+ getIdToken(forceRefresh?: boolean): Promise<string>;
38
+ private exchange;
39
+ }
@@ -0,0 +1,173 @@
1
+ // Stores the Firebase refresh token and mints short-lived ID tokens from it
2
+ // so the CLI can call the control server with Firebase auth, headlessly.
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { resolveSleevedPaths } from "../config/paths.js";
6
+ import { firebaseConfig } from "../product.js";
7
+ const SECURE_TOKEN_URL = "https://securetoken.googleapis.com/v1/token";
8
+ // Refresh a little early so an in-flight request never carries a token that
9
+ // expires mid-flight.
10
+ const EXPIRY_SKEW_MS = 60_000;
11
+ // Thrown when there is no usable refresh token (never signed in, or the
12
+ // refresh token was revoked/expired). Callers should prompt a fresh sign-in.
13
+ export class FirebaseAuthRequiredError extends Error {
14
+ constructor(message = "Firebase sign-in is required.") {
15
+ super(message);
16
+ this.name = "FirebaseAuthRequiredError";
17
+ }
18
+ }
19
+ export class FirebaseTokenError extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = "FirebaseTokenError";
23
+ }
24
+ }
25
+ export async function readStoredFirebaseAuth(file = resolveSleevedPaths().firebaseCredentialsFile) {
26
+ try {
27
+ return normalize(JSON.parse(await fs.readFile(file, "utf8")));
28
+ }
29
+ catch (error) {
30
+ if (isNotFoundError(error))
31
+ return null;
32
+ throw error;
33
+ }
34
+ }
35
+ export async function writeStoredFirebaseAuth(auth, file = resolveSleevedPaths().firebaseCredentialsFile) {
36
+ await fs.mkdir(path.dirname(file), { recursive: true });
37
+ await fs.writeFile(file, `${JSON.stringify(auth, null, 2)}\n`, {
38
+ mode: 0o600,
39
+ });
40
+ }
41
+ export async function clearStoredFirebaseAuth(file = resolveSleevedPaths().firebaseCredentialsFile) {
42
+ try {
43
+ await fs.unlink(file);
44
+ }
45
+ catch (error) {
46
+ if (!isNotFoundError(error))
47
+ throw error;
48
+ }
49
+ }
50
+ export async function saveFirebaseLogin(result, now = () => new Date()) {
51
+ const auth = {
52
+ version: 1,
53
+ refreshToken: result.refreshToken,
54
+ accountId: result.accountId,
55
+ email: result.email,
56
+ label: result.label,
57
+ updatedAt: now().toISOString(),
58
+ };
59
+ await writeStoredFirebaseAuth(auth);
60
+ return auth;
61
+ }
62
+ // Caches the ID token in memory and refreshes it from the stored refresh
63
+ // token when it is missing or about to expire.
64
+ export class FirebaseTokenManager {
65
+ apiKey;
66
+ fetchImpl;
67
+ now;
68
+ load;
69
+ save;
70
+ clear;
71
+ cache = null;
72
+ constructor(deps = {}) {
73
+ this.apiKey = deps.apiKey ?? firebaseConfig().apiKey;
74
+ this.fetchImpl = deps.fetchImpl ?? fetch;
75
+ this.now = deps.now ?? Date.now;
76
+ this.load = deps.load ?? (() => readStoredFirebaseAuth());
77
+ this.save = deps.save ?? ((auth) => writeStoredFirebaseAuth(auth));
78
+ this.clear = deps.clear ?? (() => clearStoredFirebaseAuth());
79
+ }
80
+ async getIdToken(forceRefresh = false) {
81
+ if (!forceRefresh &&
82
+ this.cache &&
83
+ this.now() < this.cache.expiresAtMs - EXPIRY_SKEW_MS) {
84
+ return this.cache.idToken;
85
+ }
86
+ const stored = await this.load();
87
+ if (!stored) {
88
+ throw new FirebaseAuthRequiredError();
89
+ }
90
+ const exchanged = await this.exchange(stored.refreshToken);
91
+ // Firebase may rotate the refresh token; persist it if it changed.
92
+ if (exchanged.refresh_token !== stored.refreshToken) {
93
+ await this.save({
94
+ ...stored,
95
+ refreshToken: exchanged.refresh_token,
96
+ updatedAt: new Date(this.now()).toISOString(),
97
+ });
98
+ }
99
+ const ttlMs = Number(exchanged.expires_in) * 1000;
100
+ this.cache = {
101
+ idToken: exchanged.id_token,
102
+ expiresAtMs: this.now() + (Number.isFinite(ttlMs) ? ttlMs : 3_600_000),
103
+ };
104
+ return exchanged.id_token;
105
+ }
106
+ async exchange(refreshToken) {
107
+ let response;
108
+ try {
109
+ response = await this.fetchImpl(`${SECURE_TOKEN_URL}?key=${encodeURIComponent(this.apiKey)}`, {
110
+ method: "POST",
111
+ headers: {
112
+ "content-type": "application/x-www-form-urlencoded",
113
+ },
114
+ body: `grant_type=refresh_token&refresh_token=${encodeURIComponent(refreshToken)}`,
115
+ });
116
+ }
117
+ catch (error) {
118
+ throw new FirebaseTokenError(`Could not reach Firebase token endpoint: ${error instanceof Error ? error.message : "network error"}`);
119
+ }
120
+ const body = (await response.json().catch(() => null));
121
+ if (!response.ok) {
122
+ const reason = body?.error?.message ?? `HTTP ${response.status}`;
123
+ // A revoked/invalid/expired refresh token is unrecoverable
124
+ // without a fresh browser sign-in.
125
+ if (reason === "TOKEN_EXPIRED" ||
126
+ reason === "USER_DISABLED" ||
127
+ reason === "USER_NOT_FOUND" ||
128
+ reason === "INVALID_REFRESH_TOKEN" ||
129
+ reason === "INVALID_GRANT") {
130
+ this.cache = null;
131
+ await this.clear();
132
+ throw new FirebaseAuthRequiredError(`Firebase session is no longer valid (${reason}). Sign in again.`);
133
+ }
134
+ throw new FirebaseTokenError(`Firebase token refresh failed: ${reason}`);
135
+ }
136
+ if (!body ||
137
+ typeof body.id_token !== "string" ||
138
+ typeof body.refresh_token !== "string" ||
139
+ typeof body.expires_in !== "string") {
140
+ throw new FirebaseTokenError("Firebase token response was malformed.");
141
+ }
142
+ return {
143
+ id_token: body.id_token,
144
+ refresh_token: body.refresh_token,
145
+ expires_in: body.expires_in,
146
+ };
147
+ }
148
+ }
149
+ function normalize(value) {
150
+ if (typeof value !== "object" || value === null)
151
+ return null;
152
+ const v = value;
153
+ if (typeof v.refreshToken !== "string" || !v.refreshToken)
154
+ return null;
155
+ if (typeof v.accountId !== "string" || !v.accountId)
156
+ return null;
157
+ return {
158
+ version: 1,
159
+ refreshToken: v.refreshToken,
160
+ accountId: v.accountId,
161
+ email: typeof v.email === "string" ? v.email : null,
162
+ label: typeof v.label === "string" ? v.label : null,
163
+ updatedAt: typeof v.updatedAt === "string"
164
+ ? v.updatedAt
165
+ : new Date(0).toISOString(),
166
+ };
167
+ }
168
+ function isNotFoundError(error) {
169
+ return (error instanceof Error &&
170
+ "code" in error &&
171
+ error.code === "ENOENT");
172
+ }
173
+ //# sourceMappingURL=firebase-tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"firebase-tokens.js","sourceRoot":"","sources":["../../src/auth/firebase-tokens.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { Writable } from "node:stream";
2
+ export type FirebaseLoginResult = {
3
+ refreshToken: string;
4
+ accountId: string;
5
+ email: string | null;
6
+ label: string | null;
7
+ };
8
+ export type FirebaseLoginOptions = {
9
+ webOrigin?: string;
10
+ label?: string;
11
+ output?: Writable;
12
+ opener?: (url: string) => Promise<boolean>;
13
+ onUrl?: (url: string) => void;
14
+ timeoutMs?: number;
15
+ signal?: AbortSignal;
16
+ };
17
+ export declare function firebaseLogin(options?: FirebaseLoginOptions): Promise<FirebaseLoginResult>;
@@ -0,0 +1,55 @@
1
+ // Browser handoff that returns a Firebase refresh token for headless,
2
+ // Firebase-authenticated control-server calls (local installation mode).
3
+ import { randomBytes } from "node:crypto";
4
+ import { hostname } from "node:os";
5
+ import { webOrigin as defaultWebOrigin } from "../product.js";
6
+ import { openBrowser } from "./browser.js";
7
+ import { startFirebaseLoopback } from "./loopback.js";
8
+ const FIREBASE_TIMEOUT_MS = 10 * 60 * 1000;
9
+ export async function firebaseLogin(options = {}) {
10
+ const output = options.output ?? process.stdout;
11
+ const origin = options.webOrigin ?? defaultWebOrigin();
12
+ const opener = options.opener ?? openBrowser;
13
+ const state = randomBytes(32).toString("base64url");
14
+ const loopback = await startFirebaseLoopback({
15
+ state,
16
+ webOrigin: origin,
17
+ timeoutMs: options.timeoutMs ?? FIREBASE_TIMEOUT_MS,
18
+ ...(options.signal ? { signal: options.signal } : {}),
19
+ });
20
+ const url = firebaseUrl(origin, state, loopback.callback, options.label);
21
+ options.onUrl?.(url);
22
+ output.write(`Opening ${origin} to sign in.\n`);
23
+ try {
24
+ const opened = await opener(url);
25
+ if (!opened) {
26
+ output.write("Could not open a browser automatically. Open this URL instead:\n");
27
+ output.write(`${url}\n`);
28
+ }
29
+ output.write("Waiting for browser confirmation...\n");
30
+ const payload = await loopback.wait;
31
+ output.write("Browser login confirmed.\n");
32
+ return {
33
+ refreshToken: payload.refreshToken,
34
+ accountId: payload.accountId,
35
+ email: payload.email,
36
+ label: payload.label,
37
+ };
38
+ }
39
+ finally {
40
+ await loopback.close();
41
+ }
42
+ }
43
+ function firebaseUrl(origin, state, callback, label) {
44
+ const url = new URL("/cli/login", origin);
45
+ url.searchParams.set("mode", "signin");
46
+ url.searchParams.set("client", "sleeved");
47
+ url.searchParams.set("handoff", "firebase");
48
+ url.searchParams.set("state", state);
49
+ url.searchParams.set("callback", callback);
50
+ url.searchParams.set("device", hostname().slice(0, 80));
51
+ if (label)
52
+ url.searchParams.set("label", label);
53
+ return url.toString();
54
+ }
55
+ //# sourceMappingURL=firebase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"firebase.js","sourceRoot":"","sources":["../../src/auth/firebase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { Writable } from "node:stream";
2
+ import type { AccountIdentity } from "./schema.js";
3
+ export type LoginMode = "signup" | "signin";
4
+ export type LoginResult = {
5
+ label: string | null;
6
+ accountId: string;
7
+ email: string | null;
8
+ createdAt: string;
9
+ };
10
+ export type LoginOptions = {
11
+ mode?: LoginMode;
12
+ webOrigin?: string;
13
+ output?: Writable;
14
+ opener?: (url: string) => Promise<boolean>;
15
+ onUrl?: (url: string) => void;
16
+ timeoutMs?: number;
17
+ signal?: AbortSignal;
18
+ now?: () => Date;
19
+ expectedAccount?: AccountIdentity;
20
+ label?: string;
21
+ };
22
+ export declare function login(options?: LoginOptions): Promise<LoginResult>;
@@ -1,43 +1,42 @@
1
- // Browser login orchestration for `sleev auth login`.
2
- import { randomBytes } from 'node:crypto';
3
- import { hostname } from 'node:os';
4
- import { webOrigin as defaultWebOrigin } from '../product.js';
5
- import { brand, field, line, note, step, success } from '../terminal.js';
6
- import { openBrowser } from './browser.js';
7
- import { startLoopback } from './loopback.js';
1
+ import { randomBytes } from "node:crypto";
2
+ import { hostname } from "node:os";
3
+ import { webOrigin as defaultWebOrigin } from "../product.js";
4
+ import { openBrowser } from "./browser.js";
5
+ import { startLoopback } from "./loopback.js";
8
6
  const LOGIN_TIMEOUT_MS = 10 * 60 * 1000;
9
7
  export async function login(options = {}) {
10
- const output = options.output ?? process.stdout;
11
- brand(output);
12
- return runHandoff({ ...options, mode: options.mode ?? 'signup', output });
8
+ return runHandoff({ ...options, mode: options.mode ?? "signin" });
13
9
  }
14
10
  async function runHandoff(options) {
15
- const output = options.output;
16
- const mode = options.mode;
11
+ const output = options.output ?? process.stdout;
17
12
  const origin = options.webOrigin ?? defaultWebOrigin();
18
13
  const opener = options.opener ?? openBrowser;
19
- const state = randomBytes(32).toString('base64url');
14
+ const state = randomBytes(32).toString("base64url");
20
15
  const loopback = await startLoopback({
21
16
  state,
22
17
  webOrigin: origin,
23
18
  timeoutMs: options.timeoutMs ?? LOGIN_TIMEOUT_MS,
19
+ ...(options.signal ? { signal: options.signal } : {}),
20
+ ...(options.expectedAccount
21
+ ? { expectedAccount: options.expectedAccount }
22
+ : {}),
24
23
  });
25
- const url = loginUrl(origin, mode, state, loopback.callback);
26
- const action = mode === 'signup' ? 'create your account' : 'log in';
27
- step(output, `Opening browser to ${action}`);
28
- field(output, 'url', url);
24
+ const url = loginUrl(origin, options.mode, state, loopback.callback, options.label);
25
+ options.onUrl?.(url);
26
+ output.write(`Opening ${origin} to sign in.\n`);
29
27
  try {
30
28
  const opened = await opener(url);
31
29
  if (!opened) {
32
- note(output, 'Could not open a browser automatically. Open the URL above instead');
30
+ output.write("Could not open a browser automatically. Open this URL instead:\n");
31
+ output.write(`${url}\n`);
33
32
  }
34
- note(output, 'Waiting for the browser confirmation...');
33
+ output.write("Waiting for browser confirmation...\n");
35
34
  const payload = await loopback.wait;
36
- success(output, 'Browser login confirmed');
35
+ output.write("Browser login confirmed.\n");
37
36
  return {
38
- token: payload.token,
39
37
  label: payload.label,
40
- proxyUrl: payload.proxyUrl,
38
+ accountId: payload.accountId,
39
+ email: payload.email,
41
40
  createdAt: (options.now ?? (() => new Date()))().toISOString(),
42
41
  };
43
42
  }
@@ -45,11 +44,15 @@ async function runHandoff(options) {
45
44
  await loopback.close();
46
45
  }
47
46
  }
48
- function loginUrl(origin, mode, state, callback) {
49
- const url = new URL('/cli/login', origin);
50
- url.searchParams.set('mode', mode);
51
- url.searchParams.set('state', state);
52
- url.searchParams.set('callback', callback);
53
- url.searchParams.set('device', hostname().slice(0, 80));
47
+ function loginUrl(origin, mode, state, callback, label) {
48
+ const url = new URL("/cli/login", origin);
49
+ url.searchParams.set("mode", mode);
50
+ if (label)
51
+ url.searchParams.set("label", label);
52
+ url.searchParams.set("client", "sleeved");
53
+ url.searchParams.set("state", state);
54
+ url.searchParams.set("callback", callback);
55
+ url.searchParams.set("device", hostname().slice(0, 80));
54
56
  return url.toString();
55
57
  }
58
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/auth/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { type AccountIdentity, type FirebaseHandoff, type LoginHandoff, type TokenHandoff } from "./schema.js";
2
+ export type Loopback<T = LoginHandoff> = {
3
+ callback: string;
4
+ wait: Promise<T>;
5
+ close: () => Promise<void>;
6
+ };
7
+ type BaseLoopbackOptions = {
8
+ state: string;
9
+ webOrigin: string;
10
+ timeoutMs: number;
11
+ signal?: AbortSignal;
12
+ };
13
+ export type LoopbackOptions = BaseLoopbackOptions & {
14
+ expectedAccount?: AccountIdentity;
15
+ };
16
+ export declare function startLoopback(options: LoopbackOptions): Promise<Loopback>;
17
+ export declare function startTokenLoopback(options: BaseLoopbackOptions): Promise<Loopback<TokenHandoff>>;
18
+ export declare function startFirebaseLoopback(options: BaseLoopbackOptions): Promise<Loopback<FirebaseHandoff>>;
19
+ export {};