{"version":3,"sources":["../src/cli/commands/ingestion/install.ts"],"sourcesContent":["import chalk from \"chalk\";\n\nimport {\n  GovernanceCliError,\n  mintIngestionKey,\n} from \"@/cli/utils/governance/cli-api\";\nimport { isLoggedIn, loadConfig } from \"@/cli/utils/governance/config\";\nimport { writeCodexOtelBlock } from \"@/cli/utils/codex-config-toml\";\n\n/**\n * `langwatch ingest install <tool>` — Path B activation flow.\n *\n * Distinct from the gateway-only `langwatch <tool>` wrapper (Path A).\n * Mints the user's personal ingest key (sk-lw-*), prints the OTLP\n * export block, and — for codex specifically — idempotently merges\n * the [otel] activation block into ~/.codex/config.toml so the user\n * pastes nothing manual.\n *\n * Tools handled today:\n *   - codex      : toml merge + env exports\n *   - claude_code: env exports (no toml needed)\n *   - gemini     : env exports (no toml needed; envs are read directly)\n *   - opencode   : env exports (no toml needed)\n *\n * Returning early when the slug isn't recognised keeps the surface\n * forward-compatible — adding a new template is a one-line edit\n * here once we know whether it needs an out-of-band activation step.\n */\n\nconst SUPPORTED_TOOLS = [\n  \"codex\",\n  \"claude_code\",\n  \"gemini\",\n  \"opencode\",\n] as const;\ntype SupportedTool = (typeof SUPPORTED_TOOLS)[number];\n\nexport interface InstallOptions {\n  json?: boolean;\n  /** Suppress the toml write; useful for previewing exports only. */\n  envOnly?: boolean;\n  /**\n   * Override the codex config.toml path. Test-only — exposed because\n   * the codex-config-toml helper accepts it but the CLI surface\n   * keeps the default unless explicitly threaded through.\n   */\n  codexConfigPath?: string;\n}\n\ninterface InstallReport {\n  tool: SupportedTool;\n  source_type: string;\n  endpoint: string;\n  ingestion_token: string;\n  token_prefix: string;\n  codex_config_action?: \"created\" | \"updated\" | \"unchanged\";\n  codex_config_path?: string;\n  env_block: string[];\n}\n\nexport async function installCommand(\n  toolArg: string,\n  options: InstallOptions = {},\n): Promise<void> {\n  const cfg = loadConfig();\n  if (!isLoggedIn(cfg)) {\n    process.stderr.write(\n      \"Not logged in. Run `langwatch login --device` first.\\n\",\n    );\n    process.exit(1);\n    return;\n  }\n\n  const tool = normaliseTool(toolArg);\n  if (!tool) {\n    process.stderr.write(\n      `Unknown tool '${toolArg}'. Supported: ${SUPPORTED_TOOLS.join(\", \")}.\\n`,\n    );\n    process.exit(1);\n    return;\n  }\n\n  try {\n    const report = await runInstall(cfg, tool, options);\n    if (options.json) {\n      process.stdout.write(JSON.stringify(report, null, 2) + \"\\n\");\n      return;\n    }\n    renderHumanReport(report);\n  } catch (err) {\n    const msg = err instanceof GovernanceCliError ? err.message : String(err);\n    process.stderr.write(`Error: ${msg}\\n`);\n    process.exit(1);\n  }\n}\n\nfunction normaliseTool(raw: string): SupportedTool | null {\n  const slug = raw.trim().toLowerCase().replace(/-/g, \"_\");\n  return (SUPPORTED_TOOLS as readonly string[]).includes(slug)\n    ? (slug as SupportedTool)\n    : null;\n}\n\nasync function runInstall(\n  cfg: ReturnType<typeof loadConfig>,\n  tool: SupportedTool,\n  options: InstallOptions,\n): Promise<InstallReport> {\n  // Mint a fresh personal ingest key (sk-lw-*) for this tool's\n  // source_type. The plaintext key is only ever visible at mint\n  // time, so re-running the install command always leaves the user\n  // with a working key written straight into the export block. The\n  // SupportedTool slug doubles as the source_type the mint route\n  // expects (claude_code / codex / gemini / opencode).\n  const { token, prefix, endpoint } = await mintIngestionKey(cfg, tool);\n  const envBlock = buildEnvBlock(tool, endpoint, token);\n\n  const report: InstallReport = {\n    tool,\n    source_type: tool,\n    endpoint,\n    ingestion_token: token,\n    token_prefix: prefix,\n    env_block: envBlock,\n  };\n\n  if (tool === \"codex\" && !options.envOnly) {\n    // codex's OTLP/HTTP exporter sends every signal to the configured\n    // endpoint verbatim — it does NOT append `/v1/traces` the way the\n    // OTel SDKs do. Spell the trace-signal suffix out (mirror of the\n    // wrapper-mode.ts behaviour) so the POST lands on the real handler.\n    const result = writeCodexOtelBlock(\n      {\n        endpoint: `${endpoint}/v1/traces`,\n        ingestionToken: token,\n        environment: cfg.organization?.slug ?? \"langwatch\",\n      },\n      { filePath: options.codexConfigPath },\n    );\n    report.codex_config_action = result.action;\n    report.codex_config_path = result.path;\n  }\n\n  return report;\n}\n\nfunction buildEnvBlock(\n  tool: SupportedTool,\n  endpoint: string,\n  token: string,\n): string[] {\n  const base = [\n    `export OTEL_EXPORTER_OTLP_ENDPOINT=\"${endpoint}\"`,\n    `export OTEL_EXPORTER_OTLP_HEADERS=\"Authorization=Bearer ${token}\"`,\n  ];\n\n  switch (tool) {\n    case \"codex\":\n      return [\n        `export OTEL_TRACES_EXPORTER=otlp`,\n        `export OTEL_EXPORTER_OTLP_PROTOCOL=http/json`,\n        ...base,\n        `export OTEL_RESOURCE_ATTRIBUTES=\"service.name=codex\"`,\n      ];\n    case \"claude_code\":\n      return [\n        `export CLAUDE_CODE_ENABLE_TELEMETRY=1`,\n        // Four claude-code OTel unlock knobs (all ON, collect-everything):\n        //   OTEL_LOG_USER_PROMPTS=1     lifts user prompt text onto user_prompt events\n        //   OTEL_LOG_TOOL_DETAILS=1     lifts tool metadata expansion onto tool_* events\n        //   OTEL_LOG_TOOL_CONTENT=1     lifts tool_input (Bash command, Edit diff, file\n        //                               paths) onto tool_decision + tool_result so the\n        //                               trace shows WHAT the tool did\n        //   OTEL_LOG_RAW_API_BODIES=1   emits api_request_body + api_response_body\n        //                               events carrying the FULL JSON of every claude\n        //                               API call: system prompts, rolling message\n        //                               history, assistant response text + reasoning,\n        //                               tool_use blocks. THIS is the only OTel surface\n        //                               that carries assistant text. May include PII /\n        //                               secrets a user pasted into a prompt; payloads\n        //                               can grow large turn-over-turn — the langwatch\n        //                               receiver caps oversized bodies before they\n        //                               reach storage to keep the CH merge ceiling safe.\n        `export OTEL_LOG_USER_PROMPTS=1`,\n        `export OTEL_LOG_TOOL_DETAILS=1`,\n        `export OTEL_LOG_TOOL_CONTENT=1`,\n        `export OTEL_LOG_RAW_API_BODIES=1`,\n        `export OTEL_TRACES_EXPORTER=otlp`,\n        `export OTEL_LOGS_EXPORTER=otlp`,\n        `export OTEL_METRICS_EXPORTER=otlp`,\n        `export OTEL_EXPORTER_OTLP_PROTOCOL=http/json`,\n        ...base,\n        `export OTEL_RESOURCE_ATTRIBUTES=\"service.name=claude-code\"`,\n      ];\n    case \"gemini\":\n      return [\n        `export GEMINI_TELEMETRY_ENABLED=true`,\n        `export GEMINI_TELEMETRY_TARGET=local`,\n        `export GEMINI_TELEMETRY_USE_COLLECTOR=true`,\n        `export GEMINI_TELEMETRY_TRACES_ENABLED=true`,\n        `export GEMINI_TELEMETRY_OTLP_PROTOCOL=http`,\n        `export GEMINI_TELEMETRY_OTLP_ENDPOINT=\"${endpoint}\"`,\n        `export GEMINI_TELEMETRY_LOG_PROMPTS=true`,\n        `export OTEL_TRACES_EXPORTER=otlp`,\n        `export OTEL_EXPORTER_OTLP_PROTOCOL=http/json`,\n        ...base,\n        `export OTEL_RESOURCE_ATTRIBUTES=\"service.name=gemini-cli\"`,\n      ];\n    case \"opencode\":\n      return [\n        `export OTEL_TRACES_EXPORTER=otlp`,\n        `export OTEL_LOGS_EXPORTER=otlp`,\n        `export OTEL_METRICS_EXPORTER=otlp`,\n        `export OTEL_EXPORTER_OTLP_PROTOCOL=http/json`,\n        ...base,\n        `export OTEL_RESOURCE_ATTRIBUTES=\"service.name=opencode\"`,\n      ];\n  }\n}\n\nfunction renderHumanReport(report: InstallReport): void {\n  process.stdout.write(\n    `${chalk.green(\"✓\")} Minted ingestion key for ${chalk.bold(report.tool)}\\n`,\n  );\n  process.stdout.write(`  endpoint: ${report.endpoint}\\n`);\n  process.stdout.write(`  token:    ${report.ingestion_token}\\n`);\n\n  if (report.codex_config_action) {\n    const verb2 =\n      report.codex_config_action === \"created\"\n        ? \"created\"\n        : report.codex_config_action === \"updated\"\n          ? \"updated\"\n          : \"already up to date\";\n    process.stdout.write(\n      `${chalk.green(\"✓\")} ${report.codex_config_path} ${verb2}\\n`,\n    );\n  }\n\n  process.stdout.write(\"\\nAdd to your shell rc (or run in this shell):\\n\");\n  for (const line of report.env_block) {\n    process.stdout.write(`  ${line}\\n`);\n  }\n\n  if (report.tool === \"codex\") {\n    process.stdout.write(\n      `\\nThe [otel] activation block in your codex config.toml has been wired automatically.\\n`,\n    );\n  } else if (report.tool === \"opencode\") {\n    process.stdout.write(\n      `\\nNote: opencode 1.14 emits structural spans but no gen_ai.* attributes yet.\\n` +\n        `Spans will land but per-call tokens/model/cost wait on upstream semconv support.\\n`,\n    );\n  }\n}\n"],"mappings":"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","names":[]}