{"version":3,"sources":["../src/docker/seed-claude-config.ts"],"sourcesContent":["// Seed $HOME/.claude.json so spawned interactive Claude sessions reach a usable\n// prompt instead of a blocking first-run dialog (the mobile app shows an empty\n// screen otherwise). On a fresh /data volume the CLI would show, in order:\n// onboarding/theme picker, workspace-trust dialog, and the \"custom API key\n// detected\" approval. Seeding these flags clears all three. (The fourth gate,\n// the Bypass Permissions warning, is avoided by launching with\n// `--permission-mode acceptEdits` rather than `--dangerously-skip-permissions` —\n// see src/pty-manager.ts.)\n//\n// Compiled by tsup to dist/seed-claude-config.cjs; docker/entrypoint.sh runs it\n// with the plain `node` in the runtime image, before the streamer starts.\n// __tests__/seed-claude-config.test.ts exercises seedClaudeConfig() directly.\nimport { readFileSync, writeFileSync } from \"node:fs\";\n\n// Trust this workspace dir without the per-project trust dialog. Matches the\n// --browse-root the streamer serves in the Fly container.\nexport const TRUSTED_DIR = \"/data/.claude/projects\";\n\ninterface ProjectEntry {\n  allowedTools: string[];\n  hasTrustDialogAccepted: boolean;\n  hasCompletedProjectOnboarding: boolean;\n}\n\ninterface ClaudeConfig {\n  hasCompletedOnboarding?: boolean;\n  theme?: string;\n  hasTrustDialogAccepted?: boolean;\n  projects?: Record<string, Partial<ProjectEntry>>;\n  customApiKeyResponses?: { approved?: string[]; rejected?: string[] };\n  [key: string]: unknown;\n}\n\n// Read-modify-write merge into any existing config. Idempotent: re-runs (the\n// Fly volume persists .claude.json across restarts) preserve existing keys and\n// never duplicate an approved key suffix.\n//\n// Error handling is deliberate: a fresh volume (ENOENT) starts from empty, but\n// any OTHER read/parse error means the file EXISTS but is unreadable. The file\n// is the system of record (userID, approved keys, feature flags) — overwriting\n// it would silently truncate that state, so we throw rather than clobber.\nexport function seedClaudeConfig(configPath: string, apiKey: string): void {\n  let config: ClaudeConfig = {};\n  try {\n    config = JSON.parse(readFileSync(configPath, \"utf8\"));\n  } catch (err) {\n    if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n      throw new Error(`refusing to overwrite ${configPath}: ${(err as Error).message}`);\n    }\n    // ENOENT — expected first boot. Fall through with an empty config.\n  }\n\n  config.hasCompletedOnboarding = true;\n  config.theme = config.theme || \"dark\";\n  config.hasTrustDialogAccepted = true;\n\n  config.projects = config.projects || {};\n  config.projects[TRUSTED_DIR] = Object.assign(\n    { allowedTools: [], hasTrustDialogAccepted: true, hasCompletedProjectOnboarding: true },\n    config.projects[TRUSTED_DIR] || {},\n  );\n\n  // The custom-API-key approval is keyed by the last 20 chars of the API key,\n  // matching how the CLI records an accepted key. The streamer maps\n  // CLAUDE_API_KEY → ANTHROPIC_API_KEY at spawn, so the CLI reads the same key.\n  if (apiKey) {\n    const suffix = apiKey.slice(-20);\n    config.customApiKeyResponses = config.customApiKeyResponses || { approved: [], rejected: [] };\n    config.customApiKeyResponses.approved = config.customApiKeyResponses.approved || [];\n    if (!config.customApiKeyResponses.approved.includes(suffix)) {\n      config.customApiKeyResponses.approved.push(suffix);\n    }\n  }\n\n  writeFileSync(configPath, JSON.stringify(config, null, 2));\n}\n\n// CLI entry: `node dist/seed-claude-config.cjs`. Reads CLAUDE_CONFIG (path) and\n// CLAUDE_API_KEY from the environment. Any failure is logged with an\n// [entrypoint] prefix and exits non-zero so `set -euo pipefail` aborts boot\n// rather than letting the streamer start with unseeded config.\nfunction main(): void {\n  const configPath = process.env.CLAUDE_CONFIG;\n  if (!configPath) {\n    console.error(\"[entrypoint] seed-claude-config: CLAUDE_CONFIG is not set\");\n    process.exit(1);\n  }\n  try {\n    seedClaudeConfig(configPath, process.env.CLAUDE_API_KEY || \"\");\n  } catch (err) {\n    console.error(`[entrypoint] ${(err as Error).message}`);\n    process.exit(1);\n  }\n}\n\n// tsup bundles this as CJS, so require.main === module is the right guard.\nif (require.main === module) {\n  main();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYA,qBAA4C;AAIrC,IAAM,cAAc;AAyBpB,SAAS,iBAAiB,YAAoB,QAAsB;AACzE,MAAI,SAAuB,CAAC;AAC5B,MAAI;AACF,aAAS,KAAK,UAAM,6BAAa,YAAY,MAAM,CAAC;AAAA,EACtD,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,UAAU;AACpD,YAAM,IAAI,MAAM,yBAAyB,UAAU,KAAM,IAAc,OAAO,EAAE;AAAA,IAClF;AAAA,EAEF;AAEA,SAAO,yBAAyB;AAChC,SAAO,QAAQ,OAAO,SAAS;AAC/B,SAAO,yBAAyB;AAEhC,SAAO,WAAW,OAAO,YAAY,CAAC;AACtC,SAAO,SAAS,WAAW,IAAI,OAAO;AAAA,IACpC,EAAE,cAAc,CAAC,GAAG,wBAAwB,MAAM,+BAA+B,KAAK;AAAA,IACtF,OAAO,SAAS,WAAW,KAAK,CAAC;AAAA,EACnC;AAKA,MAAI,QAAQ;AACV,UAAM,SAAS,OAAO,MAAM,GAAG;AAC/B,WAAO,wBAAwB,OAAO,yBAAyB,EAAE,UAAU,CAAC,GAAG,UAAU,CAAC,EAAE;AAC5F,WAAO,sBAAsB,WAAW,OAAO,sBAAsB,YAAY,CAAC;AAClF,QAAI,CAAC,OAAO,sBAAsB,SAAS,SAAS,MAAM,GAAG;AAC3D,aAAO,sBAAsB,SAAS,KAAK,MAAM;AAAA,IACnD;AAAA,EACF;AAEA,oCAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC3D;AAMA,SAAS,OAAa;AACpB,QAAM,aAAa,QAAQ,IAAI;AAC/B,MAAI,CAAC,YAAY;AACf,YAAQ,MAAM,2DAA2D;AACzE,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,MAAI;AACF,qBAAiB,YAAY,QAAQ,IAAI,kBAAkB,EAAE;AAAA,EAC/D,SAAS,KAAK;AACZ,YAAQ,MAAM,gBAAiB,IAAc,OAAO,EAAE;AACtD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAGA,IAAI,QAAQ,SAAS,QAAQ;AAC3B,OAAK;AACP;","names":[]}