@balise.dev/cli 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -8
- package/dist/index.js +30 -28
- package/package.json +1 -1
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -28,8 +28,8 @@ balise sync
|
|
|
28
28
|
|
|
29
29
|
| Command | Description |
|
|
30
30
|
|---|---|
|
|
31
|
-
| `balise login` | OAuth2 PKCE loopback login against Supabase GoTrue. Tokens stored in
|
|
32
|
-
| `balise logout` |
|
|
31
|
+
| `balise login` | OAuth2 PKCE loopback login against Supabase GoTrue. Tokens stored in `~/.config/balise/credentials.json` (mode `0600`). |
|
|
32
|
+
| `balise logout` | Remove the credentials file. |
|
|
33
33
|
| `balise whoami` | Print current user (`GET /v1/me`). |
|
|
34
34
|
| `balise init` | Create/link a repo & write `.balise/config`. |
|
|
35
35
|
| `balise sync` | Tarball current git repo → multipart upload → poll progress. |
|
|
@@ -48,15 +48,18 @@ owner_login = colin
|
|
|
48
48
|
url = https://api.balise.dev
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
##
|
|
51
|
+
## Credentials storage
|
|
52
52
|
|
|
53
|
-
Tokens are
|
|
53
|
+
Tokens are written to a plaintext JSON file with restrictive permissions:
|
|
54
54
|
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
55
|
+
- **Path**: `$XDG_CONFIG_HOME/balise/credentials.json` (defaults to `~/.config/balise/credentials.json`)
|
|
56
|
+
- **Permissions**: file `0600`, parent directory `0700` — re-enforced on every write
|
|
57
|
+
- **Override path**: `BALISE_CREDENTIALS_FILE=<path>`
|
|
58
|
+
- **Stateless / CI**: `BALISE_TOKEN=<jwt>` bypasses the file entirely (no file is ever read or written)
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
> Windows: file storage is **not yet supported** — use the `BALISE_TOKEN` env var.
|
|
61
|
+
|
|
62
|
+
If you need OS-keychain-grade protection, set `BALISE_CREDENTIALS_FILE` to a path on an encrypted volume, or feed `BALISE_TOKEN` from your existing secret manager.
|
|
60
63
|
|
|
61
64
|
## Dev
|
|
62
65
|
|
package/dist/index.js
CHANGED
|
@@ -31,7 +31,8 @@ var FAILURE_HTML = `<!doctype html>
|
|
|
31
31
|
<html><head><title>Balise \u2014 Login failed</title></head>
|
|
32
32
|
<body style="font-family:system-ui;padding:4rem;text-align:center">
|
|
33
33
|
<h1>Login failed</h1><p>See your terminal for details.</p></body></html>`;
|
|
34
|
-
function startLoopbackServer(
|
|
34
|
+
function startLoopbackServer(opts) {
|
|
35
|
+
const timeoutMs = opts.timeoutMs ?? 3e5;
|
|
35
36
|
let resolveCode;
|
|
36
37
|
let rejectCode;
|
|
37
38
|
const waitForCode = new Promise((res, rej) => {
|
|
@@ -40,18 +41,24 @@ function startLoopbackServer(timeoutMs = 3e5) {
|
|
|
40
41
|
});
|
|
41
42
|
const server = http.createServer((req, res) => {
|
|
42
43
|
if (!req.url) return;
|
|
43
|
-
const url = new URL(req.url, "http://
|
|
44
|
+
const url = new URL(req.url, "http://127.0.0.1");
|
|
44
45
|
if (url.pathname !== "/callback") {
|
|
45
46
|
res.writeHead(404).end("Not Found");
|
|
46
47
|
return;
|
|
47
48
|
}
|
|
48
49
|
const code = url.searchParams.get("code");
|
|
49
50
|
const error = url.searchParams.get("error");
|
|
51
|
+
const state = url.searchParams.get("state");
|
|
50
52
|
if (error) {
|
|
51
53
|
res.writeHead(400, { "Content-Type": "text/html" }).end(FAILURE_HTML);
|
|
52
54
|
rejectCode(new Error(`OAuth error: ${error}`));
|
|
53
55
|
return;
|
|
54
56
|
}
|
|
57
|
+
if (state !== opts.expectedState) {
|
|
58
|
+
res.writeHead(400, { "Content-Type": "text/html" }).end(FAILURE_HTML);
|
|
59
|
+
rejectCode(new Error("OAuth state mismatch"));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
55
62
|
if (!code) {
|
|
56
63
|
res.writeHead(400, { "Content-Type": "text/html" }).end(FAILURE_HTML);
|
|
57
64
|
rejectCode(new Error("OAuth callback missing `code`"));
|
|
@@ -319,11 +326,12 @@ async function runLogin(opts) {
|
|
|
319
326
|
const verifier = generateCodeVerifier();
|
|
320
327
|
const challenge = codeChallengeFor(verifier);
|
|
321
328
|
const state = crypto2.randomBytes(16).toString("hex");
|
|
322
|
-
const { port: portPromise, waitForCode } = startLoopbackServer(
|
|
323
|
-
|
|
324
|
-
|
|
329
|
+
const { port: portPromise, waitForCode } = startLoopbackServer({
|
|
330
|
+
expectedState: state,
|
|
331
|
+
timeoutMs: opts.timeoutMs ?? 3e5
|
|
332
|
+
});
|
|
325
333
|
const port = await portPromise;
|
|
326
|
-
const redirectUri = `http://
|
|
334
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
327
335
|
const clientId = await registerClient({
|
|
328
336
|
supabaseUrl: opts.supabaseUrl,
|
|
329
337
|
clientName: "Balise CLI",
|
|
@@ -616,7 +624,7 @@ import path2 from "path";
|
|
|
616
624
|
import ini from "ini";
|
|
617
625
|
var CONFIG_DIR = ".balise";
|
|
618
626
|
var CONFIG_FILE = "config";
|
|
619
|
-
var DEFAULT_API_URL = process.env.BALISE_API_URL ?? "https://api.balise.dev";
|
|
627
|
+
var DEFAULT_API_URL = process.env.BALISE_API_URL ?? "https://api.balise.dev/app";
|
|
620
628
|
function configPath(cwd = process.cwd()) {
|
|
621
629
|
return path2.join(cwd, CONFIG_DIR, CONFIG_FILE);
|
|
622
630
|
}
|
|
@@ -671,8 +679,7 @@ async function runWhoami(opts) {
|
|
|
671
679
|
const apiUrl = cfg?.api.url ?? DEFAULT_API_URL;
|
|
672
680
|
const client = new ApiClient({
|
|
673
681
|
apiUrl,
|
|
674
|
-
supabaseUrl: opts.supabaseUrl
|
|
675
|
-
clientId: opts.clientId
|
|
682
|
+
supabaseUrl: opts.supabaseUrl
|
|
676
683
|
});
|
|
677
684
|
try {
|
|
678
685
|
const me = await client.getJson("/v1/me");
|
|
@@ -824,11 +831,12 @@ async function ensureAuthenticated(opts) {
|
|
|
824
831
|
const verifier = generateCodeVerifier();
|
|
825
832
|
const challenge = codeChallengeFor(verifier);
|
|
826
833
|
const state = crypto3.randomBytes(16).toString("hex");
|
|
827
|
-
const { port: portPromise, waitForCode } = startLoopbackServer(
|
|
828
|
-
|
|
829
|
-
|
|
834
|
+
const { port: portPromise, waitForCode } = startLoopbackServer({
|
|
835
|
+
expectedState: state,
|
|
836
|
+
timeoutMs: opts.timeoutMs ?? 3e5
|
|
837
|
+
});
|
|
830
838
|
const port = await portPromise;
|
|
831
|
-
const redirectUri = `http://
|
|
839
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
832
840
|
const clientId = await registerClient({
|
|
833
841
|
supabaseUrl: opts.supabaseUrl,
|
|
834
842
|
clientName: "Balise CLI",
|
|
@@ -949,8 +957,7 @@ async function runInit(opts) {
|
|
|
949
957
|
}
|
|
950
958
|
try {
|
|
951
959
|
await ensureAuthenticated({
|
|
952
|
-
supabaseUrl: opts.supabaseUrl
|
|
953
|
-
clientId: opts.clientId
|
|
960
|
+
supabaseUrl: opts.supabaseUrl
|
|
954
961
|
});
|
|
955
962
|
} catch (err) {
|
|
956
963
|
if (err instanceof LoginDeclinedError) {
|
|
@@ -962,8 +969,7 @@ async function runInit(opts) {
|
|
|
962
969
|
const apiUrl = DEFAULT_API_URL;
|
|
963
970
|
const client = new ApiClient({
|
|
964
971
|
apiUrl,
|
|
965
|
-
supabaseUrl: opts.supabaseUrl
|
|
966
|
-
clientId: opts.clientId
|
|
972
|
+
supabaseUrl: opts.supabaseUrl
|
|
967
973
|
});
|
|
968
974
|
let ownerships = [];
|
|
969
975
|
let repos = [];
|
|
@@ -1205,8 +1211,7 @@ async function runSyncInner(opts) {
|
|
|
1205
1211
|
}
|
|
1206
1212
|
try {
|
|
1207
1213
|
await ensureAuthenticated({
|
|
1208
|
-
supabaseUrl: opts.supabaseUrl
|
|
1209
|
-
clientId: opts.clientId
|
|
1214
|
+
supabaseUrl: opts.supabaseUrl
|
|
1210
1215
|
});
|
|
1211
1216
|
} catch (err) {
|
|
1212
1217
|
if (err instanceof LoginDeclinedError) {
|
|
@@ -1232,8 +1237,7 @@ async function runSyncInner(opts) {
|
|
|
1232
1237
|
}
|
|
1233
1238
|
const client = new ApiClient({
|
|
1234
1239
|
apiUrl: cfg.api.url,
|
|
1235
|
-
supabaseUrl: opts.supabaseUrl
|
|
1236
|
-
clientId: opts.clientId
|
|
1240
|
+
supabaseUrl: opts.supabaseUrl
|
|
1237
1241
|
});
|
|
1238
1242
|
process.stderr.write(
|
|
1239
1243
|
`Packing ${cfg.repo.owner_login}/${cfg.repo.slug} at HEAD\u2026
|
|
@@ -1281,12 +1285,11 @@ async function runSyncInner(opts) {
|
|
|
1281
1285
|
}
|
|
1282
1286
|
|
|
1283
1287
|
// src/index.ts
|
|
1284
|
-
var SUPABASE_URL = process.env.BALISE_SUPABASE_URL ?? "https://
|
|
1285
|
-
var CLIENT_ID = process.env.BALISE_CLIENT_ID ?? "balise-cli";
|
|
1288
|
+
var SUPABASE_URL = process.env.BALISE_SUPABASE_URL ?? "https://api.balise.dev";
|
|
1286
1289
|
var loginCmd = defineCommand({
|
|
1287
1290
|
meta: { name: "login", description: "Authenticate via OAuth (PKCE loopback)." },
|
|
1288
1291
|
async run() {
|
|
1289
|
-
await runLogin({ supabaseUrl: SUPABASE_URL
|
|
1292
|
+
await runLogin({ supabaseUrl: SUPABASE_URL });
|
|
1290
1293
|
}
|
|
1291
1294
|
});
|
|
1292
1295
|
var logoutCmd = defineCommand({
|
|
@@ -1298,7 +1301,7 @@ var logoutCmd = defineCommand({
|
|
|
1298
1301
|
var whoamiCmd = defineCommand({
|
|
1299
1302
|
meta: { name: "whoami", description: "Show current authenticated user." },
|
|
1300
1303
|
async run() {
|
|
1301
|
-
await runWhoami({ supabaseUrl: SUPABASE_URL
|
|
1304
|
+
await runWhoami({ supabaseUrl: SUPABASE_URL });
|
|
1302
1305
|
}
|
|
1303
1306
|
});
|
|
1304
1307
|
var initCmd = defineCommand({
|
|
@@ -1307,7 +1310,7 @@ var initCmd = defineCommand({
|
|
|
1307
1310
|
description: "Link or create a Balise repo and write .balise/config."
|
|
1308
1311
|
},
|
|
1309
1312
|
async run() {
|
|
1310
|
-
await runInit({ supabaseUrl: SUPABASE_URL
|
|
1313
|
+
await runInit({ supabaseUrl: SUPABASE_URL });
|
|
1311
1314
|
}
|
|
1312
1315
|
});
|
|
1313
1316
|
var syncCmd = defineCommand({
|
|
@@ -1316,7 +1319,7 @@ var syncCmd = defineCommand({
|
|
|
1316
1319
|
description: "Tarball current repo \u2192 upload \u2192 poll extraction progress."
|
|
1317
1320
|
},
|
|
1318
1321
|
async run() {
|
|
1319
|
-
await runSync({ supabaseUrl: SUPABASE_URL
|
|
1322
|
+
await runSync({ supabaseUrl: SUPABASE_URL });
|
|
1320
1323
|
}
|
|
1321
1324
|
});
|
|
1322
1325
|
var main = defineCommand({
|
|
@@ -1334,4 +1337,3 @@ var main = defineCommand({
|
|
|
1334
1337
|
}
|
|
1335
1338
|
});
|
|
1336
1339
|
void runMain(main);
|
|
1337
|
-
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/commands/login.ts","../src/oauth.ts","../src/credentials.ts","../src/commands/logout.ts","../src/api.ts","../src/config.ts","../src/commands/whoami.ts","../src/commands/init.ts","../src/git.ts","../src/auth-ensure.ts","../src/ui/InitPicker.tsx","../src/commands/sync.ts","../src/ui/SyncProgress.tsx"],"sourcesContent":["/**\n * @balise/cli entry point.\n * Dispatches to subcommands via citty.\n *\n * Runtime config via env vars:\n * BALISE_SUPABASE_URL (default: https://auth.balise.dev)\n * BALISE_CLIENT_ID (default: \"balise-cli\")\n * BALISE_API_URL (default: https://api.balise.dev)\n */\nimport { defineCommand, runMain } from \"citty\";\nimport { runLogin } from \"./commands/login.js\";\nimport { runLogout } from \"./commands/logout.js\";\nimport { runWhoami } from \"./commands/whoami.js\";\nimport { runInit } from \"./commands/init.js\";\nimport { runSync } from \"./commands/sync.js\";\n\nconst SUPABASE_URL =\n process.env.BALISE_SUPABASE_URL ?? \"https://auth.balise.dev\";\nconst CLIENT_ID = process.env.BALISE_CLIENT_ID ?? \"balise-cli\";\n\nconst loginCmd = defineCommand({\n meta: { name: \"login\", description: \"Authenticate via OAuth (PKCE loopback).\" },\n async run() {\n await runLogin({ supabaseUrl: SUPABASE_URL, clientId: CLIENT_ID });\n },\n});\n\nconst logoutCmd = defineCommand({\n meta: { name: \"logout\", description: \"Clear stored credentials.\" },\n async run() {\n await runLogout();\n },\n});\n\nconst whoamiCmd = defineCommand({\n meta: { name: \"whoami\", description: \"Show current authenticated user.\" },\n async run() {\n await runWhoami({ supabaseUrl: SUPABASE_URL, clientId: CLIENT_ID });\n },\n});\n\nconst initCmd = defineCommand({\n meta: {\n name: \"init\",\n description: \"Link or create a Balise repo and write .balise/config.\",\n },\n async run() {\n await runInit({ supabaseUrl: SUPABASE_URL, clientId: CLIENT_ID });\n },\n});\n\nconst syncCmd = defineCommand({\n meta: {\n name: \"sync\",\n description: \"Tarball current repo → upload → poll extraction progress.\",\n },\n async run() {\n await runSync({ supabaseUrl: SUPABASE_URL, clientId: CLIENT_ID });\n },\n});\n\nconst main = defineCommand({\n meta: {\n name: \"balise\",\n version: \"0.1.0\",\n description: \"Balise CLI — push codebase for spec extraction.\",\n },\n subCommands: {\n login: loginCmd,\n logout: logoutCmd,\n whoami: whoamiCmd,\n init: initCmd,\n sync: syncCmd,\n },\n});\n\nvoid runMain(main);\n","import open from \"open\";\nimport crypto from \"node:crypto\";\nimport {\n buildAuthorizeUrl,\n codeChallengeFor,\n exchangeCodeForTokens,\n generateCodeVerifier,\n registerClient,\n startLoopbackServer,\n} from \"../oauth.js\";\nimport {\n CredentialsError,\n credentialsHelpMessage,\n saveTokens,\n} from \"../credentials.js\";\n\nexport interface LoginOptions {\n supabaseUrl: string;\n clientId: string;\n timeoutMs?: number;\n /** Override for tests — skip browser. */\n openBrowser?: (url: string) => Promise<void>;\n}\n\nexport async function runLogin(opts: LoginOptions): Promise<void> {\n if (process.env.BALISE_TOKEN && process.env.BALISE_TOKEN.length > 0) {\n process.stderr.write(\n \"Already authenticated via BALISE_TOKEN env var. Unset it to run browser-based login.\\n\",\n );\n return;\n }\n\n const verifier = generateCodeVerifier();\n const challenge = codeChallengeFor(verifier);\n const state = crypto.randomBytes(16).toString(\"hex\");\n\n const { port: portPromise, waitForCode } = startLoopbackServer(\n opts.timeoutMs ?? 300_000,\n );\n const port = await portPromise;\n const redirectUri = `http://localhost:${port}/callback`;\n\n const clientId = await registerClient({\n supabaseUrl: opts.supabaseUrl,\n clientName: \"Balise CLI\",\n redirectUri,\n });\n\n const url = buildAuthorizeUrl({\n supabaseUrl: opts.supabaseUrl,\n clientId,\n redirectUri,\n codeChallenge: challenge,\n state,\n });\n\n process.stderr.write(`Opening browser to log in…\\n ${url}\\n`);\n const opener = opts.openBrowser ?? (async (u: string) => {\n await open(u);\n });\n try {\n await opener(url);\n } catch {\n process.stderr.write(\n \"Could not auto-open browser. Copy the URL above manually.\\n\",\n );\n }\n\n const code = await waitForCode;\n const tokens = await exchangeCodeForTokens({\n supabaseUrl: opts.supabaseUrl,\n code,\n codeVerifier: verifier,\n redirectUri,\n clientId,\n });\n\n try {\n await saveTokens({\n access_token: tokens.access_token,\n refresh_token: tokens.refresh_token,\n client_id: clientId,\n expires_at:\n Math.floor(Date.now() / 1000) + (tokens.expires_in ?? 3600),\n user_id: tokens.user?.id,\n });\n } catch (err) {\n if (err instanceof CredentialsError) {\n process.stderr.write(`\\n${err.message}\\n\\n${credentialsHelpMessage()}\\n`);\n process.exit(1);\n }\n throw err;\n }\n\n const who = tokens.user?.email ?? tokens.user?.id ?? \"you\";\n process.stdout.write(`Logged in as ${who}\\n`);\n}\n","/**\n * OAuth 2.1 Authorization Code + PKCE, loopback redirect.\n *\n * Flow:\n * 1. Generate code_verifier + code_challenge (S256).\n * 2. Start local HTTP server on an OS-assigned port.\n * 3. Open browser at ${SUPABASE_URL}/auth/v1/authorize?...&redirect_uri=http://localhost:<port>/callback\n * 4. Wait for callback → extract ?code=...\n * 5. POST /auth/v1/token?grant_type=pkce with code + verifier.\n * 6. Return {access_token, refresh_token, expires_in, user}.\n */\nimport crypto from \"node:crypto\";\nimport http from \"node:http\";\nimport { AddressInfo } from \"node:net\";\nimport { request } from \"undici\";\n\nexport interface OAuthConfig {\n supabaseUrl: string;\n clientId: string;\n /** Default 300s (5min) per cliLoginCommand spec. */\n timeoutMs?: number;\n}\n\nexport interface TokenResponse {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n token_type: string;\n user?: { id: string; email?: string };\n}\n\n/** OAuth PKCE verifier: 43-128 chars, base64url of 32-96 random bytes. */\nexport function generateCodeVerifier(): string {\n return base64url(crypto.randomBytes(64));\n}\n\nexport function codeChallengeFor(verifier: string): string {\n return base64url(crypto.createHash(\"sha256\").update(verifier).digest());\n}\n\nexport function base64url(buf: Buffer): string {\n return buf\n .toString(\"base64\")\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport const SUCCESS_HTML = `<!doctype html>\n<html><head><title>Balise — Logged in</title>\n<style>body{font-family:system-ui,sans-serif;padding:4rem;text-align:center;background:#0b0b0b;color:#eee}\nh1{font-size:2rem;margin:0 0 1rem}p{opacity:.7}</style></head>\n<body><h1>✓ Balise CLI logged in</h1>\n<p>You can close this tab and return to your terminal.</p>\n<script>setTimeout(()=>window.close(),1200)</script></body></html>`;\n\nexport const FAILURE_HTML = `<!doctype html>\n<html><head><title>Balise — Login failed</title></head>\n<body style=\"font-family:system-ui;padding:4rem;text-align:center\">\n<h1>Login failed</h1><p>See your terminal for details.</p></body></html>`;\n\n/**\n * Start a loopback server; return {port, waitForCode, close}.\n * waitForCode resolves with the ?code=... query param on /callback,\n * rejects on timeout or ?error=... response.\n */\nexport function startLoopbackServer(timeoutMs = 300_000): {\n port: Promise<number>;\n waitForCode: Promise<string>;\n close: () => void;\n} {\n let resolveCode: (code: string) => void;\n let rejectCode: (err: Error) => void;\n const waitForCode = new Promise<string>((res, rej) => {\n resolveCode = res;\n rejectCode = rej;\n });\n\n const server = http.createServer((req, res) => {\n if (!req.url) return;\n const url = new URL(req.url, \"http://localhost\");\n if (url.pathname !== \"/callback\") {\n res.writeHead(404).end(\"Not Found\");\n return;\n }\n const code = url.searchParams.get(\"code\");\n const error = url.searchParams.get(\"error\");\n if (error) {\n res.writeHead(400, { \"Content-Type\": \"text/html\" }).end(FAILURE_HTML);\n rejectCode(new Error(`OAuth error: ${error}`));\n return;\n }\n if (!code) {\n res.writeHead(400, { \"Content-Type\": \"text/html\" }).end(FAILURE_HTML);\n rejectCode(new Error(\"OAuth callback missing `code`\"));\n return;\n }\n res.writeHead(200, { \"Content-Type\": \"text/html\" }).end(SUCCESS_HTML);\n resolveCode(code);\n });\n\n const portPromise = new Promise<number>((res, rej) => {\n server.once(\"error\", rej);\n server.listen(0, \"127.0.0.1\", () => {\n const { port } = server.address() as AddressInfo;\n res(port);\n });\n });\n\n const timer = setTimeout(() => {\n rejectCode(new Error(`OAuth login timed out after ${timeoutMs / 1000}s`));\n server.close();\n }, timeoutMs);\n\n return {\n port: portPromise,\n waitForCode: waitForCode.finally(() => {\n clearTimeout(timer);\n server.close();\n }),\n close: () => {\n clearTimeout(timer);\n server.close();\n },\n };\n}\n\nexport function buildAuthorizeUrl(params: {\n supabaseUrl: string;\n clientId: string;\n redirectUri: string;\n codeChallenge: string;\n state: string;\n}): string {\n const u = new URL(\n `${params.supabaseUrl.replace(/\\/$/, \"\")}/auth/v1/oauth/authorize`,\n );\n u.searchParams.set(\"response_type\", \"code\");\n u.searchParams.set(\"client_id\", params.clientId);\n u.searchParams.set(\"redirect_uri\", params.redirectUri);\n u.searchParams.set(\"code_challenge\", params.codeChallenge);\n u.searchParams.set(\"code_challenge_method\", \"S256\");\n u.searchParams.set(\"state\", params.state);\n return u.toString();\n}\n\n/**\n * RFC 7591 dynamic client registration against GoTrue.\n *\n * GoTrue enforces strict redirect_uri matching (no loopback port wildcard per\n * RFC 8252 §7.3), so we register one client per login with the exact loopback\n * port of that session. The resulting UUID `client_id` is stored alongside the\n * tokens and reused for refresh.\n */\nexport async function registerClient(params: {\n supabaseUrl: string;\n clientName: string;\n redirectUri: string;\n}): Promise<string> {\n const url = `${params.supabaseUrl.replace(/\\/$/, \"\")}/auth/v1/oauth/clients/register`;\n const body = JSON.stringify({\n client_name: params.clientName,\n redirect_uris: [params.redirectUri],\n grant_types: [\"authorization_code\", \"refresh_token\"],\n response_types: [\"code\"],\n token_endpoint_auth_method: \"none\",\n application_type: \"native\",\n });\n const { statusCode, body: respBody } = await request(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n });\n const text = await respBody.text();\n if (statusCode < 200 || statusCode >= 300) {\n throw new Error(\n `Client registration failed (${statusCode}): ${text.slice(0, 200)}`,\n );\n }\n const data = JSON.parse(text) as { client_id: string };\n if (!data.client_id) {\n throw new Error(\n `Client registration response missing client_id: ${text.slice(0, 200)}`,\n );\n }\n return data.client_id;\n}\n\nexport async function exchangeCodeForTokens(params: {\n supabaseUrl: string;\n code: string;\n codeVerifier: string;\n redirectUri: string;\n clientId: string;\n}): Promise<TokenResponse> {\n const url = `${params.supabaseUrl.replace(/\\/$/, \"\")}/auth/v1/oauth/token`;\n const body = new URLSearchParams({\n grant_type: \"authorization_code\",\n code: params.code,\n code_verifier: params.codeVerifier,\n redirect_uri: params.redirectUri,\n client_id: params.clientId,\n }).toString();\n const { statusCode, body: respBody } = await request(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n const text = await respBody.text();\n if (statusCode < 200 || statusCode >= 300) {\n throw new Error(\n `Token exchange failed (${statusCode}): ${text.slice(0, 200)}`,\n );\n }\n return JSON.parse(text) as TokenResponse;\n}\n\nexport async function refreshAccessToken(params: {\n supabaseUrl: string;\n refreshToken: string;\n clientId: string;\n}): Promise<TokenResponse> {\n const url = `${params.supabaseUrl.replace(/\\/$/, \"\")}/auth/v1/oauth/token`;\n const reqBody = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: params.refreshToken,\n client_id: params.clientId,\n }).toString();\n const { statusCode, body } = await request(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: reqBody,\n });\n const text = await body.text();\n if (statusCode < 200 || statusCode >= 300) {\n throw new Error(\n `Token refresh failed (${statusCode}): ${text.slice(0, 200)}`,\n );\n }\n return JSON.parse(text) as TokenResponse;\n}\n","/**\n * Plaintext credentials file storage for the Balise CLI.\n *\n * File location (precedence, read + write):\n * BALISE_CREDENTIALS_FILE > $XDG_CONFIG_HOME/balise/credentials.json > ~/.config/balise/credentials.json\n *\n * Read-time precedence:\n * 1. process.env.BALISE_TOKEN — stateless override; no file is read.\n * 2. File at the resolved path.\n *\n * File is stored as UTF-8 JSON, mode 0o600. Parent directory is created with mode 0o700.\n * Windows file writes are not supported in V1 — BALISE_TOKEN env var works across all platforms.\n */\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\nexport const APP_DIR = \"balise\";\nexport const FILENAME = \"credentials.json\";\n\nexport interface StoredTokens {\n access_token: string;\n refresh_token: string;\n /** OAuth 2.1 client_id (UUID) from dynamic registration. Needed for token refresh. */\n client_id?: string;\n expires_at?: number;\n user_id?: string;\n}\n\nexport class CredentialsError extends Error {\n constructor(\n message: string,\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"CredentialsError\";\n }\n}\n\n/** Resolve the absolute path to the credentials file. */\nexport function credentialsPath(): string {\n const fileOverride = process.env.BALISE_CREDENTIALS_FILE;\n if (fileOverride && fileOverride.length > 0) return fileOverride;\n const xdg = process.env.XDG_CONFIG_HOME;\n const base =\n xdg && xdg.length > 0 ? xdg : path.join(os.homedir(), \".config\");\n return path.join(base, APP_DIR, FILENAME);\n}\n\nfunction hasEnvToken(): boolean {\n const v = process.env.BALISE_TOKEN;\n return typeof v === \"string\" && v.length > 0;\n}\n\nfunction isValidTokens(v: unknown): v is StoredTokens {\n if (!v || typeof v !== \"object\") return false;\n const t = v as Record<string, unknown>;\n return (\n typeof t.access_token === \"string\" &&\n typeof t.refresh_token === \"string\"\n );\n}\n\nexport async function loadTokens(): Promise<StoredTokens | null> {\n if (hasEnvToken()) {\n return {\n access_token: process.env.BALISE_TOKEN as string,\n refresh_token: \"\",\n };\n }\n\n const p = credentialsPath();\n let raw: string;\n try {\n raw = await fs.readFile(p, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException)?.code === \"ENOENT\") return null;\n throw new CredentialsError(\n `Cannot read credentials file ${p}: ${(err as Error).message}`,\n err,\n );\n }\n\n if (process.platform !== \"win32\") {\n try {\n const st = await fs.stat(p);\n if ((st.mode & 0o077) !== 0) {\n process.stderr.write(\n `Warning: credentials file ${p} has loose permissions; will re-tighten on next write.\\n`,\n );\n }\n } catch {\n // best-effort; ignore\n }\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n process.stderr.write(\n `Warning: credentials file ${p} is corrupt, please re-login.\\n`,\n );\n return null;\n }\n if (!isValidTokens(parsed)) {\n process.stderr.write(\n `Warning: credentials file ${p} is missing required keys, please re-login.\\n`,\n );\n return null;\n }\n return parsed;\n}\n\nexport async function saveTokens(t: StoredTokens): Promise<void> {\n if (hasEnvToken()) {\n process.stderr.write(\n \"Warning: env var BALISE_TOKEN active, skipping credentials file write.\\n\",\n );\n return;\n }\n\n if (process.platform === \"win32\") {\n throw new CredentialsError(\n \"Windows credential file storage not yet supported. Use BALISE_TOKEN env var instead.\",\n );\n }\n\n const p = credentialsPath();\n const dir = path.dirname(p);\n try {\n await fs.mkdir(dir, { recursive: true, mode: 0o700 });\n } catch (err) {\n throw new CredentialsError(\n `Cannot create credentials directory ${dir}: ${(err as Error).message}. Set BALISE_TOKEN env var instead.`,\n err,\n );\n }\n try {\n await fs.writeFile(p, JSON.stringify(t), { mode: 0o600 });\n // writeFile's mode applies only on creation — re-enforce for existing files.\n await fs.chmod(p, 0o600);\n } catch (err) {\n throw new CredentialsError(\n `Cannot write credentials file ${p}: ${(err as Error).message}. Set BALISE_TOKEN env var instead.`,\n err,\n );\n }\n}\n\nexport async function clearTokens(): Promise<boolean> {\n const p = credentialsPath();\n let existed = true;\n try {\n await fs.unlink(p);\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n if (e?.code === \"ENOENT\") {\n existed = false;\n } else {\n throw new CredentialsError(\n `Cannot remove credentials file ${p}: ${e.message ?? String(err)}`,\n err,\n );\n }\n }\n if (hasEnvToken()) {\n process.stderr.write(\n \"Warning: BALISE_TOKEN env var still set — unset it manually to fully log out.\\n\",\n );\n }\n return existed;\n}\n\nexport function credentialsHelpMessage(): string {\n const p = credentialsPath();\n return (\n `Credentials are stored at ${p} (mode 0600).\\n` +\n ` • Set BALISE_TOKEN=<jwt> to supply a token directly (useful in CI).\\n` +\n ` • Set BALISE_CREDENTIALS_FILE=<path> to override the storage location.`\n );\n}\n","import {\n clearTokens,\n CredentialsError,\n credentialsHelpMessage,\n} from \"../credentials.js\";\n\nexport async function runLogout(): Promise<void> {\n try {\n const existed = await clearTokens();\n if (existed) {\n process.stdout.write(\"Logged out — credentials file removed.\\n\");\n } else {\n process.stdout.write(\"Already logged out (no credentials file found).\\n\");\n }\n } catch (err) {\n if (err instanceof CredentialsError) {\n process.stderr.write(`\\n${err.message}\\n\\n${credentialsHelpMessage()}\\n`);\n process.exit(1);\n }\n throw err;\n }\n}\n","/**\n * HTTP client against the Balise FastAPI backend.\n *\n * Responsibilities:\n * - Auto-attach Bearer from the credentials store.\n * - On 401: try refresh_token once; if refresh fails → throw NotAuthenticatedError\n * (caller should prompt `balise login`).\n */\nimport { Readable } from \"node:stream\";\nimport { request, Dispatcher } from \"undici\";\nimport { loadTokens, saveTokens, StoredTokens } from \"./credentials.js\";\nimport { refreshAccessToken } from \"./oauth.js\";\n\nexport class NotAuthenticatedError extends Error {\n constructor(msg = \"Not authenticated — run `balise login`\") {\n super(msg);\n this.name = \"NotAuthenticatedError\";\n }\n}\n\nexport class ApiError extends Error {\n constructor(\n public readonly status: number,\n public readonly body: string,\n message?: string,\n ) {\n super(message ?? `API ${status}: ${body.slice(0, 200)}`);\n this.name = \"ApiError\";\n }\n}\n\nexport class ApiUnreachableError extends Error {\n constructor(public readonly cause: unknown) {\n super(\"Cannot reach the Balise API\");\n this.name = \"ApiUnreachableError\";\n }\n}\n\nconst UNREACHABLE_CODES = new Set([\n \"ECONNREFUSED\",\n \"ENOTFOUND\",\n \"ECONNRESET\",\n \"ETIMEDOUT\",\n \"EAI_AGAIN\",\n \"UND_ERR_CONNECT_TIMEOUT\",\n \"UND_ERR_SOCKET\",\n]);\n\nfunction isUnreachable(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n const e = err as { code?: string; cause?: { code?: string }; errors?: unknown[] };\n if (e.code && UNREACHABLE_CODES.has(e.code)) return true;\n if (e.cause?.code && UNREACHABLE_CODES.has(e.cause.code)) return true;\n // undici wraps multi-family lookup failures (IPv4/IPv6) in AggregateError.\n if (Array.isArray(e.errors)) {\n return e.errors.some((inner) => isUnreachable(inner));\n }\n return false;\n}\n\nfunction wrapNetwork<T>(fn: () => Promise<T>): Promise<T> {\n return fn().catch((err) => {\n if (isUnreachable(err)) throw new ApiUnreachableError(err);\n throw err;\n });\n}\n\nexport interface ApiClientOptions {\n apiUrl: string;\n supabaseUrl: string;\n clientId: string;\n /** Injection seam for tests. */\n dispatcher?: Dispatcher;\n}\n\nexport class ApiClient {\n constructor(private readonly opts: ApiClientOptions) {}\n\n private async authHeader(): Promise<Record<string, string>> {\n const tokens = await loadTokens();\n if (!tokens) throw new NotAuthenticatedError();\n return { Authorization: `Bearer ${tokens.access_token}` };\n }\n\n /**\n * Proactively refresh the access token if it expires within `graceSec`.\n *\n * Needed before non-replayable requests (e.g. uploadBundle, whose body is\n * a `git archive` subprocess stdout that cannot be re-consumed). Without\n * this the withAuth retry-on-401 path would kick in but the retry would\n * send a drained stream (0 bytes).\n *\n * No-op when BALISE_TOKEN env var is in effect (stateless CI mode —\n * caller is responsible for providing a fresh token).\n */\n async ensureFreshToken(graceSec = 60): Promise<void> {\n if (process.env.BALISE_TOKEN && process.env.BALISE_TOKEN.length > 0) return;\n const tokens = await loadTokens();\n if (!tokens) throw new NotAuthenticatedError();\n if (!tokens.expires_at || !tokens.client_id) return; // unknown lifetime or legacy blob — rely on 401 retry path\n const secondsLeft = tokens.expires_at - Math.floor(Date.now() / 1000);\n if (secondsLeft > graceSec) return;\n try {\n const r = await refreshAccessToken({\n supabaseUrl: this.opts.supabaseUrl,\n refreshToken: tokens.refresh_token,\n clientId: tokens.client_id,\n });\n await saveTokens({\n access_token: r.access_token,\n refresh_token: r.refresh_token,\n client_id: tokens.client_id,\n expires_at: Math.floor(Date.now() / 1000) + (r.expires_in ?? 3600),\n user_id: r.user?.id ?? tokens.user_id,\n });\n } catch {\n throw new NotAuthenticatedError();\n }\n }\n\n /**\n * Execute a request, retrying exactly once after a 401 via refresh_token.\n */\n private async withAuth<T>(\n fn: (headers: Record<string, string>) => Promise<{\n status: number;\n text: string;\n json: () => T;\n }>,\n ): Promise<T> {\n let tokens = await loadTokens();\n if (!tokens) throw new NotAuthenticatedError();\n\n let res = await fn({ Authorization: `Bearer ${tokens.access_token}` });\n if (res.status !== 401) {\n if (res.status < 200 || res.status >= 300) {\n throw new ApiError(res.status, res.text);\n }\n return res.json();\n }\n\n // 401 → refresh once. Refresh requires the same client_id used at login.\n if (!tokens.client_id) {\n // BALISE_TOKEN mode or legacy credentials without client_id — cannot refresh.\n throw new NotAuthenticatedError();\n }\n let refreshed: StoredTokens;\n try {\n const r = await refreshAccessToken({\n supabaseUrl: this.opts.supabaseUrl,\n refreshToken: tokens.refresh_token,\n clientId: tokens.client_id,\n });\n refreshed = {\n access_token: r.access_token,\n refresh_token: r.refresh_token,\n client_id: tokens.client_id,\n expires_at: Math.floor(Date.now() / 1000) + (r.expires_in ?? 3600),\n user_id: r.user?.id ?? tokens.user_id,\n };\n await saveTokens(refreshed);\n } catch {\n throw new NotAuthenticatedError();\n }\n\n res = await fn({ Authorization: `Bearer ${refreshed.access_token}` });\n if (res.status < 200 || res.status >= 300) {\n if (res.status === 401) throw new NotAuthenticatedError();\n throw new ApiError(res.status, res.text);\n }\n return res.json();\n }\n\n async getJson<T>(path: string): Promise<T> {\n const url = joinUrl(this.opts.apiUrl, path);\n return this.withAuth(async (headers) => {\n const { statusCode, body } = await wrapNetwork(() => request(url, {\n method: \"GET\",\n headers,\n dispatcher: this.opts.dispatcher,\n }));\n const text = await body.text();\n return {\n status: statusCode,\n text,\n json: () => JSON.parse(text) as T,\n };\n });\n }\n\n async postJson<T>(path: string, payload: unknown): Promise<T> {\n const url = joinUrl(this.opts.apiUrl, path);\n return this.withAuth(async (headers) => {\n const { statusCode, body } = await wrapNetwork(() => request(url, {\n method: \"POST\",\n headers: {\n ...headers,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n dispatcher: this.opts.dispatcher,\n }));\n const text = await body.text();\n return {\n status: statusCode,\n text,\n json: () => JSON.parse(text) as T,\n };\n });\n }\n\n /**\n * Raw-body bundle upload — streams a git bundle directly as the request body,\n * metadata travels in the query string. Matches the FastAPI contract\n * `POST /v1/repos/:owner/:slug/sync?commit_sha=...&branch=...`.\n */\n async uploadBundle<T>(\n path: string,\n opts: {\n bundleStream: Readable;\n query: Record<string, string>;\n },\n ): Promise<T> {\n // The bundle body is a single-shot read stream. Proactively refresh if\n // the access token is about to expire, so we never hit the 401-then-retry\n // path that would replay a drained stream.\n await this.ensureFreshToken();\n\n const qs = new URLSearchParams(opts.query).toString();\n const url = `${joinUrl(this.opts.apiUrl, path)}${qs ? `?${qs}` : \"\"}`;\n\n return this.withAuth(async (headers) => {\n const { statusCode, body } = await wrapNetwork(() => request(url, {\n method: \"POST\",\n headers: {\n ...headers,\n \"Content-Type\": \"application/octet-stream\",\n // Chunked encoding is applied automatically by undici for streams\n // when no Content-Length is set; setting it manually is rejected.\n },\n body: opts.bundleStream,\n dispatcher: this.opts.dispatcher,\n }));\n const text = await body.text();\n return {\n status: statusCode,\n text,\n json: () => JSON.parse(text) as T,\n };\n });\n }\n}\n\nexport function joinUrl(base: string, path: string): string {\n return `${base.replace(/\\/$/, \"\")}${path.startsWith(\"/\") ? path : `/${path}`}`;\n}\n\n// ---------- Response shapes ----------\n\nexport interface Me {\n login: string;\n type: \"user\" | \"org\";\n account_id: string;\n email?: string;\n}\n\nexport interface RepoRef {\n id: string;\n slug: string;\n owner_login: string;\n owner_id: string;\n}\n\nexport interface Ownership {\n id: string;\n login: string;\n type: \"user\" | \"org\";\n}\n\nexport interface SyncAccepted {\n sync_id: string;\n status_url: string;\n}\n\nexport interface SyncStatus {\n sync_id: string;\n status: \"queued\" | \"running\" | \"done\" | \"failed\";\n phase: \"queued\" | \"extracting\" | \"done\" | \"failed\";\n progress: {\n files_processed: number;\n files_total: number;\n nodes_pushed: number;\n };\n dolt_commit?: string;\n started_at?: string;\n finished_at?: string;\n}\n","/**\n * `.balise/config` (INI) read / write.\n *\n * Format:\n * [repo]\n * id = <uuid>\n * slug = <slug>\n * owner_login = <login>\n *\n * [api]\n * url = https://api.balise.dev\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport ini from \"ini\";\n\nexport interface BaliseConfig {\n repo: {\n id: string;\n slug: string;\n owner_login: string;\n };\n api: {\n url: string;\n };\n}\n\nexport const CONFIG_DIR = \".balise\";\nexport const CONFIG_FILE = \"config\";\nexport const DEFAULT_API_URL =\n process.env.BALISE_API_URL ?? \"https://api.balise.dev\";\n\nexport function configPath(cwd: string = process.cwd()): string {\n return path.join(cwd, CONFIG_DIR, CONFIG_FILE);\n}\n\nexport async function readConfig(\n cwd: string = process.cwd(),\n): Promise<BaliseConfig | null> {\n const p = configPath(cwd);\n let raw: string;\n try {\n raw = await fs.readFile(p, \"utf8\");\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n const parsed = ini.parse(raw) as Partial<BaliseConfig>;\n if (!parsed.repo?.id || !parsed.repo.slug || !parsed.repo.owner_login) {\n throw new Error(`Invalid .balise/config at ${p}: missing [repo] fields`);\n }\n return {\n repo: {\n id: String(parsed.repo.id),\n slug: String(parsed.repo.slug),\n owner_login: String(parsed.repo.owner_login),\n },\n api: {\n url: String(parsed.api?.url ?? DEFAULT_API_URL),\n },\n };\n}\n\nexport async function writeConfig(\n cfg: BaliseConfig,\n cwd: string = process.cwd(),\n): Promise<void> {\n const dir = path.join(cwd, CONFIG_DIR);\n await fs.mkdir(dir, { recursive: true });\n const serialized = ini.stringify(cfg as unknown as Record<string, unknown>);\n await fs.writeFile(path.join(dir, CONFIG_FILE), serialized, \"utf8\");\n}\n\n/**\n * Append `.balise/` to the repo-local .gitignore if not already present.\n * Idempotent. Creates .gitignore if missing.\n */\nexport async function ensureGitignored(\n cwd: string = process.cwd(),\n): Promise<void> {\n const gi = path.join(cwd, \".gitignore\");\n let current = \"\";\n try {\n current = await fs.readFile(gi, \"utf8\");\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n const lines = current.split(/\\r?\\n/).map((l) => l.trim());\n if (lines.some((l) => l === \".balise\" || l === \".balise/\")) return;\n const suffix = current.length && !current.endsWith(\"\\n\") ? \"\\n\" : \"\";\n await fs.writeFile(gi, `${current}${suffix}.balise/\\n`, \"utf8\");\n}\n","import { ApiClient, Me, NotAuthenticatedError } from \"../api.js\";\nimport { DEFAULT_API_URL, readConfig } from \"../config.js\";\n\nexport interface WhoamiOptions {\n supabaseUrl: string;\n clientId: string;\n}\n\nexport async function runWhoami(opts: WhoamiOptions): Promise<void> {\n const cfg = await readConfig().catch(() => null);\n const apiUrl = cfg?.api.url ?? DEFAULT_API_URL;\n const client = new ApiClient({\n apiUrl,\n supabaseUrl: opts.supabaseUrl,\n clientId: opts.clientId,\n });\n try {\n const me = await client.getJson<Me>(\"/v1/me\");\n process.stdout.write(\n `login : ${me.login}\\n` +\n `type : ${me.type}\\n` +\n `account_id : ${me.account_id}\\n` +\n (me.email ? `email : ${me.email}\\n` : \"\"),\n );\n } catch (err) {\n if (err instanceof NotAuthenticatedError) {\n process.stderr.write(\"Not logged in — run `balise login`.\\n\");\n process.exit(1);\n }\n throw err;\n }\n}\n","import path from \"node:path\";\nimport React from \"react\";\nimport { render } from \"ink\";\nimport {\n ApiClient,\n Me,\n NotAuthenticatedError,\n Ownership,\n RepoRef,\n} from \"../api.js\";\n\ninterface OrgMembership {\n id: string;\n login: string;\n role: string;\n}\nimport { BaliseConfig, DEFAULT_API_URL, ensureGitignored, writeConfig } from \"../config.js\";\nimport { assertGitRepo, NotAGitRepoError } from \"../git.js\";\nimport { ensureAuthenticated, LoginDeclinedError } from \"../auth-ensure.js\";\nimport { InitPicker, PickerResult } from \"../ui/InitPicker.js\";\n\nexport interface InitOptions {\n supabaseUrl: string;\n clientId: string;\n cwd?: string;\n}\n\nexport async function runInit(opts: InitOptions): Promise<void> {\n const cwd = opts.cwd ?? process.cwd();\n\n // Must be inside a git repo (spec: `not a git repo` exit).\n try {\n await assertGitRepo({ cwd });\n } catch (err) {\n if (err instanceof NotAGitRepoError) {\n process.stderr.write(\"not a git repo\\n\");\n process.exit(1);\n }\n throw err;\n }\n\n // Inline auto-login prompt if no credentials are available.\n try {\n await ensureAuthenticated({\n supabaseUrl: opts.supabaseUrl,\n clientId: opts.clientId,\n });\n } catch (err) {\n if (err instanceof LoginDeclinedError) {\n process.stderr.write(\"Login required. Aborting.\\n\");\n process.exit(1);\n }\n throw err;\n }\n\n const apiUrl = DEFAULT_API_URL;\n const client = new ApiClient({\n apiUrl,\n supabaseUrl: opts.supabaseUrl,\n clientId: opts.clientId,\n });\n\n // The API does not expose /v1/me/ownerships — compose user + org memberships client-side.\n let ownerships: Ownership[] = [];\n let repos: RepoRef[] = [];\n try {\n const [me, orgs, repoList] = await Promise.all([\n client.getJson<Me>(\"/v1/me\"),\n client.getJson<OrgMembership[]>(\"/v1/me/orgs\"),\n client.getJson<RepoRef[]>(\"/v1/me/repos\"),\n ]);\n ownerships = [\n { id: me.account_id, login: me.login, type: \"user\" },\n ...orgs.map((o) => ({ id: o.id, login: o.login, type: \"org\" as const })),\n ];\n repos = repoList;\n } catch (err) {\n if (err instanceof NotAuthenticatedError) {\n process.stderr.write(\"Not logged in — run `balise login` first.\\n\");\n process.exit(1);\n }\n throw err;\n }\n\n // Alphabetical sort by \"<owner>/<slug>\" — cliSyncInitPicker.sortOrder spec.\n const sortedRepos = [...repos].sort((a, b) =>\n `${a.owner_login}/${a.slug}`.localeCompare(`${b.owner_login}/${b.slug}`),\n );\n const defaultSlug = path.basename(cwd).toLowerCase();\n const result = await new Promise<PickerResult>((resolve) => {\n const app = render(\n React.createElement(InitPicker, {\n defaultSlug,\n ownerships,\n repos: sortedRepos,\n onDone: (r) => {\n resolve(r);\n app.unmount();\n },\n }),\n );\n });\n\n if (result.action === \"cancel\") {\n process.stderr.write(\"Cancelled.\\n\");\n process.exit(1);\n }\n\n let cfg: BaliseConfig;\n if (result.action === \"create\") {\n const created = await client.postJson<RepoRef>(\"/v1/repos\", {\n owner_id: result.owner.id,\n slug: result.slug,\n });\n cfg = {\n repo: {\n id: created.id,\n slug: created.slug,\n owner_login: created.owner_login,\n },\n api: { url: apiUrl },\n };\n } else {\n cfg = {\n repo: {\n id: result.repo.id,\n slug: result.repo.slug,\n owner_login: result.repo.owner_login,\n },\n api: { url: apiUrl },\n };\n }\n\n await writeConfig(cfg, cwd);\n await ensureGitignored(cwd);\n process.stdout.write(\n `Linked ${cfg.repo.owner_login}/${cfg.repo.slug} → .balise/config\\n`,\n );\n}\n","/**\n * Git subprocess helpers for `balise sync` / `balise init`.\n *\n * - `isGitRepo` : fails fast if cwd is not inside a worktree.\n * - `gitBundle` : runs `git bundle create <tmp> --all` and returns a Readable\n * of the bundle bytes, alongside commit sha, current branch and dirty-tree\n * flag.\n *\n * Bundle (vs. archive) is used because the server-side extraction pipeline\n * needs a real git DB to run `git worktree add --detach <sha>` and, later, to\n * compute diffs between two commits — a flat tarball of HEAD doesn't carry\n * the objects/refs required for that.\n */\nimport { spawn } from \"node:child_process\";\nimport { Readable } from \"node:stream\";\nimport { createReadStream } from \"node:fs\";\nimport { unlink } from \"node:fs/promises\";\nimport { randomBytes } from \"node:crypto\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport class GitError extends Error {\n constructor(\n public readonly args: string[],\n public readonly code: number | null,\n public readonly stderr: string,\n ) {\n super(`git ${args.join(\" \")} failed (exit ${code}): ${stderr.trim()}`);\n this.name = \"GitError\";\n }\n}\n\nexport class NotAGitRepoError extends Error {\n constructor(cwd: string) {\n super(`not a git repo: ${cwd}`);\n this.name = \"NotAGitRepoError\";\n }\n}\n\nexport interface GitRunOptions {\n cwd: string;\n /** Injection seam for tests. Defaults to real `child_process.spawn`. */\n spawner?: typeof spawn;\n}\n\nasync function runGit(\n opts: GitRunOptions,\n args: string[],\n): Promise<string> {\n const sp = opts.spawner ?? spawn;\n return new Promise<string>((resolve, reject) => {\n const proc = sp(\"git\", args, { cwd: opts.cwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n let stdout = \"\";\n let stderr = \"\";\n proc.stdout?.on(\"data\", (d: Buffer) => {\n stdout += d.toString();\n });\n proc.stderr?.on(\"data\", (d: Buffer) => {\n stderr += d.toString();\n });\n proc.on(\"error\", reject);\n proc.on(\"close\", (code: number | null) => {\n if (code === 0) resolve(stdout);\n else reject(new GitError(args, code, stderr));\n });\n });\n}\n\nexport async function isGitRepo(opts: GitRunOptions): Promise<boolean> {\n try {\n await runGit(opts, [\"rev-parse\", \"--is-inside-work-tree\"]);\n return true;\n } catch (err) {\n if (err instanceof GitError) return false;\n throw err;\n }\n}\n\nexport async function assertGitRepo(opts: GitRunOptions): Promise<void> {\n if (!(await isGitRepo(opts))) {\n throw new NotAGitRepoError(opts.cwd);\n }\n}\n\nexport async function getCommitSha(\n opts: GitRunOptions,\n ref = \"HEAD\",\n): Promise<string> {\n return (await runGit(opts, [\"rev-parse\", ref])).trim();\n}\n\nexport async function getCurrentBranch(opts: GitRunOptions): Promise<string> {\n return (await runGit(opts, [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"])).trim();\n}\n\nexport async function isDirty(opts: GitRunOptions): Promise<boolean> {\n const status = await runGit(opts, [\"status\", \"--porcelain\"]);\n return status.trim().length > 0;\n}\n\nexport interface GitBundleResult {\n stream: Readable;\n commitSha: string;\n branch: string;\n dirty: boolean;\n}\n\n/**\n * Pack the full repo (all refs) into a git bundle on a tmp file and return a\n * Readable of its bytes plus metadata. Caller is responsible for consuming\n * the stream; the tmp file is unlinked when the stream closes.\n *\n * `git bundle create` does not support stdout on all platforms — we write to\n * a tmp file then stream it back.\n */\nexport async function gitBundle(\n opts: GitRunOptions,\n): Promise<GitBundleResult> {\n await assertGitRepo(opts);\n const [commitSha, branch, dirty] = await Promise.all([\n getCommitSha(opts),\n getCurrentBranch(opts),\n isDirty(opts),\n ]);\n\n const tmpFile = join(\n tmpdir(),\n `balise-bundle-${randomBytes(8).toString(\"hex\")}.bundle`,\n );\n\n await runGit(opts, [\"bundle\", \"create\", tmpFile, \"--all\"]);\n\n const stream = createReadStream(tmpFile);\n const cleanup = () => {\n unlink(tmpFile).catch(() => {});\n };\n stream.on(\"close\", cleanup);\n stream.on(\"error\", cleanup);\n\n return { stream, commitSha, branch, dirty };\n}\n","/**\n * Shared \"ensure authenticated\" helper for sync/init commands.\n *\n * If a token is present (env var or credentials file) → return immediately.\n * Otherwise prompt `Not logged in. Login now? (Y/n)`:\n * - `y` / `yes` / empty → run the OAuth PKCE flow inline, then return.\n * - anything else → throw `LoginDeclinedError` (caller should exit 1).\n *\n * Non-interactive (stdin not a TTY) → don't prompt, throw immediately so scripts\n * don't hang forever.\n */\nimport readline from \"node:readline/promises\";\nimport crypto from \"node:crypto\";\nimport open from \"open\";\nimport {\n loadTokens,\n saveTokens,\n CredentialsError,\n credentialsHelpMessage,\n} from \"./credentials.js\";\nimport {\n buildAuthorizeUrl,\n codeChallengeFor,\n exchangeCodeForTokens,\n generateCodeVerifier,\n registerClient,\n startLoopbackServer,\n} from \"./oauth.js\";\n\nexport class LoginDeclinedError extends Error {\n constructor() {\n super(\"login declined by user\");\n this.name = \"LoginDeclinedError\";\n }\n}\n\nexport interface EnsureAuthOptions {\n supabaseUrl: string;\n clientId: string;\n /** Defaults to `process.stdout` / `process.stdin`. */\n stdin?: NodeJS.ReadableStream;\n stdout?: NodeJS.WritableStream;\n stderr?: NodeJS.WritableStream;\n /** Injected for tests. */\n openBrowser?: (url: string) => Promise<void>;\n /** Default 300_000 ms (5 min) — matches cliLoginCommand.timeoutSec. */\n timeoutMs?: number;\n}\n\n/** Resolve true if stdin is a TTY capable of a Y/n prompt. */\nfunction isInteractive(stdin: NodeJS.ReadableStream | undefined): boolean {\n const s = (stdin ?? process.stdin) as NodeJS.ReadableStream & { isTTY?: boolean };\n return Boolean(s.isTTY);\n}\n\n/**\n * Ensures an access token is available (via env var or credentials file).\n * On first use prompts the user to log in inline, then runs the full\n * OAuth PKCE loopback flow and persists the tokens.\n */\nexport async function ensureAuthenticated(opts: EnsureAuthOptions): Promise<void> {\n const existing = await loadTokens();\n if (existing) return;\n\n const stderr = opts.stderr ?? process.stderr;\n\n if (!isInteractive(opts.stdin)) {\n throw new LoginDeclinedError();\n }\n\n const rl = readline.createInterface({\n input: opts.stdin ?? process.stdin,\n output: opts.stdout ?? process.stdout,\n });\n let answer: string;\n try {\n answer = (await rl.question(\"Not logged in. Login now? (Y/n) \")).trim();\n } finally {\n rl.close();\n }\n if (answer && !/^y(es)?$/i.test(answer)) {\n throw new LoginDeclinedError();\n }\n\n // Run OAuth PKCE loopback — same code path as `balise login`.\n const verifier = generateCodeVerifier();\n const challenge = codeChallengeFor(verifier);\n const state = crypto.randomBytes(16).toString(\"hex\");\n const { port: portPromise, waitForCode } = startLoopbackServer(\n opts.timeoutMs ?? 300_000,\n );\n const port = await portPromise;\n const redirectUri = `http://localhost:${port}/callback`;\n const clientId = await registerClient({\n supabaseUrl: opts.supabaseUrl,\n clientName: \"Balise CLI\",\n redirectUri,\n });\n const url = buildAuthorizeUrl({\n supabaseUrl: opts.supabaseUrl,\n clientId,\n redirectUri,\n codeChallenge: challenge,\n state,\n });\n\n stderr.write(`Opening browser to log in…\\n ${url}\\n`);\n const opener = opts.openBrowser ?? (async (u: string) => {\n await open(u);\n });\n try {\n await opener(url);\n } catch {\n stderr.write(\"Could not auto-open browser. Copy the URL above manually.\\n\");\n }\n\n const code = await waitForCode;\n const tokens = await exchangeCodeForTokens({\n supabaseUrl: opts.supabaseUrl,\n code,\n codeVerifier: verifier,\n redirectUri,\n clientId,\n });\n\n try {\n await saveTokens({\n access_token: tokens.access_token,\n refresh_token: tokens.refresh_token,\n client_id: clientId,\n expires_at: Math.floor(Date.now() / 1000) + (tokens.expires_in ?? 3600),\n user_id: tokens.user?.id,\n });\n } catch (err) {\n if (err instanceof CredentialsError) {\n stderr.write(`\\n${err.message}\\n\\n${credentialsHelpMessage()}\\n`);\n throw err;\n }\n throw err;\n }\n}\n","/**\n * Ink TUI: create-or-link picker (cliSyncInitPicker).\n *\n * Minimal implementation — single screen with:\n * • Create-new input (prefilled with cwd basename)\n * • List of existing repos\n * • Tab to switch zone, Enter to confirm, Esc to cancel.\n */\nimport React, { useState } from \"react\";\nimport { Box, Text, useApp, useInput } from \"ink\";\nimport type { RepoRef, Ownership } from \"../api.js\";\n\nexport type PickerResult =\n | { action: \"create\"; slug: string; owner: Ownership }\n | { action: \"link\"; repo: RepoRef }\n | { action: \"cancel\" };\n\nexport interface InitPickerProps {\n defaultSlug: string;\n ownerships: Ownership[];\n repos: RepoRef[];\n onDone: (r: PickerResult) => void;\n}\n\nexport function InitPicker(props: InitPickerProps): React.ReactElement {\n const { exit } = useApp();\n const [zone, setZone] = useState<\"create\" | \"link\">(\n props.repos.length > 0 ? \"link\" : \"create\",\n );\n const [slug, setSlug] = useState(props.defaultSlug);\n const [ownerIdx, setOwnerIdx] = useState(0);\n const [repoIdx, setRepoIdx] = useState(0);\n\n useInput((input, key) => {\n if (key.escape) {\n props.onDone({ action: \"cancel\" });\n exit();\n return;\n }\n if (key.tab) {\n setZone((z) => (z === \"create\" ? \"link\" : \"create\"));\n return;\n }\n if (key.return) {\n if (zone === \"create\") {\n if (!props.ownerships[ownerIdx]) return;\n props.onDone({\n action: \"create\",\n slug,\n owner: props.ownerships[ownerIdx],\n });\n } else {\n if (!props.repos[repoIdx]) return;\n props.onDone({ action: \"link\", repo: props.repos[repoIdx] });\n }\n exit();\n return;\n }\n if (zone === \"create\") {\n if (key.upArrow)\n setOwnerIdx((i) => (i - 1 + props.ownerships.length) % Math.max(1, props.ownerships.length));\n else if (key.downArrow)\n setOwnerIdx((i) => (i + 1) % Math.max(1, props.ownerships.length));\n else if (key.backspace || key.delete) setSlug((s) => s.slice(0, -1));\n else if (input && /^[a-z0-9-]$/i.test(input))\n setSlug((s) => (s + input).toLowerCase());\n } else {\n if (key.upArrow)\n setRepoIdx((i) => (i - 1 + props.repos.length) % Math.max(1, props.repos.length));\n else if (key.downArrow)\n setRepoIdx((i) => (i + 1) % Math.max(1, props.repos.length));\n }\n });\n\n return (\n <Box flexDirection=\"column\" paddingX={1}>\n <Text bold>Balise — link or create a repo</Text>\n\n <Box marginTop={1} flexDirection=\"column\">\n <Text color={zone === \"create\" ? \"cyan\" : \"gray\"}>\n {zone === \"create\" ? \"▸ \" : \" \"}Create new repo\n </Text>\n {zone === \"create\" && (\n <Box marginLeft={2} flexDirection=\"column\">\n <Text>slug : <Text color=\"yellow\">{slug}</Text></Text>\n <Text>owner: <Text color=\"yellow\">\n {props.ownerships[ownerIdx]\n ? `${props.ownerships[ownerIdx].login} (${props.ownerships[ownerIdx].type})`\n : \"<no ownership>\"}\n </Text> (↑/↓ to change)</Text>\n </Box>\n )}\n </Box>\n\n <Box marginTop={1} flexDirection=\"column\">\n <Text color={zone === \"link\" ? \"cyan\" : \"gray\"}>\n {zone === \"link\" ? \"▸ \" : \" \"}Link existing ({props.repos.length})\n </Text>\n {zone === \"link\" && props.repos.length > 0 && (\n <Box marginLeft={2} flexDirection=\"column\">\n {props.repos.map((r, i) => (\n <Text key={r.id} color={i === repoIdx ? \"yellow\" : undefined}>\n {i === repoIdx ? \"▸ \" : \" \"}\n {r.owner_login}/{r.slug}\n </Text>\n ))}\n </Box>\n )}\n </Box>\n\n <Box marginTop={1}>\n <Text dimColor>\n Tab: switch zone · ↑/↓: navigate · Enter: confirm · Esc: cancel\n </Text>\n </Box>\n </Box>\n );\n}\n","import React from \"react\";\nimport { render } from \"ink\";\nimport {\n ApiClient,\n ApiUnreachableError,\n NotAuthenticatedError,\n SyncAccepted,\n} from \"../api.js\";\nimport { readConfig } from \"../config.js\";\nimport { gitBundle, assertGitRepo, isDirty, NotAGitRepoError } from \"../git.js\";\nimport { ensureAuthenticated, LoginDeclinedError } from \"../auth-ensure.js\";\nimport { SyncProgress } from \"../ui/SyncProgress.js\";\nimport { runInit } from \"./init.js\";\n\nexport interface SyncOptions {\n supabaseUrl: string;\n clientId: string;\n cwd?: string;\n}\n\nexport async function runSync(opts: SyncOptions): Promise<void> {\n try {\n await runSyncInner(opts);\n } catch (err) {\n // Last-resort guard: never let a raw stacktrace reach the user.\n if (err instanceof ApiUnreachableError) {\n process.stderr.write(\n \"Cannot reach the Balise service. Please try again in a moment.\\n\",\n );\n } else {\n process.stderr.write(\"Something went wrong. Please try again.\\n\");\n }\n process.exit(1);\n }\n}\n\nasync function runSyncInner(opts: SyncOptions): Promise<void> {\n const cwd = opts.cwd ?? process.cwd();\n\n // 1. Must be inside a git repo (spec: `not a git repo` exit).\n try {\n await assertGitRepo({ cwd });\n } catch (err) {\n if (err instanceof NotAGitRepoError) {\n process.stderr.write(\"not a git repo\\n\");\n process.exit(1);\n }\n throw err;\n }\n\n // 2. Inline auto-login prompt if no credentials are available.\n try {\n await ensureAuthenticated({\n supabaseUrl: opts.supabaseUrl,\n clientId: opts.clientId,\n });\n } catch (err) {\n if (err instanceof LoginDeclinedError) {\n process.stderr.write(\"Login required. Aborting.\\n\");\n process.exit(1);\n }\n throw err;\n }\n\n // 3. Resolve the per-repo config, else run init first (reused picker).\n let cfg = await readConfig(cwd);\n if (!cfg) {\n process.stderr.write(\"No .balise/config — running `balise init` first.\\n\");\n await runInit({ ...opts, cwd });\n cfg = await readConfig(cwd);\n if (!cfg) {\n process.stderr.write(\"Init cancelled. Aborting sync.\\n\");\n process.exit(1);\n }\n }\n\n // 4. Warn on dirty tree but continue — we archive HEAD, not the working tree.\n if (await isDirty({ cwd })) {\n process.stderr.write(\n \"Warning: working tree has uncommitted changes — syncing HEAD anyway.\\n\",\n );\n }\n\n const client = new ApiClient({\n apiUrl: cfg.api.url,\n supabaseUrl: opts.supabaseUrl,\n clientId: opts.clientId,\n });\n\n process.stderr.write(\n `Packing ${cfg.repo.owner_login}/${cfg.repo.slug} at HEAD…\\n`,\n );\n const { stream, commitSha, branch } = await gitBundle({ cwd });\n\n let accepted: SyncAccepted;\n try {\n accepted = await client.uploadBundle<SyncAccepted>(\n `/v1/repos/${cfg.repo.owner_login}/${cfg.repo.slug}/sync`,\n {\n bundleStream: stream,\n query: { commit_sha: commitSha, branch },\n },\n );\n } catch (err) {\n if (err instanceof NotAuthenticatedError) {\n process.stderr.write(\"Not logged in — run `balise login`.\\n\");\n process.exit(1);\n }\n if (err instanceof ApiUnreachableError) {\n process.stderr.write(\n \"Cannot reach the Balise service. Please try again in a moment.\\n\",\n );\n process.exit(1);\n }\n // Any other unexpected error: show a generic line, never a stack.\n process.stderr.write(\n \"Something went wrong while uploading. Please try again.\\n\",\n );\n process.exit(1);\n }\n\n const result = await new Promise<boolean>((resolve) => {\n const app = render(\n React.createElement(SyncProgress, {\n client,\n syncId: accepted.sync_id,\n onDone: (ok: boolean) => {\n resolve(ok);\n app.unmount();\n },\n }),\n );\n });\n\n process.exit(result ? 0 : 1);\n}\n","/**\n * Ink sync-progress display (cliSyncProgressDisplay).\n *\n * Poll every 1s. User-safe: no internal phases, no error details.\n * Rotates a random flavour message every 10s so the wait doesn't feel dead.\n * On failure shows a generic \"contact support\" + sync_id.\n */\nimport React, { useEffect, useState } from \"react\";\nimport { Box, Text, useApp } from \"ink\";\nimport Spinner from \"ink-spinner\";\nimport type { ApiClient, SyncStatus } from \"../api.js\";\n\nexport interface SyncProgressProps {\n client: ApiClient;\n syncId: string;\n pollIntervalMs?: number;\n messageRotationMs?: number;\n onDone: (ok: boolean) => void;\n}\n\nconst QUEUED_MESSAGES: readonly string[] = [\n \"Waiting for an agent willing to accept the job…\",\n \"Your request is in line. An agent will be assigned once one stops pretending to be busy.\",\n \"Looking for an available agent. Most are currently \\\"in a meeting.\\\"\",\n \"Queued. An agent will pick this up as soon as they finish rereading the spec.\",\n \"Your task is waiting for an agent with the right vibes.\",\n \"Negotiating with an agent. They want a raise.\",\n \"All agents are currently on strike. Sending in a scab.\",\n \"Waiting for an agent to finish their coffee ☕\",\n \"An agent saw your task and walked away slowly. Finding another one.\",\n \"Your task is being passed around like a hot potato. Someone will catch it eventually.\",\n \"Agents are currently arguing about who has to do this one.\",\n \"Waiting for an agent brave enough to open your repo.\",\n \"Your task is sitting in the agent break room. Someone will notice it soon.\",\n \"An agent was assigned, but they ghosted. Recruiting a replacement.\",\n \"Paging an agent. Please hold while we bribe one.\",\n \"Queued. Waiting for an agent with enough context window to care.\",\n \"An agent is spinning up. They're doing their stretches.\",\n \"Looking for an agent whose system prompt allows this.\",\n \"Waiting for a worker. Their last task traumatized them.\",\n \"Your task is in queue. Agents are busy arguing about tabs vs spaces.\",\n \"Finding an agent…\",\n \"Found one. They said no.\",\n \"Finding another agent…\",\n \"This one looks promising.\",\n \"Negotiating…\",\n];\n\nconst RUNNING_MESSAGES: readonly string[] = [\n \"An agent is on it. Try not to watch.\",\n \"Your task has an owner. They seem focused.\",\n \"An agent accepted the job. Surprisingly.\",\n \"Working. The agent asked not to be disturbed.\",\n \"An agent is handling it. They'll let us know if they need anything.\",\n \"Progress is happening. Allegedly.\",\n \"An agent rolled up their sleeves. Mostly for show.\",\n \"Hard at work. The agent has opened seventeen browser tabs.\",\n \"Your agent is locked in. Do not make eye contact.\",\n \"An agent is typing furiously. Some of it might be relevant.\",\n \"Working. The agent has entered the zone. And also a Wikipedia rabbit hole.\",\n \"An agent is deep in thought. Or buffering. Hard to tell.\",\n \"Your task is being handled. The agent is muttering to themselves.\",\n \"Cooking. 🧑🍳\",\n \"The agent is working. They've asked for snacks.\",\n \"An agent is doing the thing. Please clap.\",\n \"Agent is crunching tokens.\",\n \"Working. The agent is reasoning about your reasoning.\",\n \"An agent is in a tool-use loop. Going well so far.\",\n \"Thinking hard. The agent just discovered your codebase has opinions.\",\n \"Working. The agent is negotiating with your linter.\",\n \"An agent is running. They promise it's not an infinite loop.\",\n \"The agent is writing, deleting, and rewriting. Classic.\",\n \"An agent picked it up.\",\n \"They're reading the task…\",\n \"Looks like they have a plan.\",\n \"Executing…\",\n \"Still going. Confidently.\",\n \"Almost there. (They always say that.)\",\n];\n\nfunction pickInitialIndex(len: number): number {\n // Random starting index so consecutive runs don't always open on the same line.\n return Math.floor(Math.random() * len);\n}\n\nexport function SyncProgress(props: SyncProgressProps): React.ReactElement {\n const { exit } = useApp();\n const [status, setStatus] = useState<SyncStatus | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [startedAt] = useState(() => Date.now());\n const [queuedIdx, setQueuedIdx] = useState(() =>\n pickInitialIndex(QUEUED_MESSAGES.length),\n );\n const [runningIdx, setRunningIdx] = useState(() =>\n pickInitialIndex(RUNNING_MESSAGES.length),\n );\n\n // Poll status.\n useEffect(() => {\n let cancelled = false;\n const tick = async (): Promise<void> => {\n try {\n const s = await props.client.getJson<SyncStatus>(\n `/v1/syncs/${props.syncId}`,\n );\n if (cancelled) return;\n setStatus(s);\n if (s.status === \"done\") {\n props.onDone(true);\n exit();\n return;\n }\n if (s.status === \"failed\") {\n props.onDone(false);\n exit();\n return;\n }\n } catch (err) {\n if (cancelled) return;\n setError((err as Error).message);\n props.onDone(false);\n exit();\n return;\n }\n setTimeout(tick, props.pollIntervalMs ?? 1000);\n };\n void tick();\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Rotate flavour messages every N ms (default 10s).\n useEffect(() => {\n const period = props.messageRotationMs ?? 10_000;\n const h = setInterval(() => {\n setQueuedIdx((i) => (i + 1) % QUEUED_MESSAGES.length);\n setRunningIdx((i) => (i + 1) % RUNNING_MESSAGES.length);\n }, period);\n return () => clearInterval(h);\n }, [props.messageRotationMs]);\n\n if (error) {\n return <Text color=\"red\">✗ Failed</Text>;\n }\n\n if (!status) {\n return (\n <Box>\n <Text color=\"cyan\">\n <Spinner type=\"dots\" />\n </Text>\n <Text> Getting things ready…</Text>\n </Box>\n );\n }\n\n if (status.status === \"done\") {\n return <Text color=\"green\">✓ Done</Text>;\n }\n\n if (status.status === \"failed\") {\n return <Text color=\"red\">✗ Failed</Text>;\n }\n\n const message =\n status.status === \"queued\"\n ? QUEUED_MESSAGES[queuedIdx]\n : RUNNING_MESSAGES[runningIdx];\n\n const { files_processed, files_total, nodes_pushed } = status.progress;\n const hasCounters =\n status.status === \"running\" && (files_total > 0 || nodes_pushed > 0);\n\n return (\n <Box flexDirection=\"column\">\n <Box>\n <Text color=\"cyan\">\n <Spinner type=\"dots\" />\n </Text>\n <Text> {message}</Text>\n </Box>\n {hasCounters ? (\n <Text dimColor>\n {\" \"}\n {files_processed}/{files_total} files · {nodes_pushed} concepts\n </Text>\n ) : null}\n </Box>\n );\n}\n"],"mappings":"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","names":["crypto","crypto","request","path","request","fs","path","path","React","crypto","open","crypto","open","path","React","React","render","React","useState","Box","Text","useApp","render","React"]}
|