@dreamboard-games/cli 0.1.30-alpha.30 → 0.1.30-alpha.32

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 (39) hide show
  1. package/README.md +8 -8
  2. package/dist/agent-verifier/agent-workspace-verifier.mjs +243 -102
  3. package/dist/agent-verifier/agent-workspace-verifier.mjs.map +1 -1
  4. package/dist/agent-verifier/{chunk-QMOBTQ5G.mjs → chunk-3SPDNMLA.mjs} +3 -3
  5. package/dist/agent-verifier/{chunk-QMOBTQ5G.mjs.map → chunk-3SPDNMLA.mjs.map} +1 -1
  6. package/dist/agent-verifier/{chunk-FNSHNMDY.mjs → chunk-MIRGCMUC.mjs} +3 -10
  7. package/dist/agent-verifier/chunk-MIRGCMUC.mjs.map +1 -0
  8. package/dist/agent-verifier/{global-config-SWWR2LP4.mjs → global-config-2NUESNEQ.mjs} +2 -2
  9. package/dist/agent-verifier/{keychain-backend-UF3Z26JM.mjs → keychain-backend-FF4I6ODB.mjs} +1 -1
  10. package/dist/agent-verifier/{keychain-backend-UF3Z26JM.mjs.map → keychain-backend-FF4I6ODB.mjs.map} +1 -1
  11. package/dist/agent-verifier/{materialize-workspace-K4WYFG5E.mjs → materialize-workspace-J2S4XIIC.mjs} +2 -2
  12. package/dist/agent-verifier/{static-scaffold-MHVM63HU.mjs → static-scaffold-56QBCO6P.mjs} +2 -2
  13. package/dist/authoring-compatibility-internal.js +1 -1
  14. package/dist/{chunk-UI7NWSYA.js → chunk-6NYVJYN4.js} +7 -28
  15. package/dist/chunk-6NYVJYN4.js.map +1 -0
  16. package/dist/{chunk-RTNKVNQA.js → chunk-DWWMZBFB.js} +247 -158
  17. package/dist/chunk-DWWMZBFB.js.map +1 -0
  18. package/dist/{chunk-I4SZ7FA4.js → chunk-TRF7IPXK.js} +3 -9
  19. package/dist/{chunk-I4SZ7FA4.js.map → chunk-TRF7IPXK.js.map} +1 -1
  20. package/dist/{global-config-GK2UC2X6.js → global-config-RBMW7IVA.js} +2 -2
  21. package/dist/index.js +123 -305
  22. package/dist/index.js.map +1 -1
  23. package/dist/internal.js +3 -3
  24. package/dist/internal.js.map +1 -1
  25. package/dist/{keychain-backend-GO34KGTG.js → keychain-backend-FSNTNTZE.js} +1 -1
  26. package/dist/{keychain-backend-GO34KGTG.js.map → keychain-backend-FSNTNTZE.js.map} +1 -1
  27. package/package.json +1 -1
  28. package/release/authoring-release-set.json +2 -2
  29. package/skills/dreamboard/SKILL.md +3 -3
  30. package/skills/dreamboard/references/building-your-first-game.md +1 -1
  31. package/skills/dreamboard/references/cli.md +20 -19
  32. package/skills/dreamboard/references/quickstart.md +3 -3
  33. package/dist/agent-verifier/chunk-FNSHNMDY.mjs.map +0 -1
  34. package/dist/chunk-RTNKVNQA.js.map +0 -1
  35. package/dist/chunk-UI7NWSYA.js.map +0 -1
  36. /package/dist/agent-verifier/{global-config-SWWR2LP4.mjs.map → global-config-2NUESNEQ.mjs.map} +0 -0
  37. /package/dist/agent-verifier/{materialize-workspace-K4WYFG5E.mjs.map → materialize-workspace-J2S4XIIC.mjs.map} +0 -0
  38. /package/dist/agent-verifier/{static-scaffold-MHVM63HU.mjs.map → static-scaffold-56QBCO6P.mjs.map} +0 -0
  39. /package/dist/{global-config-GK2UC2X6.js.map → global-config-RBMW7IVA.js.map} +0 -0
package/dist/internal.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resolveConfig,
10
10
  resolveProjectContext,
11
11
  shortHash
12
- } from "./chunk-RTNKVNQA.js";
12
+ } from "./chunk-DWWMZBFB.js";
13
13
  import {
14
14
  applyWorkspaceCodegen,
15
15
  loadManifest,
@@ -18,11 +18,11 @@ import {
18
18
  setLatestCompileAttempt,
19
19
  updateProjectState,
20
20
  writeSnapshot
21
- } from "./chunk-I4SZ7FA4.js";
21
+ } from "./chunk-TRF7IPXK.js";
22
22
  import {
23
23
  getStoredSession,
24
24
  loadGlobalConfig
25
- } from "./chunk-UI7NWSYA.js";
25
+ } from "./chunk-6NYVJYN4.js";
26
26
  import {
27
27
  ENVIRONMENT_CONFIGS,
28
28
  readJsonFile,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ui/playwright-runner.ts"],"sourcesContent":["import os from \"node:os\";\nimport path from \"node:path\";\nimport type { ResolvedConfig } from \"../types.js\";\nimport { createUserTokenManager } from \"../auth/user-token-manager.js\";\nimport { resolveLocalHarnessAccessToken } from \"../config/local-harness-auth.js\";\n\n/**\n * Browser test runner helpers shared with reducer-native-test-harness (browser runner).\n * Screenshot / JSON scenario navigation helpers lived in the deleted `run` command.\n */\nexport function configurePlaywrightBrowsersPath(): void {\n if (process.env.PLAYWRIGHT_BROWSERS_PATH) {\n return;\n }\n\n const runtimeHome = process.env.HOME;\n let realHome: string;\n try {\n realHome = os.userInfo().homedir;\n } catch {\n return;\n }\n\n if (!runtimeHome || path.resolve(runtimeHome) === path.resolve(realHome)) {\n return;\n }\n\n const browserCachePath =\n process.platform === \"darwin\"\n ? path.join(realHome, \"Library\", \"Caches\", \"ms-playwright\")\n : process.platform === \"win32\"\n ? path.join(realHome, \"AppData\", \"Local\", \"ms-playwright\")\n : path.join(realHome, \".cache\", \"ms-playwright\");\n\n process.env.PLAYWRIGHT_BROWSERS_PATH = browserCachePath;\n}\n\nexport async function buildBrowserAuthInitScript(\n config: ResolvedConfig,\n): Promise<string | null> {\n const resolvedToken =\n resolveLocalHarnessAccessToken(config) ??\n (await createUserTokenManager(config).resolveApiToken())?.token;\n if (!resolvedToken) return null;\n\n return `(function(){localStorage.setItem('dreamboard_auth_token',${JSON.stringify(resolvedToken)});})();`;\n}\n\nexport async function waitForGameReady(\n page: import(\"playwright\").Page,\n timeoutMs = 60000,\n): Promise<void> {\n await page.waitForSelector('iframe[title=\"Game UI Plugin\"]', {\n timeout: timeoutMs,\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AASV,SAAS,kCAAwC;AACtD,MAAI,QAAQ,IAAI,0BAA0B;AACxC;AAAA,EACF;AAEA,QAAM,cAAc,QAAQ,IAAI;AAChC,MAAI;AACJ,MAAI;AACF,eAAW,GAAG,SAAS,EAAE;AAAA,EAC3B,QAAQ;AACN;AAAA,EACF;AAEA,MAAI,CAAC,eAAe,KAAK,QAAQ,WAAW,MAAM,KAAK,QAAQ,QAAQ,GAAG;AACxE;AAAA,EACF;AAEA,QAAM,mBACJ,QAAQ,aAAa,WACjB,KAAK,KAAK,UAAU,WAAW,UAAU,eAAe,IACxD,QAAQ,aAAa,UACnB,KAAK,KAAK,UAAU,WAAW,SAAS,eAAe,IACvD,KAAK,KAAK,UAAU,UAAU,eAAe;AAErD,UAAQ,IAAI,2BAA2B;AACzC;","names":[]}
1
+ {"version":3,"sources":["../src/ui/playwright-runner.ts"],"sourcesContent":["import os from \"node:os\";\nimport path from \"node:path\";\nimport type { ResolvedConfig } from \"../types.js\";\nimport { createUserSessionManager } from \"../auth/user-session-manager.js\";\nimport { resolveLocalHarnessAccessToken } from \"../config/local-harness-auth.js\";\n\n/**\n * Browser test runner helpers shared with reducer-native-test-harness (browser runner).\n * Screenshot / JSON scenario navigation helpers lived in the deleted `run` command.\n */\nexport function configurePlaywrightBrowsersPath(): void {\n if (process.env.PLAYWRIGHT_BROWSERS_PATH) {\n return;\n }\n\n const runtimeHome = process.env.HOME;\n let realHome: string;\n try {\n realHome = os.userInfo().homedir;\n } catch {\n return;\n }\n\n if (!runtimeHome || path.resolve(runtimeHome) === path.resolve(realHome)) {\n return;\n }\n\n const browserCachePath =\n process.platform === \"darwin\"\n ? path.join(realHome, \"Library\", \"Caches\", \"ms-playwright\")\n : process.platform === \"win32\"\n ? path.join(realHome, \"AppData\", \"Local\", \"ms-playwright\")\n : path.join(realHome, \".cache\", \"ms-playwright\");\n\n process.env.PLAYWRIGHT_BROWSERS_PATH = browserCachePath;\n}\n\nexport async function buildBrowserAuthInitScript(\n config: ResolvedConfig,\n): Promise<string | null> {\n const resolvedToken =\n resolveLocalHarnessAccessToken(config) ??\n (await createUserSessionManager(config).resolveApiToken())?.token;\n if (!resolvedToken) return null;\n\n return `(function(){localStorage.setItem('dreamboard_auth_token',${JSON.stringify(resolvedToken)});})();`;\n}\n\nexport async function waitForGameReady(\n page: import(\"playwright\").Page,\n timeoutMs = 60000,\n): Promise<void> {\n await page.waitForSelector('iframe[title=\"Game UI Plugin\"]', {\n timeout: timeoutMs,\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AASV,SAAS,kCAAwC;AACtD,MAAI,QAAQ,IAAI,0BAA0B;AACxC;AAAA,EACF;AAEA,QAAM,cAAc,QAAQ,IAAI;AAChC,MAAI;AACJ,MAAI;AACF,eAAW,GAAG,SAAS,EAAE;AAAA,EAC3B,QAAQ;AACN;AAAA,EACF;AAEA,MAAI,CAAC,eAAe,KAAK,QAAQ,WAAW,MAAM,KAAK,QAAQ,QAAQ,GAAG;AACxE;AAAA,EACF;AAEA,QAAM,mBACJ,QAAQ,aAAa,WACjB,KAAK,KAAK,UAAU,WAAW,UAAU,eAAe,IACxD,QAAQ,aAAa,UACnB,KAAK,KAAK,UAAU,WAAW,SAAS,eAAe,IACvD,KAAK,KAAK,UAAU,UAAU,eAAe;AAErD,UAAQ,IAAI,2BAA2B;AACzC;","names":[]}
@@ -137,4 +137,4 @@ export {
137
137
  _setKeyringModuleForTests,
138
138
  tryKeychainBackend
139
139
  };
140
- //# sourceMappingURL=keychain-backend-GO34KGTG.js.map
140
+ //# sourceMappingURL=keychain-backend-FSNTNTZE.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config/keychain-backend.ts"],"sourcesContent":["/**\n * OS keychain-backed `CredentialBackend` built on top of `@napi-rs/keyring`.\n *\n * Keychain is an optional storage backend. Users can opt in with\n * `credentialBackend: \"keychain\"` in `~/.dreamboard/config.json`\n * (see `credential-store.ts` for the resolver).\n * When enabled, it gives us:\n * - A refresh token encrypted at rest by the OS (Keychain on macOS,\n * Credential Vault on Windows, Secret Service on Linux).\n * - Protection against other processes running as the same user tailing\n * `~/.dreamboard/auth.json` to scrape the token.\n *\n * This module is loaded optionally: `@napi-rs/keyring` is declared as an\n * `optionalDependencies` entry. If the native binary or OS keyring is\n * unavailable, the resolver falls back to the file backend.\n *\n * One-time migration: when the active backend is keychain and `auth.json` still\n * has tokens, `credential-store.ts` copies them into the keychain, verifies the\n * keychain read, and deletes the file. This is the only path that intentionally\n * mutates both backends.\n */\n\nimport type {\n CredentialBackend,\n Credentials,\n StoredSessionSnapshot,\n} from \"./credential-store.js\";\n\n/** Keychain service id. Shared across all Dreamboard CLI builds. */\nconst KEYCHAIN_SERVICE = \"dreamboard-cli\";\n/**\n * Keychain account id. The `user@host` shape is conventional but we keep\n * it fixed for now because the CLI only cares about \"the session for this\n * OS user\", not per-process sessions.\n */\nconst KEYCHAIN_ACCOUNT = \"session\";\n\ntype EntryInstance = {\n setPassword(value: string): void;\n getPassword(): string | null | undefined;\n deletePassword(): boolean;\n};\n\ntype KeyringModule = {\n Entry: new (service: string, account: string) => EntryInstance;\n};\n\nlet cachedModule: KeyringModule | null | undefined;\n\nasync function loadKeyringModule(): Promise<KeyringModule | null> {\n if (cachedModule !== undefined) return cachedModule;\n try {\n // `@napi-rs/keyring` is an optional dependency. If the native binary is\n // missing on this platform the dynamic import throws; resolver policy in\n // credential-store decides whether that is fatal.\n const mod = (await import(\"@napi-rs/keyring\")) as unknown as KeyringModule;\n cachedModule = mod;\n } catch {\n cachedModule = null;\n }\n return cachedModule;\n}\n\nfunction keychainProbe(entry: EntryInstance): boolean {\n // Some platforms have the module installed but no accessible keyring\n // (e.g. headless Linux without DBus). Touch getPassword to verify we\n // can talk to the service without side effects.\n try {\n entry.getPassword();\n return true;\n } catch {\n return false;\n }\n}\n\ntype KeychainPayload = {\n clerkAccessToken?: string;\n accessToken?: string;\n refreshToken?: string;\n clerkAccessExpiresAt?: string;\n tokenExpiresAt?: string;\n dreamboardApiToken?: string;\n dreamboardApiExpiresAt?: string;\n clerkOAuthIssuer?: string;\n clerkOAuthClientId?: string;\n clerkOAuthTokenUrl?: string;\n environment?: string;\n};\n\nfunction parsePayload(\n raw: string | null | undefined,\n): StoredSessionSnapshot | null {\n if (raw === null || raw === undefined) return null;\n const trimmed = raw.trim();\n if (trimmed.length === 0) return null;\n try {\n const parsed = JSON.parse(trimmed) as KeychainPayload;\n const accessToken = parsed.clerkAccessToken ?? parsed.accessToken;\n if (!accessToken && !parsed.refreshToken) return null;\n return {\n accessToken: accessToken || undefined,\n refreshToken: parsed.refreshToken || undefined,\n tokenExpiresAt:\n parsed.clerkAccessExpiresAt || parsed.tokenExpiresAt || undefined,\n dreamboardApiToken: parsed.dreamboardApiToken || undefined,\n dreamboardApiExpiresAt: parsed.dreamboardApiExpiresAt || undefined,\n clerkOAuthIssuer: parsed.clerkOAuthIssuer || undefined,\n clerkOAuthClientId: parsed.clerkOAuthClientId || undefined,\n clerkOAuthTokenUrl: parsed.clerkOAuthTokenUrl || undefined,\n environment: parsed.environment || undefined,\n };\n } catch {\n return null;\n }\n}\n\nfunction writeFull(entry: EntryInstance, creds: Credentials): void {\n if (!creds.accessToken || !creds.refreshToken) {\n throw new Error(\n \"Refusing to persist credentials with an empty accessToken or refreshToken.\",\n );\n }\n const payload: KeychainPayload = {\n clerkAccessToken: creds.accessToken,\n refreshToken: creds.refreshToken,\n clerkAccessExpiresAt: creds.tokenExpiresAt,\n dreamboardApiToken: creds.dreamboardApiToken,\n dreamboardApiExpiresAt: creds.dreamboardApiExpiresAt,\n clerkOAuthIssuer: creds.clerkOAuthIssuer,\n clerkOAuthClientId: creds.clerkOAuthClientId,\n clerkOAuthTokenUrl: creds.clerkOAuthTokenUrl,\n environment: creds.environment,\n };\n entry.setPassword(JSON.stringify(payload));\n}\n\nfunction writeAccessOnly(entry: EntryInstance, accessToken: string): void {\n if (!accessToken) {\n throw new Error(\"Refusing to persist an empty access token.\");\n }\n const payload: KeychainPayload = { accessToken };\n entry.setPassword(JSON.stringify(payload));\n}\n\nfunction clear(entry: EntryInstance): void {\n try {\n entry.deletePassword();\n } catch {\n // keyring-rs throws when the entry does not exist. That is fine -\n // `clearCredentials` contracts as idempotent.\n }\n}\n\nexport type KeychainAvailability =\n | { available: true; backend: CredentialBackend }\n | { available: false; reason: string };\n\n/**\n * Attempt to construct a keychain-backed `CredentialBackend`. Returns an\n * `available: false` result (with a reason) if the native module, the\n * OS keyring, or the probe fails.\n */\nexport async function tryKeychainBackend(): Promise<KeychainAvailability> {\n const mod = await loadKeyringModule();\n if (!mod) {\n return {\n available: false,\n reason: \"@napi-rs/keyring is not installed for this platform\",\n };\n }\n\n let entry: EntryInstance;\n try {\n entry = new mod.Entry(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);\n } catch (err) {\n return {\n available: false,\n reason: `Failed to construct keyring entry: ${String((err as Error).message ?? err)}`,\n };\n }\n\n if (!keychainProbe(entry)) {\n return {\n available: false,\n reason: \"OS keyring is not accessible from this process\",\n };\n }\n\n const backend: CredentialBackend = {\n name: \"keychain\",\n async read() {\n try {\n return parsePayload(entry.getPassword());\n } catch (err) {\n const message = String((err as Error).message ?? err);\n // Transient keychain access errors (e.g. Touch ID prompt\n // cancelled) should not surface as \"session wiped\". Treat the\n // unreadable state as \"no session\" so the caller can fall back\n // to prompting for login.\n if (/no matching entry|not found/i.test(message)) {\n return null;\n }\n throw err;\n }\n },\n async writeFull(creds) {\n writeFull(entry, creds);\n },\n async writeAccessOnly(accessToken) {\n writeAccessOnly(entry, accessToken);\n },\n async clear() {\n clear(entry);\n },\n };\n return { available: true, backend };\n}\n\n/**\n * Test-only escape hatch so unit tests can install a fake keyring module\n * without going through the dynamic import cache.\n */\nexport function _setKeyringModuleForTests(mod: KeyringModule | null): void {\n cachedModule = mod;\n}\n\nexport function _resetKeyringModuleForTests(): void {\n cachedModule = undefined;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/config/keychain-backend.ts"],"sourcesContent":["/**\n * OS keychain-backed `CredentialBackend` built on top of `@napi-rs/keyring`.\n *\n * Keychain is an optional storage backend. Users can opt in with\n * `credentialBackend: \"keychain\"` in `~/.dreamboard/config.json`\n * (see `credential-store.ts` for the resolver).\n * When enabled, it gives us:\n * - A refresh token encrypted at rest by the OS (Keychain on macOS,\n * Credential Vault on Windows, Secret Service on Linux).\n * - Protection against other processes running as the same user tailing\n * `~/.dreamboard/auth.json` to scrape the token.\n *\n * This module is loaded optionally: `@napi-rs/keyring` is declared as an\n * `optionalDependencies` entry. If the native binary or OS keyring is\n * unavailable, the resolver falls back to the file backend.\n *\n * One-time migration: when the active backend is keychain and `auth.json` still\n * has tokens, `credential-store.ts` copies them into the keychain, verifies the\n * keychain read, and deletes the file. This is the only path that intentionally\n * mutates both backends.\n */\n\nimport type {\n CredentialBackend,\n Credentials,\n StoredSessionSnapshot,\n} from \"./credential-store.js\";\n\n/** Keychain service id. Shared across all Dreamboard CLI builds. */\nconst KEYCHAIN_SERVICE = \"dreamboard-cli\";\n/**\n * Keychain account id. The `user@host` shape is conventional but we keep\n * it fixed for now because the CLI only cares about \"the session for this\n * OS user\", not per-process sessions.\n */\nconst KEYCHAIN_ACCOUNT = \"session\";\n\ntype EntryInstance = {\n setPassword(value: string): void;\n getPassword(): string | null | undefined;\n deletePassword(): boolean;\n};\n\ntype KeyringModule = {\n Entry: new (service: string, account: string) => EntryInstance;\n};\n\nlet cachedModule: KeyringModule | null | undefined;\n\nasync function loadKeyringModule(): Promise<KeyringModule | null> {\n if (cachedModule !== undefined) return cachedModule;\n try {\n // `@napi-rs/keyring` is an optional dependency. If the native binary is\n // missing on this platform the dynamic import throws; resolver policy in\n // credential-store decides whether that is fatal.\n const mod = (await import(\"@napi-rs/keyring\")) as unknown as KeyringModule;\n cachedModule = mod;\n } catch {\n cachedModule = null;\n }\n return cachedModule;\n}\n\nfunction keychainProbe(entry: EntryInstance): boolean {\n // Some platforms have the module installed but no accessible keyring\n // (e.g. headless Linux without DBus). Touch getPassword to verify we\n // can talk to the service without side effects.\n try {\n entry.getPassword();\n return true;\n } catch {\n return false;\n }\n}\n\ntype KeychainPayload = {\n clerkAccessToken?: string;\n accessToken?: string;\n refreshToken?: string;\n clerkAccessExpiresAt?: string;\n tokenExpiresAt?: string;\n dreamboardApiToken?: string;\n dreamboardApiExpiresAt?: string;\n clerkOAuthIssuer?: string;\n clerkOAuthClientId?: string;\n clerkOAuthTokenUrl?: string;\n environment?: string;\n};\n\nfunction parsePayload(\n raw: string | null | undefined,\n): StoredSessionSnapshot | null {\n if (raw === null || raw === undefined) return null;\n const trimmed = raw.trim();\n if (trimmed.length === 0) return null;\n try {\n const parsed = JSON.parse(trimmed) as KeychainPayload;\n const accessToken = parsed.clerkAccessToken ?? parsed.accessToken;\n if (!accessToken && !parsed.refreshToken) return null;\n return {\n accessToken: accessToken || undefined,\n refreshToken: parsed.refreshToken || undefined,\n tokenExpiresAt:\n parsed.clerkAccessExpiresAt || parsed.tokenExpiresAt || undefined,\n dreamboardApiToken: parsed.dreamboardApiToken || undefined,\n dreamboardApiExpiresAt: parsed.dreamboardApiExpiresAt || undefined,\n clerkOAuthIssuer: parsed.clerkOAuthIssuer || undefined,\n clerkOAuthClientId: parsed.clerkOAuthClientId || undefined,\n clerkOAuthTokenUrl: parsed.clerkOAuthTokenUrl || undefined,\n environment: parsed.environment || undefined,\n };\n } catch {\n return null;\n }\n}\n\nfunction writeFull(entry: EntryInstance, creds: Credentials): void {\n if (!creds.accessToken || !creds.refreshToken) {\n throw new Error(\n \"Refusing to persist credentials with an empty accessToken or refreshToken.\",\n );\n }\n const payload: KeychainPayload = {\n clerkAccessToken: creds.accessToken,\n refreshToken: creds.refreshToken,\n clerkAccessExpiresAt: creds.tokenExpiresAt,\n dreamboardApiToken: creds.dreamboardApiToken,\n dreamboardApiExpiresAt: creds.dreamboardApiExpiresAt,\n clerkOAuthIssuer: creds.clerkOAuthIssuer,\n clerkOAuthClientId: creds.clerkOAuthClientId,\n clerkOAuthTokenUrl: creds.clerkOAuthTokenUrl,\n environment: creds.environment,\n };\n entry.setPassword(JSON.stringify(payload));\n}\n\nfunction writeAccessOnly(entry: EntryInstance, accessToken: string): void {\n if (!accessToken) {\n throw new Error(\"Refusing to persist an empty access token.\");\n }\n const payload: KeychainPayload = { accessToken };\n entry.setPassword(JSON.stringify(payload));\n}\n\nfunction clear(entry: EntryInstance): void {\n try {\n entry.deletePassword();\n } catch {\n // keyring-rs throws when the entry does not exist. That is fine -\n // Session clearing is idempotent.\n }\n}\n\nexport type KeychainAvailability =\n | { available: true; backend: CredentialBackend }\n | { available: false; reason: string };\n\n/**\n * Attempt to construct a keychain-backed `CredentialBackend`. Returns an\n * `available: false` result (with a reason) if the native module, the\n * OS keyring, or the probe fails.\n */\nexport async function tryKeychainBackend(): Promise<KeychainAvailability> {\n const mod = await loadKeyringModule();\n if (!mod) {\n return {\n available: false,\n reason: \"@napi-rs/keyring is not installed for this platform\",\n };\n }\n\n let entry: EntryInstance;\n try {\n entry = new mod.Entry(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);\n } catch (err) {\n return {\n available: false,\n reason: `Failed to construct keyring entry: ${String((err as Error).message ?? err)}`,\n };\n }\n\n if (!keychainProbe(entry)) {\n return {\n available: false,\n reason: \"OS keyring is not accessible from this process\",\n };\n }\n\n const backend: CredentialBackend = {\n name: \"keychain\",\n async read() {\n try {\n return parsePayload(entry.getPassword());\n } catch (err) {\n const message = String((err as Error).message ?? err);\n // Transient keychain access errors (e.g. Touch ID prompt\n // cancelled) should not surface as \"session wiped\". Treat the\n // unreadable state as \"no session\" so the caller can fall back\n // to prompting for login.\n if (/no matching entry|not found/i.test(message)) {\n return null;\n }\n throw err;\n }\n },\n async writeFull(creds) {\n writeFull(entry, creds);\n },\n async writeAccessOnly(accessToken) {\n writeAccessOnly(entry, accessToken);\n },\n async clear() {\n clear(entry);\n },\n };\n return { available: true, backend };\n}\n\n/**\n * Test-only escape hatch so unit tests can install a fake keyring module\n * without going through the dynamic import cache.\n */\nexport function _setKeyringModuleForTests(mod: KeyringModule | null): void {\n cachedModule = mod;\n}\n\nexport function _resetKeyringModuleForTests(): void {\n cachedModule = undefined;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreamboard-games/cli",
3
- "version": "0.1.30-alpha.30",
3
+ "version": "0.1.30-alpha.32",
4
4
  "description": "Design board games with AI and turn ideas into playable digital prototypes.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,7 +4,7 @@
4
4
  "packages": {
5
5
  "cli": {
6
6
  "name": "@dreamboard-games/cli",
7
- "version": "0.1.30-alpha.30"
7
+ "version": "0.1.30-alpha.32"
8
8
  },
9
9
  "sdk": {
10
10
  "name": "@dreamboard-games/sdk",
@@ -34,5 +34,5 @@
34
34
  "portable": true
35
35
  },
36
36
  "packageManager": "pnpm@10.4.1",
37
- "releaseSetId": "sha256:3bd4ab3361ecffc08feeb8557d6f9cec98997cc1b838984ec56bd37908539d01"
37
+ "releaseSetId": "sha256:97923d17c15d884bd792924d8892b729a9abea1c49a9e0b49fc78bb2a8b52b3d"
38
38
  }
@@ -6,16 +6,16 @@ metadata:
6
6
  tags: [dreamboard, cli, game-dev, board-game, turn-based, multiplayer]
7
7
  ---
8
8
 
9
- # Dreamboard CLI
9
+ # Dreamboard
10
10
 
11
11
  ## Goal
12
12
 
13
13
  Create and iterate on a Dreamboard game locally with the Git-native Dreamboard
14
- CLI, then verify exact commits, run tests, and use the local dev host.
14
+ command, then verify exact commits, run tests, and use the local dev host.
15
15
 
16
16
  ## Prereqs
17
17
 
18
- - Dreamboard CLI installed and available as `dreamboard`
18
+ - Dreamboard installed and available as `dreamboard`
19
19
  Install with `npm install -g dreamboard`
20
20
  - Authenticated via `dreamboard auth login`
21
21
 
@@ -33,7 +33,7 @@ By the end of the tutorial you will have:
33
33
 
34
34
  ## Prerequisites
35
35
 
36
- - Dreamboard CLI installed: `npm install -g dreamboard`
36
+ - Dreamboard installed: `npm install -g dreamboard`
37
37
  - authenticated with `dreamboard auth login`
38
38
 
39
39
  ## 1. Create the workspace
@@ -1,17 +1,17 @@
1
1
  <!-- Generated by apps/dreamboard-cli/scripts/sync-skill-docs.ts. -->
2
2
  <!-- Source: docs/reference/cli.mdx -->
3
3
 
4
- # CLI
4
+ # Dreamboard command reference
5
5
 
6
- Reference for Dreamboard CLI workflows and commands.
6
+ Reference for Dreamboard workflows and commands.
7
7
 
8
- Dreamboard CLI manages the Git-native authored workspace loop: authenticate,
8
+ Dreamboard manages the Git-native authored workspace loop: authenticate,
9
9
  create or clone a project, verify exact commits, build or preview pushed
10
10
  commits, run a local dev host, and test reducer scenarios.
11
11
 
12
12
  ## Responsibility
13
13
 
14
- Use the CLI for:
14
+ Use Dreamboard for:
15
15
 
16
16
  - authenticating the current machine
17
17
  - creating or cloning project workspaces
@@ -46,14 +46,14 @@ Use this sequence for the normal authored loop:
46
46
 
47
47
  ## Workspace commands
48
48
 
49
- | Command | Use it for |
50
- | --- | --- |
51
- | `dreamboard auth login` | Open browser login and store a refreshable session |
52
- | `dreamboard auth status` | Show the current authenticated session |
49
+ | Command | Use it for |
50
+ | ------------------------------------------------------ | --------------------------------------------------------------- |
51
+ | `dreamboard auth login` | Open browser login and store a refreshable session |
52
+ | `dreamboard auth status` | Show the current authenticated session |
53
53
  | `dreamboard project create <slug> --description "..."` | Create a project, scaffold a local workspace, and configure Git |
54
- | `dreamboard project clone <project>` | Clone an existing project repository |
55
- | `dreamboard project status --commit <rev>` | Show server state for one exact commit |
56
- | `dreamboard verify --commit <rev>` | Verify one exact commit in a detached worktree |
54
+ | `dreamboard project clone <project>` | Clone an existing project repository |
55
+ | `dreamboard project status --commit <rev>` | Show server state for one exact commit |
56
+ | `dreamboard verify --commit <rev>` | Verify one exact commit in a detached worktree |
57
57
 
58
58
  ```bash
59
59
  dreamboard auth login
@@ -70,14 +70,14 @@ dreamboard verify --commit HEAD
70
70
  Use commit commands after the commit has been pushed to the configured Git
71
71
  remote.
72
72
 
73
- | Command | Use it for |
74
- | --- | --- |
75
- | `dreamboard test` | Run offline reducer tests |
76
- | `dreamboard dev [--from-scenario <id>]` | Start the local project dev host |
77
- | `dreamboard build --commit <rev>` | Create a server build for a pushed commit |
78
- | `dreamboard preview --commit <rev>` | Create a preview for a pushed commit |
79
- | `dreamboard release publish --commit <rev> --yes` | Publish an explicit release |
80
- | `dreamboard release current` | Show the current release pointer |
73
+ | Command | Use it for |
74
+ | ------------------------------------------------- | ----------------------------------------- |
75
+ | `dreamboard test` | Run offline reducer tests |
76
+ | `dreamboard dev [--from-scenario <id>]` | Start the local project dev host |
77
+ | `dreamboard build --commit <rev>` | Create a server build for a pushed commit |
78
+ | `dreamboard preview --commit <rev>` | Create a preview for a pushed commit |
79
+ | `dreamboard release publish --commit <rev> --yes` | Publish an explicit release |
80
+ | `dreamboard release current` | Show the current release pointer |
81
81
 
82
82
  ```bash
83
83
  dreamboard test
@@ -96,6 +96,7 @@ dreamboard test --scenario test/scenarios/player-two-wins.scenario.ts
96
96
  ```
97
97
 
98
98
  ## Start local server
99
+
99
100
  ```bash
100
101
  dreamboard dev
101
102
  ```
@@ -3,9 +3,9 @@
3
3
 
4
4
  # Quickstart
5
5
 
6
- Install Dreamboard CLI, create a game, and iterate on manifest-driven scaffolding.
6
+ Install Dreamboard, create a game, and iterate on manifest-driven scaffolding.
7
7
 
8
- Install the CLI:
8
+ Install Dreamboard:
9
9
 
10
10
  ```bash
11
11
  npm install -g dreamboard
@@ -63,7 +63,7 @@ Useful follow-up commands:
63
63
  - `dreamboard build --commit HEAD` creates a server build for a pushed commit.
64
64
  - `dreamboard preview --commit HEAD` creates a preview for a pushed commit.
65
65
 
66
- For a concise command reference, see [CLI](./cli.md).
66
+ For a concise command reference, see [Dreamboard command reference](./cli.md).
67
67
 
68
68
  For a full walkthrough, continue with
69
69
  [Building your first game](./building-your-first-game.md).
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/config/global-config.ts","../../src/config/credential-store.ts"],"sourcesContent":["import os from \"node:os\";\nimport path from \"node:path\";\nimport type { CredentialBackendPreference, GlobalConfig } from \"../types.js\";\nimport { PROJECT_DIR_NAME } from \"../constants.js\";\nimport { ensureDir, readJsonFile } from \"../utils/fs.js\";\nimport { atomicWriteFile } from \"../utils/atomic-file.js\";\nimport { getCredentialFilePath } from \"./credential-store.js\";\n\nfunction normalizeCredentialBackend(\n value: unknown,\n): CredentialBackendPreference | undefined {\n if (value === \"file\" || value === \"keychain\") return value;\n // Tolerate unknown / malformed values rather than refusing to load the\n // whole config - an unrecognised backend name should degrade to \"use\n // the default\" instead of locking the user out of their CLI.\n return undefined;\n}\n\nexport function getGlobalConfigPath(): string {\n return path.join(os.homedir(), PROJECT_DIR_NAME, \"config.json\");\n}\n\n/**\n * Path to the on-disk credential file used by the file backend of\n * `CredentialStore`. Re-exported here to avoid circular / ad-hoc imports\n * in UI surface (`auth status`, `config show`, etc).\n */\nexport function getGlobalAuthPath(): string {\n return getCredentialFilePath();\n}\n\n/**\n * Load non-credential CLI configuration.\n *\n * Note: this function used to also load `authToken` / `refreshToken`\n * from `auth.json` and flatten them onto `GlobalConfig`. That shape\n * enabled the refresh-token-wipe bug: `saveGlobalConfig({ ...config })`\n * without explicit auth fields erased the stored refresh token.\n *\n * Credentials are now owned exclusively by `CredentialStore`. Callers\n * that need them must import `getCredentials()` directly.\n */\nexport async function loadGlobalConfig(): Promise<GlobalConfig> {\n const config = await readJsonFile<GlobalConfig>(getGlobalConfigPath()).catch(\n () => ({}) as GlobalConfig,\n );\n return {\n environment: config.environment,\n credentialBackend: normalizeCredentialBackend(config.credentialBackend),\n };\n}\n\n/**\n * Persist non-credential CLI configuration.\n *\n * This function cannot write credentials, by construction: the\n * `GlobalConfig` type has no credential fields. Credentials must be\n * persisted through `setCredentials` / `clearCredentials` from\n * `credential-store.ts`.\n */\nexport async function saveGlobalConfig(config: GlobalConfig): Promise<void> {\n const configDir = path.join(os.homedir(), PROJECT_DIR_NAME);\n await ensureDir(configDir);\n const normalized: GlobalConfig = {\n environment: config.environment,\n credentialBackend: normalizeCredentialBackend(config.credentialBackend),\n };\n await atomicWriteFile(\n getGlobalConfigPath(),\n `${JSON.stringify(normalized, null, 2)}\\n`,\n { mode: 0o600 },\n );\n}\n","/**\n * Single writer for the long-lived Dreamboard session credentials.\n *\n * Design invariants (enforced at the type level and tested in\n * `credential-store.test.ts`):\n *\n * 1. This module is the ONLY place in the CLI that writes credentials to\n * disk or the OS keychain. `global-config.ts` used to own both the\n * config and the credentials via `saveGlobalConfig`, which made it\n * trivial to wipe a refresh token by accident. The `GlobalConfig` type\n * no longer carries credentials, so attempting to persist one through\n * the config path is a type error.\n *\n * 2. The mutating surface is intentionally narrow:\n * - `setCredentials(c)` for refreshable sessions (both tokens present)\n * - `setAccessOnlySession(accessToken)` for the `auth set` / `config set\n * --token` power-user path, which has no refresh token by\n * construction\n * - `clearCredentials()` wipes the file entirely\n * There is no \"partial update\" API. `Credentials` requires both\n * `accessToken` and `refreshToken`, so it is impossible to persist a\n * half-populated refreshable session.\n *\n * 3. Writes go through `atomicWriteFile` + `withFileLock`, so a crash or\n * interrupt during CLI writes cannot leave `auth.json`\n * truncated, and parallel CLI invocations cannot clobber each other's\n * rotated refresh tokens.\n *\n * 4. The on-disk JSON shape for the file backend is kept backward\n * compatible: we continue to read/write `authToken` + `refreshToken`\n * so existing users are not forced to log in again after this change.\n * A newer `accessToken` key is also accepted for read to ease any\n * future format bump.\n *\n * 5. All builds default to the file backend. The OS keychain is an explicit\n * opt-in through config or `DREAMBOARD_CREDENTIAL_BACKEND=keychain`.\n */\n\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { promises as fs } from \"node:fs\";\nimport { PROJECT_DIR_NAME } from \"../constants.js\";\nimport {\n atomicWriteFile,\n withFileLock,\n type FileLockOptions,\n} from \"../utils/atomic-file.js\";\n\n/**\n * Fully refreshable session. `accessToken` is the Clerk OAuth bootstrap token\n * retained for refresh/exchange compatibility; ordinary API calls use\n * `dreamboardApiToken`.\n */\nexport type Credentials = {\n readonly accessToken: string;\n readonly refreshToken: string;\n readonly tokenExpiresAt?: string;\n readonly dreamboardApiToken?: string;\n readonly dreamboardApiExpiresAt?: string;\n readonly clerkOAuthIssuer?: string;\n readonly clerkOAuthClientId?: string;\n readonly clerkOAuthTokenUrl?: string;\n readonly environment?: string;\n};\n\n/**\n * Raw on-disk snapshot. Either or both fields may be present. The refresh\n * coordinator only acts on snapshots that have both tokens populated.\n */\nexport type StoredSessionSnapshot = {\n readonly accessToken?: string;\n readonly refreshToken?: string;\n readonly tokenExpiresAt?: string;\n readonly dreamboardApiToken?: string;\n readonly dreamboardApiExpiresAt?: string;\n readonly clerkOAuthIssuer?: string;\n readonly clerkOAuthClientId?: string;\n readonly clerkOAuthTokenUrl?: string;\n readonly environment?: string;\n};\n\nexport type CredentialBackendName = \"file\" | \"keychain\";\n\nexport type CredentialBackend = {\n readonly name: CredentialBackendName;\n read(): Promise<StoredSessionSnapshot | null>;\n writeFull(creds: Credentials): Promise<void>;\n writeAccessOnly(accessToken: string): Promise<void>;\n clear(reason?: CredentialClearReason): Promise<void>;\n};\n\nexport type CredentialLockOps = {\n readonly backendName: CredentialBackendName;\n read(): Promise<StoredSessionSnapshot | null>;\n writeFull(creds: Credentials): Promise<void>;\n writeAccessOnly(accessToken: string): Promise<void>;\n clear(reason?: CredentialClearReason): Promise<void>;\n};\n\nexport type CredentialClearReason =\n | \"auth_clear_command\"\n | \"logout_command\"\n | \"user_token_manager_logout\"\n | \"credential_store_clear\";\n\ntype DiskShape = Partial<{\n clerkAccessToken: string;\n clerkAccessExpiresAt: string;\n accessToken: string;\n authToken: string;\n refreshToken: string;\n tokenExpiresAt: string;\n dreamboardApiToken: string;\n dreamboardApiExpiresAt: string;\n clerkOAuthIssuer: string;\n clerkOAuthClientId: string;\n clerkOAuthTokenUrl: string;\n environment: string;\n}>;\n\nlet credentialDirectoryOverrideForTests: string | null = null;\n\nfunction getCredentialDirectory(): string {\n return (\n credentialDirectoryOverrideForTests ??\n path.join(os.homedir(), PROJECT_DIR_NAME)\n );\n}\n\nexport function getCredentialFilePath(): string {\n return path.join(getCredentialDirectory(), \"auth.json\");\n}\n\nexport function getCredentialAuditLogPath(): string {\n return path.join(getCredentialDirectory(), \"auth-events.log\");\n}\n\nfunction getCredentialLockPath(): string {\n return `${getCredentialFilePath()}.lock`;\n}\n\nasync function appendCredentialAuditEvent(event: {\n readonly event: \"auth_file_deleted\" | \"auth_file_delete_missing\";\n readonly reason: CredentialClearReason;\n readonly authPath: string;\n readonly backend: CredentialBackendName;\n}): Promise<void> {\n try {\n const logPath = getCredentialAuditLogPath();\n await fs.mkdir(path.dirname(logPath), { recursive: true, mode: 0o700 });\n await fs.appendFile(\n logPath,\n `${JSON.stringify({\n timestamp: new Date().toISOString(),\n pid: process.pid,\n ...event,\n })}\\n`,\n { mode: 0o600 },\n );\n } catch {\n // Credential clearing must not fail because local diagnostic logging failed.\n }\n}\n\nasync function fileRead(): Promise<StoredSessionSnapshot | null> {\n const filePath = getCredentialFilePath();\n let data: string;\n try {\n data = await fs.readFile(filePath, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n if (data.trim().length === 0) {\n return null;\n }\n let parsed: DiskShape;\n try {\n parsed = JSON.parse(data) as DiskShape;\n } catch {\n return null;\n }\n const accessToken =\n parsed.clerkAccessToken ?? parsed.accessToken ?? parsed.authToken;\n const refreshToken = parsed.refreshToken;\n if (!accessToken && !refreshToken) return null;\n return {\n accessToken: accessToken || undefined,\n refreshToken: refreshToken || undefined,\n tokenExpiresAt:\n parsed.clerkAccessExpiresAt || parsed.tokenExpiresAt || undefined,\n dreamboardApiToken: parsed.dreamboardApiToken || undefined,\n dreamboardApiExpiresAt: parsed.dreamboardApiExpiresAt || undefined,\n clerkOAuthIssuer: parsed.clerkOAuthIssuer || undefined,\n clerkOAuthClientId: parsed.clerkOAuthClientId || undefined,\n clerkOAuthTokenUrl: parsed.clerkOAuthTokenUrl || undefined,\n environment: parsed.environment || undefined,\n };\n}\n\nasync function writeFilePayload(payload: DiskShape): Promise<void> {\n await atomicWriteFile(\n getCredentialFilePath(),\n `${JSON.stringify(payload, null, 2)}\\n`,\n { mode: 0o600 },\n );\n}\n\nasync function fileWriteFull(creds: Credentials): Promise<void> {\n if (!creds.accessToken || !creds.refreshToken) {\n throw new Error(\n \"Refusing to persist credentials with an empty accessToken or refreshToken.\",\n );\n }\n await writeFilePayload({\n clerkAccessToken: creds.accessToken,\n refreshToken: creds.refreshToken,\n clerkAccessExpiresAt: creds.tokenExpiresAt,\n dreamboardApiToken: creds.dreamboardApiToken,\n dreamboardApiExpiresAt: creds.dreamboardApiExpiresAt,\n clerkOAuthIssuer: creds.clerkOAuthIssuer,\n clerkOAuthClientId: creds.clerkOAuthClientId,\n clerkOAuthTokenUrl: creds.clerkOAuthTokenUrl,\n environment: creds.environment,\n });\n}\n\nasync function fileWriteAccessOnly(accessToken: string): Promise<void> {\n if (!accessToken) {\n throw new Error(\"Refusing to persist an empty access token.\");\n }\n await writeFilePayload({ authToken: accessToken });\n}\n\nasync function fileClear(\n reason: CredentialClearReason = \"credential_store_clear\",\n): Promise<void> {\n const filePath = getCredentialFilePath();\n try {\n await fs.unlink(filePath);\n await appendCredentialAuditEvent({\n event: \"auth_file_deleted\",\n reason,\n authPath: filePath,\n backend: \"file\",\n });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n await appendCredentialAuditEvent({\n event: \"auth_file_delete_missing\",\n reason,\n authPath: filePath,\n backend: \"file\",\n });\n return;\n }\n throw err;\n }\n}\n\nexport const fileCredentialBackend: CredentialBackend = {\n name: \"file\",\n read: fileRead,\n writeFull: fileWriteFull,\n writeAccessOnly: fileWriteAccessOnly,\n clear: fileClear,\n};\n\nexport type BackendResolver = () =>\n | CredentialBackend\n | Promise<CredentialBackend>;\n\nexport class CredentialStoreUnavailableError extends Error {\n readonly code = \"CREDENTIAL_STORE_UNAVAILABLE\";\n\n constructor(reason: string) {\n super(`Credential store unavailable: ${reason}`);\n this.name = \"CredentialStoreUnavailableError\";\n }\n}\n\nlet cachedBackend: CredentialBackend | null = null;\nlet migrationCompleted = false;\nlet backendResolver: BackendResolver = defaultBackendResolver;\n\n/**\n * Resolver precedence for all builds:\n *\n * 1. `DREAMBOARD_CREDENTIAL_BACKEND` env var (debugging / CI override).\n * - \"file\" -> force file\n * - \"keychain\" -> force keychain (falls back to file if the native\n * module or the OS keyring is unavailable)\n * - \"auto\" -> same as unset (use config)\n * - unknown -> throw so typos fail loud\n * 2. `credentialBackend` in `~/.dreamboard/config.json`.\n * - \"keychain\" -> opt in to the OS keychain (with file fallback)\n * - \"file\" / unset / malformed -> file\n * 3. Default: file backend.\n *\n * Keychain is opt-in because on macOS the OS login-keychain prompts for\n * the user's password the first time a new binary tries to write to an\n * item, and re-prompts whenever the Node binary signature changes. We\n * would rather ship a zero-prompt default and let users who care about\n * encrypted-at-rest storage enable it.\n *\n * The resolver is async because the keychain probe requires a dynamic\n * `@napi-rs/keyring` import.\n */\nasync function defaultBackendResolver(): Promise<CredentialBackend> {\n const override = (process.env.DREAMBOARD_CREDENTIAL_BACKEND ?? \"\")\n .trim()\n .toLowerCase();\n if (override === \"file\") {\n return fileCredentialBackend;\n }\n if (override && override !== \"keychain\" && override !== \"auto\") {\n // Fail loud on typos rather than silently falling back: this env\n // var exists specifically for users who are debugging auth issues\n // and need to know their override took effect.\n throw new Error(\n `Unknown DREAMBOARD_CREDENTIAL_BACKEND value \"${override}\" (expected \"file\", \"keychain\", or \"auto\").`,\n );\n }\n\n const useKeychain =\n override === \"keychain\" || (await readCredentialBackendPreference());\n if (!useKeychain) {\n return fileCredentialBackend;\n }\n\n const { tryKeychainBackend } = await import(\"./keychain-backend.js\");\n const keychain = await tryKeychainBackend();\n if (keychain.available) {\n return keychain.backend;\n }\n // The user explicitly asked for keychain but the platform can't\n // provide one (no libsecret on Linux, missing native module, etc).\n // Silently degrade to the file backend so the CLI stays usable; the\n // active backend is still visible through `dreamboard auth status`.\n return fileCredentialBackend;\n}\n\nasync function readCredentialBackendPreference(): Promise<boolean> {\n try {\n // Dynamic import to avoid a top-level cycle with `global-config.ts`\n // (which imports `getCredentialFilePath` from this module). Using\n // the async path keeps the cycle purely lazy.\n const { loadGlobalConfig } = await import(\"./global-config.js\");\n const config = await loadGlobalConfig();\n return config.credentialBackend === \"keychain\";\n } catch {\n // If the config file is unreadable or the dynamic import fails\n // (e.g. during early bootstrap), fall back to the file-backed\n // default rather than crashing credential lookups.\n return false;\n }\n}\n\n/**\n * Override which backend is used. Tests use this to inject in-memory\n * backends; production code uses the file-default resolver.\n */\nexport function setCredentialBackendResolver(resolver: BackendResolver): void {\n backendResolver = resolver;\n cachedBackend = null;\n migrationCompleted = false;\n}\n\nexport async function getCredentialBackend(): Promise<CredentialBackend> {\n if (cachedBackend === null) {\n cachedBackend = await backendResolver();\n // One-time migration: if we resolved to a non-file backend and\n // `auth.json` still has credentials from the old layout, copy them\n // over. The file is intentionally left in place; implicit backend\n // migration must not make a working CLI session appear to vanish from\n // the default file-backed view.\n if (!migrationCompleted && cachedBackend.name !== \"file\") {\n await migrateFromFileBackendIfNeeded(cachedBackend);\n }\n migrationCompleted = true;\n }\n return cachedBackend;\n}\n\nasync function migrateFromFileBackendIfNeeded(\n target: CredentialBackend,\n options: { failClosed?: boolean } = {},\n): Promise<void> {\n try {\n const [onDisk, onTarget] = await Promise.all([\n fileCredentialBackend.read(),\n target.read(),\n ]);\n if (!onDisk) return;\n if (onTarget) {\n // Target already has a session - the user has already migrated. Leave the\n // file copy alone so a transient keychain override/probe cannot remove\n // the visible file-backed session.\n return;\n }\n if (onDisk.accessToken && onDisk.refreshToken) {\n const migrated: Credentials = {\n accessToken: onDisk.accessToken,\n refreshToken: onDisk.refreshToken,\n tokenExpiresAt: onDisk.tokenExpiresAt,\n dreamboardApiToken: onDisk.dreamboardApiToken,\n dreamboardApiExpiresAt: onDisk.dreamboardApiExpiresAt,\n clerkOAuthIssuer: onDisk.clerkOAuthIssuer,\n clerkOAuthClientId: onDisk.clerkOAuthClientId,\n clerkOAuthTokenUrl: onDisk.clerkOAuthTokenUrl,\n environment: onDisk.environment,\n };\n await target.writeFull(migrated);\n await verifyMigratedSession(target, migrated);\n } else if (onDisk.accessToken) {\n await target.writeAccessOnly(onDisk.accessToken);\n const migrated = await target.read();\n if (migrated?.accessToken !== onDisk.accessToken) {\n throw new Error(\"Credential migration verification failed.\");\n }\n } else {\n return;\n }\n } catch (error) {\n if (options.failClosed) {\n throw new CredentialStoreUnavailableError(\n error instanceof Error ? error.message : String(error),\n );\n }\n // Migration is best-effort. A failure here should not block CLI\n // operation; on next run the file backend is still consulted\n // directly because the keychain backend's `read` returns null and\n // callers fall through to \"missing session\" → login prompt.\n }\n}\n\nasync function verifyMigratedSession(\n target: CredentialBackend,\n expected: Credentials,\n): Promise<void> {\n const migrated = await target.read();\n if (\n migrated?.accessToken !== expected.accessToken ||\n migrated.refreshToken !== expected.refreshToken\n ) {\n throw new Error(\"Credential migration verification failed.\");\n }\n}\n\nexport async function getActiveCredentialBackendName(): Promise<CredentialBackendName> {\n const backend = await getCredentialBackend();\n return backend.name;\n}\n\n/** Loose read: returns whatever is on disk, including access-only sessions. */\nexport async function getStoredSession(): Promise<StoredSessionSnapshot | null> {\n if (process.env.DREAMBOARD_AGENT_TOKEN?.trim()) {\n return null;\n }\n const backend = await getCredentialBackend();\n return backend.read();\n}\n\n/** Strict read: returns a refreshable pair, or null if either token is missing. */\nexport async function getCredentials(): Promise<Credentials | null> {\n const snapshot = await getStoredSession();\n if (!snapshot) return null;\n const { accessToken, refreshToken } = snapshot;\n if (!accessToken || !refreshToken) return null;\n return {\n accessToken,\n refreshToken,\n tokenExpiresAt: snapshot.tokenExpiresAt,\n dreamboardApiToken: snapshot.dreamboardApiToken,\n dreamboardApiExpiresAt: snapshot.dreamboardApiExpiresAt,\n clerkOAuthIssuer: snapshot.clerkOAuthIssuer,\n clerkOAuthClientId: snapshot.clerkOAuthClientId,\n clerkOAuthTokenUrl: snapshot.clerkOAuthTokenUrl,\n environment: snapshot.environment,\n };\n}\n\nexport async function setCredentials(creds: Credentials): Promise<void> {\n await withFileLock(getCredentialLockPath(), async () => {\n const backend = await getCredentialBackend();\n await backend.writeFull(creds);\n });\n}\n\nexport async function setAccessOnlySession(accessToken: string): Promise<void> {\n await withFileLock(getCredentialLockPath(), async () => {\n const backend = await getCredentialBackend();\n await backend.writeAccessOnly(accessToken);\n });\n}\n\nexport async function clearCredentials(\n reason: CredentialClearReason = \"credential_store_clear\",\n): Promise<void> {\n await withFileLock(getCredentialLockPath(), async () => {\n const backend = await getCredentialBackend();\n await backend.clear(reason);\n });\n}\n\n/**\n * Run `fn` while holding the cross-process credential lock. `fn` receives\n * an ops handle that reads/writes the active backend without re-acquiring\n * the lock (avoiding deadlock).\n *\n * This is the only correct way to perform a read-modify-write on stored\n * credentials (e.g. CLI refresh rotation) in the presence of\n * concurrent CLI invocations.\n */\nexport async function withCredentialLock<T>(\n fn: (ops: CredentialLockOps) => Promise<T>,\n options?: FileLockOptions,\n): Promise<T> {\n return withFileLock(\n getCredentialLockPath(),\n async () => {\n const backend = await getCredentialBackend();\n const ops: CredentialLockOps = {\n backendName: backend.name,\n read: () => backend.read(),\n writeFull: (creds) => backend.writeFull(creds),\n writeAccessOnly: (accessToken) => backend.writeAccessOnly(accessToken),\n clear: (reason) => backend.clear(reason),\n };\n return fn(ops);\n },\n options,\n );\n}\n\n/** Test-only reset of module state. Not exported through the barrel. */\nexport function _resetCredentialStoreForTests(): void {\n cachedBackend = null;\n migrationCompleted = false;\n backendResolver = defaultBackendResolver;\n credentialDirectoryOverrideForTests = null;\n}\n\n/** Test-only override of the credential directory. Not exported through the barrel. */\nexport function _setCredentialDirectoryForTests(\n directory: string | null,\n): void {\n credentialDirectoryOverrideForTests = directory;\n cachedBackend = null;\n migrationCompleted = false;\n}\n"],"mappings":"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","names":["os","path","loadGlobalConfig","path","os"]}