panopticon-cli 0.4.33 → 0.5.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.
Files changed (71) hide show
  1. package/README.md +96 -210
  2. package/dist/{agents-VLK4BMVA.js → agents-5OPQKM5K.js} +6 -5
  3. package/dist/{chunk-OMNXYPXC.js → chunk-2V4NF7J2.js} +14 -1
  4. package/dist/chunk-2V4NF7J2.js.map +1 -0
  5. package/dist/{chunk-XKT5MHPT.js → chunk-4YSYJ4HM.js} +2 -2
  6. package/dist/{chunk-XFR2DLMR.js → chunk-76F6DSVS.js} +49 -10
  7. package/dist/chunk-76F6DSVS.js.map +1 -0
  8. package/dist/{chunk-PI7Y3PSN.js → chunk-F5555J3A.js} +42 -6
  9. package/dist/chunk-F5555J3A.js.map +1 -0
  10. package/dist/{chunk-KJ2TRXNK.js → chunk-FTCPTHIJ.js} +47 -420
  11. package/dist/chunk-FTCPTHIJ.js.map +1 -0
  12. package/dist/chunk-HJSM6E6U.js +1038 -0
  13. package/dist/chunk-HJSM6E6U.js.map +1 -0
  14. package/dist/{chunk-RBUO57TC.js → chunk-NLQRED36.js} +3 -3
  15. package/dist/chunk-NLQRED36.js.map +1 -0
  16. package/dist/{chunk-ASY7T35E.js → chunk-OWHXCGVO.js} +245 -90
  17. package/dist/chunk-OWHXCGVO.js.map +1 -0
  18. package/dist/{chunk-BKCWRMUX.js → chunk-VHKSS7QX.js} +106 -11
  19. package/dist/chunk-VHKSS7QX.js.map +1 -0
  20. package/dist/{chunk-GFP3PIPB.js → chunk-YGJ54GW2.js} +1 -1
  21. package/dist/chunk-YGJ54GW2.js.map +1 -0
  22. package/dist/cli/index.js +1521 -935
  23. package/dist/cli/index.js.map +1 -1
  24. package/dist/dashboard/prompts/work-agent.md +2 -0
  25. package/dist/dashboard/public/assets/index-Ce6q21Fm.js +743 -0
  26. package/dist/dashboard/public/assets/{index-UjZq6ykz.css → index-NzpI0ItZ.css} +1 -1
  27. package/dist/dashboard/public/index.html +2 -2
  28. package/dist/dashboard/server.js +4274 -2320
  29. package/dist/{feedback-writer-LVZ5TFYZ.js → feedback-writer-VRMMWWTW.js} +2 -2
  30. package/dist/git-utils-I2UDKNZH.js +131 -0
  31. package/dist/git-utils-I2UDKNZH.js.map +1 -0
  32. package/dist/index.d.ts +12 -1
  33. package/dist/index.js +5 -3
  34. package/dist/index.js.map +1 -1
  35. package/dist/{projects-JEIVIYC6.js → projects-CFX3RTDL.js} +4 -2
  36. package/dist/{remote-workspace-AHVHQEES.js → remote-workspace-7FPGF2RM.js} +2 -2
  37. package/dist/{review-status-EPFG4XM7.js → review-status-TDPSOU5J.js} +2 -2
  38. package/dist/{specialist-context-T3NBMCIE.js → specialist-context-WGUUYDWY.js} +5 -5
  39. package/dist/{specialist-logs-CVKD3YJ3.js → specialist-logs-XJB5TCKJ.js} +5 -5
  40. package/dist/{specialists-TKAP6T6Z.js → specialists-5LBRHYFA.js} +5 -5
  41. package/dist/{traefik-QX4ZV4YG.js → traefik-WFMQX2LY.js} +3 -3
  42. package/dist/{workspace-manager-KLHUCIZV.js → workspace-manager-E434Z45T.js} +2 -2
  43. package/package.json +1 -1
  44. package/scripts/record-cost-event.js +5 -5
  45. package/scripts/stop-hook +7 -0
  46. package/scripts/work-agent-stop-hook +137 -0
  47. package/skills/myn-standards/SKILL.md +351 -0
  48. package/skills/pan-new-project/SKILL.md +304 -0
  49. package/skills/write-spec/SKILL.md +138 -0
  50. package/dist/chunk-7XNJJBH6.js +0 -538
  51. package/dist/chunk-7XNJJBH6.js.map +0 -1
  52. package/dist/chunk-ASY7T35E.js.map +0 -1
  53. package/dist/chunk-BKCWRMUX.js.map +0 -1
  54. package/dist/chunk-GFP3PIPB.js.map +0 -1
  55. package/dist/chunk-KJ2TRXNK.js.map +0 -1
  56. package/dist/chunk-OMNXYPXC.js.map +0 -1
  57. package/dist/chunk-PI7Y3PSN.js.map +0 -1
  58. package/dist/chunk-RBUO57TC.js.map +0 -1
  59. package/dist/chunk-XFR2DLMR.js.map +0 -1
  60. package/dist/dashboard/public/assets/index-kAJqtLDO.js +0 -708
  61. /package/dist/{agents-VLK4BMVA.js.map → agents-5OPQKM5K.js.map} +0 -0
  62. /package/dist/{chunk-XKT5MHPT.js.map → chunk-4YSYJ4HM.js.map} +0 -0
  63. /package/dist/{feedback-writer-LVZ5TFYZ.js.map → feedback-writer-VRMMWWTW.js.map} +0 -0
  64. /package/dist/{projects-JEIVIYC6.js.map → projects-CFX3RTDL.js.map} +0 -0
  65. /package/dist/{remote-workspace-AHVHQEES.js.map → remote-workspace-7FPGF2RM.js.map} +0 -0
  66. /package/dist/{review-status-EPFG4XM7.js.map → review-status-TDPSOU5J.js.map} +0 -0
  67. /package/dist/{specialist-context-T3NBMCIE.js.map → specialist-context-WGUUYDWY.js.map} +0 -0
  68. /package/dist/{specialist-logs-CVKD3YJ3.js.map → specialist-logs-XJB5TCKJ.js.map} +0 -0
  69. /package/dist/{specialists-TKAP6T6Z.js.map → specialists-5LBRHYFA.js.map} +0 -0
  70. /package/dist/{traefik-QX4ZV4YG.js.map → traefik-WFMQX2LY.js.map} +0 -0
  71. /package/dist/{workspace-manager-KLHUCIZV.js.map → workspace-manager-E434Z45T.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/settings.ts","../src/lib/providers.ts","../src/lib/model-capabilities.ts","../src/lib/config-yaml.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { SETTINGS_FILE } from './paths.js';\n\n// Model identifiers\nexport type AnthropicModel = 'claude-opus-4-6' | 'claude-sonnet-4-6' | 'claude-sonnet-4-5' | 'claude-haiku-4-5';\nexport type OpenAIModel = 'gpt-5.2-codex' | 'o3-deep-research' | 'gpt-4o' | 'gpt-4o-mini';\nexport type GoogleModel = 'gemini-3-pro-preview' | 'gemini-3-flash-preview' | 'gemini-2.5-pro' | 'gemini-2.5-flash';\nexport type ZAIModel = 'glm-4.7' | 'glm-4.7-flash';\nexport type KimiModel = 'kimi-k2' | 'kimi-k2.5';\nexport type ModelId = AnthropicModel | OpenAIModel | GoogleModel | ZAIModel | KimiModel;\n\n// Task complexity levels\nexport type ComplexityLevel = 'trivial' | 'simple' | 'medium' | 'complex' | 'expert';\n\n// Specialist agent types\nexport interface SpecialistModels {\n review_agent: ModelId;\n test_agent: ModelId;\n merge_agent: ModelId;\n}\n\n// Complexity-based model mapping\nexport type ComplexityModels = {\n [K in ComplexityLevel]: ModelId;\n};\n\n// All model configuration\nexport interface ModelsConfig {\n specialists: SpecialistModels;\n status_review: ModelId;\n complexity: ComplexityModels;\n}\n\n// API keys for external providers\nexport interface ApiKeysConfig {\n openai?: string;\n google?: string;\n zai?: string;\n kimi?: string;\n}\n\n// Complete settings structure\nexport interface SettingsConfig {\n models: ModelsConfig;\n api_keys: ApiKeysConfig;\n}\n\n// Default settings - match optimal defaults from settings-api.ts\nconst DEFAULT_SETTINGS: SettingsConfig = {\n models: {\n specialists: {\n review_agent: 'claude-opus-4-6',\n test_agent: 'claude-sonnet-4-6',\n merge_agent: 'claude-sonnet-4-6',\n },\n status_review: 'claude-opus-4-6',\n complexity: {\n trivial: 'claude-haiku-4-5',\n simple: 'claude-haiku-4-5',\n medium: 'kimi-k2.5',\n complex: 'kimi-k2.5',\n expert: 'claude-opus-4-6',\n },\n },\n api_keys: {},\n};\n\n/**\n * Deep merge utility that recursively merges objects.\n * - Recursively merges nested objects\n * - User values take precedence over defaults\n */\nfunction deepMerge<T extends object>(defaults: T, overrides: Partial<T>): T {\n const result = { ...defaults };\n\n for (const key of Object.keys(overrides) as (keyof T)[]) {\n const defaultVal = defaults[key];\n const overrideVal = overrides[key];\n\n // Skip undefined values in overrides\n if (overrideVal === undefined) continue;\n\n // Deep merge if both values are non-array objects\n if (\n typeof defaultVal === 'object' &&\n defaultVal !== null &&\n !Array.isArray(defaultVal) &&\n typeof overrideVal === 'object' &&\n overrideVal !== null &&\n !Array.isArray(overrideVal)\n ) {\n result[key] = deepMerge(defaultVal, overrideVal as any);\n } else {\n // For primitives or null - override wins\n result[key] = overrideVal as T[keyof T];\n }\n }\n\n return result;\n}\n\n/**\n * Load settings from ~/.panopticon/settings.json\n * Returns default settings if file doesn't exist or is invalid\n * Also loads API keys from environment variables as fallback\n */\nexport function loadSettings(): SettingsConfig {\n let settings: SettingsConfig;\n\n if (!existsSync(SETTINGS_FILE)) {\n settings = getDefaultSettings();\n } else {\n try {\n const content = readFileSync(SETTINGS_FILE, 'utf8');\n const parsed = JSON.parse(content) as Partial<SettingsConfig>;\n settings = deepMerge(DEFAULT_SETTINGS, parsed);\n } catch (error) {\n console.error('Warning: Failed to parse settings.json, using defaults');\n settings = getDefaultSettings();\n }\n }\n\n // Load API keys from environment variables as fallback\n // This allows using ~/.panopticon.env for API keys\n const envApiKeys: ApiKeysConfig = {};\n if (process.env.OPENAI_API_KEY) envApiKeys.openai = process.env.OPENAI_API_KEY;\n if (process.env.GOOGLE_API_KEY) envApiKeys.google = process.env.GOOGLE_API_KEY;\n if (process.env.ZAI_API_KEY) envApiKeys.zai = process.env.ZAI_API_KEY;\n if (process.env.KIMI_API_KEY) envApiKeys.kimi = process.env.KIMI_API_KEY;\n\n // Merge env vars as fallback (settings.json takes precedence)\n settings.api_keys = {\n ...envApiKeys,\n ...settings.api_keys,\n };\n\n return settings;\n}\n\n/**\n * Save settings to ~/.panopticon/settings.json\n * Writes with pretty formatting (2-space indent)\n */\nexport function saveSettings(settings: SettingsConfig): void {\n const content = JSON.stringify(settings, null, 2);\n writeFileSync(SETTINGS_FILE, content, 'utf8');\n}\n\n/**\n * Validate settings structure and model IDs\n * Returns error message if invalid, null if valid\n */\nexport function validateSettings(settings: SettingsConfig): string | null {\n // Validate models structure\n if (!settings.models) {\n return 'Missing models configuration';\n }\n\n // Validate specialists\n if (!settings.models.specialists) {\n return 'Missing specialists configuration';\n }\n const specialists = settings.models.specialists;\n if (!specialists.review_agent || !specialists.test_agent || !specialists.merge_agent) {\n return 'Missing specialist agent model configuration';\n }\n\n // Validate complexity levels\n if (!settings.models.complexity) {\n return 'Missing complexity configuration';\n }\n const complexity = settings.models.complexity;\n const requiredLevels: ComplexityLevel[] = ['trivial', 'simple', 'medium', 'complex', 'expert'];\n for (const level of requiredLevels) {\n if (!complexity[level]) {\n return `Missing complexity level: ${level}`;\n }\n }\n\n // Validate api_keys structure (optional keys)\n if (!settings.api_keys) {\n return 'Missing api_keys configuration';\n }\n\n return null;\n}\n\n/**\n * Get a deep copy of the default settings\n */\nexport function getDefaultSettings(): SettingsConfig {\n return JSON.parse(JSON.stringify(DEFAULT_SETTINGS));\n}\n\n/**\n * Get available models for a provider based on configured API keys\n * Returns empty array if provider API key is not configured\n */\nexport function getAvailableModels(settings: SettingsConfig): {\n anthropic: AnthropicModel[];\n openai: OpenAIModel[];\n google: GoogleModel[];\n zai: ZAIModel[];\n kimi: KimiModel[];\n} {\n const anthropicModels: AnthropicModel[] = [\n 'claude-opus-4-6',\n 'claude-sonnet-4-6',\n 'claude-haiku-4-5',\n ];\n\n const openaiModels: OpenAIModel[] = settings.api_keys.openai\n ? ['gpt-5.2-codex', 'o3-deep-research', 'gpt-4o', 'gpt-4o-mini']\n : [];\n\n const googleModels: GoogleModel[] = settings.api_keys.google\n ? ['gemini-3-pro-preview', 'gemini-3-flash-preview']\n : [];\n\n const zaiModels: ZAIModel[] = settings.api_keys.zai\n ? ['glm-4.7', 'glm-4.7-flash']\n : [];\n\n const kimiModels: KimiModel[] = settings.api_keys.kimi\n ? ['kimi-k2', 'kimi-k2.5']\n : [];\n\n return {\n anthropic: anthropicModels,\n openai: openaiModels,\n google: googleModels,\n zai: zaiModels,\n kimi: kimiModels,\n };\n}\n\n/**\n * Check if a model ID is an Anthropic model\n * Anthropic models can be run directly with `claude` CLI\n */\nexport function isAnthropicModel(modelId: ModelId | string): boolean {\n return modelId.startsWith('claude-');\n}\n\n/**\n * Get the Claude CLI model flag for an Anthropic model\n * Maps our model IDs to Claude's expected format\n */\nexport function getClaudeModelFlag(modelId: ModelId | string): string {\n const modelMap: Record<string, string> = {\n 'claude-opus-4-6': 'opus',\n 'claude-sonnet-4-6': 'sonnet',\n 'claude-sonnet-4-5': 'sonnet',\n 'claude-haiku-4-5': 'haiku',\n };\n return modelMap[modelId] || 'sonnet';\n}\n\n/**\n * Get the command to run an agent with a specific model\n * Returns 'claude' for Anthropic models, 'claude-code-router' for others\n */\nexport function getAgentCommand(modelId: ModelId | string): { command: string; args: string[] } {\n if (isAnthropicModel(modelId)) {\n return {\n command: 'claude',\n args: ['--model', getClaudeModelFlag(modelId)],\n };\n }\n // Non-Anthropic models require the router\n return {\n command: 'claude-code-router',\n args: [],\n };\n}\n","/**\r\n * Provider Configuration and Compatibility\r\n *\r\n * Defines which LLM providers are compatible with Claude Code's API format.\r\n * - Direct providers: Implement Anthropic-compatible API (no router needed)\r\n * - Router providers: Require claude-code-router for API translation\r\n */\r\n\r\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\r\nimport { join } from 'path';\r\nimport type { ModelId, AnthropicModel, OpenAIModel, GoogleModel, ZAIModel } from './settings.js';\r\n\r\nexport type ProviderName = 'anthropic' | 'kimi' | 'openai' | 'google' | 'zai';\r\n\r\n/**\r\n * Provider compatibility types\r\n * - direct: Anthropic-compatible API, use ANTHROPIC_BASE_URL directly\r\n * - router: Incompatible API, requires claude-code-router for translation\r\n */\r\nexport type ProviderCompatibility = 'direct' | 'router';\r\n\r\n/**\r\n * Provider configuration\r\n */\r\n/**\r\n * Auth type for direct providers:\r\n * - static: Use a long-lived API key passed via ANTHROPIC_AUTH_TOKEN (default)\r\n * - credential-file: Use apiKeyHelper to read a fresh token from a credential file.\r\n * Used for providers like Kimi Code Plan whose JWT tokens expire every ~15 minutes.\r\n */\r\nexport type ProviderAuthType = 'static' | 'credential-file';\r\n\r\nexport interface ProviderConfig {\r\n name: ProviderName;\r\n displayName: string;\r\n compatibility: ProviderCompatibility;\r\n baseUrl?: string; // For direct providers\r\n authType?: ProviderAuthType; // Defaults to 'static'\r\n credentialFile?: string; // Path to credential file (for 'credential-file' auth)\r\n credentialHelper?: string; // Script that reads credential file and prints token\r\n models: ModelId[];\r\n tested: boolean; // Whether compatibility has been verified\r\n description: string;\r\n}\r\n\r\n/**\r\n * All provider configurations\r\n */\r\nexport const PROVIDERS: Record<ProviderName, ProviderConfig> = {\r\n anthropic: {\r\n name: 'anthropic',\r\n displayName: 'Anthropic',\r\n compatibility: 'direct',\r\n models: ['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-sonnet-4-5', 'claude-haiku-4-5'],\r\n tested: true,\r\n description: 'Native Claude API',\r\n },\r\n\r\n kimi: {\r\n name: 'kimi',\r\n displayName: 'Kimi (Moonshot AI)',\r\n compatibility: 'direct',\r\n baseUrl: 'https://api.kimi.com/coding/',\r\n authType: 'credential-file',\r\n credentialFile: '~/.kimi/credentials/kimi-code.json',\r\n credentialHelper: '~/.panopticon/bin/kimi-token-helper.sh',\r\n models: [], // Kimi uses same model names as Anthropic\r\n tested: true,\r\n description: 'Anthropic-compatible API via Kimi Code Plan (OAuth token refresh)',\r\n },\r\n\r\n zai: {\r\n name: 'zai',\r\n displayName: 'Z.AI (GLM)',\r\n compatibility: 'direct',\r\n baseUrl: 'https://api.z.ai/api/anthropic',\r\n models: ['glm-4.7', 'glm-4.7-flash'],\r\n tested: true,\r\n description: 'Anthropic-compatible API, tested 2026-01-28',\r\n },\r\n\r\n openai: {\r\n name: 'openai',\r\n displayName: 'OpenAI',\r\n compatibility: 'router',\r\n models: ['gpt-5.2-codex', 'o3-deep-research', 'gpt-4o', 'gpt-4o-mini'],\r\n tested: false,\r\n description: 'Requires claude-code-router for API translation',\r\n },\r\n\r\n google: {\r\n name: 'google',\r\n displayName: 'Google (Gemini)',\r\n compatibility: 'router',\r\n models: ['gemini-3-pro-preview', 'gemini-3-flash-preview'],\r\n tested: false,\r\n description: 'Requires claude-code-router for API translation',\r\n },\r\n};\r\n\r\n/**\r\n * Get provider for a given model ID\r\n */\r\nexport function getProviderForModel(modelId: ModelId): ProviderConfig {\r\n // Check Anthropic models\r\n if (['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-sonnet-4-5', 'claude-haiku-4-5'].includes(modelId)) {\r\n return PROVIDERS.anthropic;\r\n }\r\n\r\n // Check OpenAI models\r\n if (['gpt-5.2-codex', 'o3-deep-research', 'gpt-4o', 'gpt-4o-mini'].includes(modelId)) {\r\n return PROVIDERS.openai;\r\n }\r\n\r\n // Check Google models\r\n if (['gemini-3-pro-preview', 'gemini-3-flash-preview'].includes(modelId)) {\r\n return PROVIDERS.google;\r\n }\r\n\r\n // Check Z.AI models\r\n if (['glm-4.7', 'glm-4.7-flash'].includes(modelId)) {\r\n return PROVIDERS.zai;\r\n }\r\n\r\n // Check Kimi models\r\n if (['kimi-k2', 'kimi-k2.5'].includes(modelId)) {\r\n return PROVIDERS.kimi;\r\n }\r\n\r\n // Default to Anthropic if unknown\r\n return PROVIDERS.anthropic;\r\n}\r\n\r\n/**\r\n * Check if a provider requires claude-code-router\r\n */\r\nexport function requiresRouter(provider: ProviderName): boolean {\r\n return PROVIDERS[provider].compatibility === 'router';\r\n}\r\n\r\n/**\r\n * Get all providers that require router (have router compatibility)\r\n */\r\nexport function getRouterProviders(): ProviderConfig[] {\r\n return Object.values(PROVIDERS).filter(p => p.compatibility === 'router');\r\n}\r\n\r\n/**\r\n * Get all direct-compatible providers\r\n */\r\nexport function getDirectProviders(): ProviderConfig[] {\r\n return Object.values(PROVIDERS).filter(p => p.compatibility === 'direct');\r\n}\r\n\r\n/**\r\n * Check if any configured providers require router\r\n * Used to determine if router installation is needed\r\n */\r\nexport function needsRouter(apiKeys: { openai?: string; google?: string; zai?: string }): boolean {\r\n return !!(apiKeys.openai || apiKeys.google);\r\n}\r\n\r\n/**\r\n * Get environment variables for spawning agent with specific provider\r\n */\r\nexport function getProviderEnv(\r\n provider: ProviderConfig,\r\n apiKey: string\r\n): Record<string, string> {\r\n if (provider.compatibility === 'direct') {\r\n // Direct providers use ANTHROPIC_BASE_URL\r\n const env: Record<string, string> = {};\r\n\r\n if (provider.baseUrl) {\r\n env.ANTHROPIC_BASE_URL = provider.baseUrl;\r\n }\r\n\r\n if (provider.name !== 'anthropic') {\r\n if (provider.authType === 'credential-file') {\r\n // Credential-file providers use apiKeyHelper for dynamic token refresh.\r\n // We still need an initial ANTHROPIC_AUTH_TOKEN for the first request,\r\n // but apiKeyHelper (configured via setupCredentialFileAuth) will keep it fresh.\r\n env.ANTHROPIC_AUTH_TOKEN = apiKey;\r\n // Refresh token every 60 seconds (kimi-cli refreshes credential file automatically)\r\n env.CLAUDE_CODE_API_KEY_HELPER_TTL_MS = '60000';\r\n } else {\r\n // Static providers use a long-lived API key\r\n env.ANTHROPIC_AUTH_TOKEN = apiKey;\r\n }\r\n }\r\n\r\n // Z.AI recommends longer timeout\r\n if (provider.name === 'zai') {\r\n env.API_TIMEOUT_MS = '300000';\r\n }\r\n\r\n return env;\r\n } else {\r\n // Router providers use local router proxy\r\n return {\r\n ANTHROPIC_BASE_URL: 'http://localhost:3456',\r\n ANTHROPIC_AUTH_TOKEN: 'router-managed',\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * For credential-file providers (e.g. Kimi Code Plan), configure Claude Code's\r\n * apiKeyHelper in the workspace settings so tokens are refreshed dynamically.\r\n *\r\n * This writes to .claude/settings.local.json in the workspace directory.\r\n * Must be called before spawning the agent.\r\n */\r\nexport function setupCredentialFileAuth(provider: ProviderConfig, workspacePath: string): void {\r\n if (provider.authType !== 'credential-file' || !provider.credentialHelper) return;\r\n\r\n const helperPath = provider.credentialHelper.replace('~', process.env.HOME || '');\r\n const claudeDir = join(workspacePath, '.claude');\r\n const settingsPath = join(claudeDir, 'settings.local.json');\r\n\r\n if (!existsSync(claudeDir)) {\r\n mkdirSync(claudeDir, { recursive: true });\r\n }\r\n\r\n // Read existing settings or start fresh\r\n let settings: Record<string, unknown> = {};\r\n if (existsSync(settingsPath)) {\r\n try {\r\n settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\r\n } catch { /* start fresh */ }\r\n }\r\n\r\n // Set the apiKeyHelper to our token reader script\r\n settings.apiKeyHelper = helperPath;\r\n\r\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\r\n}\r\n\r\n/**\r\n * Clear credential-file auth from workspace settings.\r\n *\r\n * When switching from a credential-file provider (e.g. Kimi) to a static/plan-based\r\n * provider (e.g. Anthropic), the apiKeyHelper must be removed from\r\n * .claude/settings.local.json. Otherwise Claude Code will keep using the stale\r\n * token helper and fail with \"Invalid API key\".\r\n */\r\nexport function clearCredentialFileAuth(workspacePath: string): void {\r\n const settingsPath = join(workspacePath, '.claude', 'settings.local.json');\r\n if (!existsSync(settingsPath)) return;\r\n\r\n try {\r\n const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\r\n if (!settings.apiKeyHelper) return; // Nothing to clear\r\n\r\n delete settings.apiKeyHelper;\r\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\r\n } catch { /* non-fatal */ }\r\n}\r\n","/**\n * Model Capability Matrix\n *\n * Defines capability scores for each model across different skill dimensions.\n * This enables intelligent model selection based on what the user has enabled\n * rather than static presets.\n *\n * Scores: 0-100 where 100 = best in class\n * Cost: $/1M tokens (input + output average)\n *\n * Last updated: 2026-01-29\n * Sources:\n * - SWE-bench Verified leaderboard (vals.ai)\n * - LiveCodeBench v6\n * - LMSYS Chatbot Arena\n * - Artificial Analysis\n * - Official provider pricing pages\n */\n\nimport { ModelId } from './settings.js';\n\n/**\n * Model ID deprecation mapping\n *\n * Maps deprecated model IDs to their current replacements.\n * When a model ID changes (e.g., claude-opus-4-5 → claude-opus-4-6),\n * add the mapping here to enable automatic migration.\n *\n * Strategy: Single-hop only. When a newer version arrives (e.g., 4-7),\n * add both old→new mappings (4-5→4-7 and 4-6→4-7).\n */\nexport const MODEL_DEPRECATIONS: Record<string, ModelId> = {\n 'claude-opus-4-5': 'claude-opus-4-6',\n 'claude-sonnet-4-5': 'claude-sonnet-4-6',\n};\n\n/**\n * Resolve a model ID to its current version\n *\n * If the model ID is deprecated, returns the replacement.\n * Otherwise, returns the model ID unchanged.\n *\n * @param modelId - Model ID to resolve (may be deprecated)\n * @returns Current model ID\n */\nexport function resolveModelId(modelId: string): ModelId {\n return (MODEL_DEPRECATIONS[modelId] as ModelId) || (modelId as ModelId);\n}\n\n/**\n * Skill dimensions that models are evaluated on\n */\nexport type SkillDimension =\n | 'code-generation' // Writing new code\n | 'code-review' // Finding issues in code\n | 'debugging' // Root cause analysis\n | 'planning' // Architecture and strategy\n | 'documentation' // Writing docs, PRDs\n | 'testing' // Test generation and analysis\n | 'security' // Security analysis\n | 'performance' // Performance optimization\n | 'synthesis' // Combining information\n | 'speed' // Response latency\n | 'context-length'; // Max context window\n\n/**\n * Capability profile for a single model\n */\nexport interface ModelCapability {\n /** Model identifier */\n model: ModelId;\n /** Provider for this model */\n provider: 'anthropic' | 'openai' | 'google' | 'zai' | 'kimi';\n /** Display name */\n displayName: string;\n /** Cost per 1M tokens (average of input/output) in USD */\n costPer1MTokens: number;\n /** Capability scores (0-100) for each skill dimension */\n skills: Record<SkillDimension, number>;\n /** Context window size in tokens */\n contextWindow: number;\n /** Additional notes about this model's strengths */\n notes?: string;\n}\n\n/**\n * Master capability database\n *\n * Scores are based on:\n * - Public benchmarks (HumanEval, SWE-bench, MBPP)\n * - Community consensus\n * - Practical experience\n *\n * These are baseline scores - run Kimi 2.5 research to refine.\n */\nexport const MODEL_CAPABILITIES: Record<ModelId, ModelCapability> = {\n // ═══════════════════════════════════════════════════════════════════════════\n // ANTHROPIC MODELS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'claude-opus-4-6': {\n model: 'claude-opus-4-6',\n provider: 'anthropic',\n displayName: 'Claude Opus 4.6',\n costPer1MTokens: 45.0, // $5 in / $25 out → same pricing as 4.5\n contextWindow: 200000, // 1M available via opt-in beta, but we use 200K\n skills: {\n 'code-generation': 96, // 80.9% SWE-bench (first >80%), 89.4% Aider Polyglot\n 'code-review': 98,\n debugging: 97,\n planning: 99, // User confirms: \"Opus 4.6 planning for sure\"\n documentation: 95,\n testing: 92,\n security: 98, // Best for security review\n performance: 90,\n synthesis: 98, // Best for combining info across domains\n speed: 40, // Slower but 76% more token efficient\n 'context-length': 95,\n },\n notes: 'Successor to Opus 4.5. Same pricing, 1M context available (opt-in beta). Best for planning, security, complex reasoning.',\n },\n\n 'claude-sonnet-4-6': {\n model: 'claude-sonnet-4-6',\n provider: 'anthropic',\n displayName: 'Claude Sonnet 4.6',\n costPer1MTokens: 9.0, // $3 in / $15 out → avg ~$9\n contextWindow: 200000,\n skills: {\n 'code-generation': 94,\n 'code-review': 94,\n debugging: 92,\n planning: 90,\n documentation: 92,\n testing: 92,\n security: 88,\n performance: 88,\n synthesis: 90,\n speed: 70,\n 'context-length': 95,\n },\n notes: 'Successor to Sonnet 4.5. Same pricing tier. Improved coding and reasoning.',\n },\n\n 'claude-sonnet-4-5': {\n model: 'claude-sonnet-4-5',\n provider: 'anthropic',\n displayName: 'Claude Sonnet 4.5',\n costPer1MTokens: 9.0, // $3 in / $15 out → avg ~$9\n contextWindow: 200000,\n skills: {\n 'code-generation': 92, // 77.2% SWE-bench (82% parallel), beats GPT-5 Codex (74.5%)\n 'code-review': 92,\n debugging: 90,\n planning: 88,\n documentation: 90, // 100% AIME with Python\n testing: 90, // 50% Terminal-Bench, 61.4% OSWorld\n security: 85,\n performance: 85,\n synthesis: 88,\n speed: 70,\n 'context-length': 95,\n },\n notes: 'Best value: 77.2% SWE-bench at 1/5th Opus cost. Beats GPT-5 Codex.',\n },\n\n 'claude-haiku-4-5': {\n model: 'claude-haiku-4-5',\n provider: 'anthropic',\n displayName: 'Claude Haiku 4.5',\n costPer1MTokens: 4.0, // $0.80 in / $4 out → avg ~$2.4\n contextWindow: 200000,\n skills: {\n 'code-generation': 75,\n 'code-review': 72,\n debugging: 70,\n planning: 65,\n documentation: 75,\n testing: 70,\n security: 60,\n performance: 65,\n synthesis: 68,\n speed: 95, // Fastest Anthropic\n 'context-length': 95,\n },\n notes: 'Fast and cheap, good for simple tasks and exploration',\n },\n\n // ═══════════════════════════════════════════════════════════════════════════\n // OPENAI MODELS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'gpt-5.2-codex': {\n model: 'gpt-5.2-codex',\n provider: 'openai',\n displayName: 'GPT-5.2 Codex',\n costPer1MTokens: 75.0, // Premium tier ~$75/M\n contextWindow: 128000,\n skills: {\n 'code-generation': 95, // 80% SWE-bench Verified, 55.6% SWE-bench Pro\n 'code-review': 90,\n debugging: 92, // 92.4% GPQA Diamond\n planning: 88,\n documentation: 85,\n testing: 90,\n security: 85,\n performance: 88, // 52.9% ARC-AGI-2 (best reasoning)\n synthesis: 88, // 100% AIME 2025 without tools\n speed: 55,\n 'context-length': 75,\n },\n notes: 'Premium coding: 80% SWE-bench. Best raw reasoning (52.9% ARC-AGI-2). Expensive.',\n },\n\n 'o3-deep-research': {\n model: 'o3-deep-research',\n provider: 'openai',\n displayName: 'O3 Deep Research',\n costPer1MTokens: 100.0, // Expensive reasoning model\n contextWindow: 200000,\n skills: {\n 'code-generation': 85,\n 'code-review': 95,\n debugging: 98, // Best for debugging\n planning: 95,\n documentation: 88,\n testing: 85,\n security: 92,\n performance: 92,\n synthesis: 95,\n speed: 20, // Very slow (reasoning chains)\n 'context-length': 95,\n },\n notes: 'Deep reasoning model, excellent for complex debugging and analysis',\n },\n\n 'gpt-4o': {\n model: 'gpt-4o',\n provider: 'openai',\n displayName: 'GPT-4o',\n costPer1MTokens: 15.0, // $5 in / $15 out\n contextWindow: 128000,\n skills: {\n 'code-generation': 88,\n 'code-review': 85,\n debugging: 85,\n planning: 82,\n documentation: 88,\n testing: 82,\n security: 78,\n performance: 80,\n synthesis: 85,\n speed: 75,\n 'context-length': 75,\n },\n notes: 'Good all-rounder, competitive with Sonnet',\n },\n\n 'gpt-4o-mini': {\n model: 'gpt-4o-mini',\n provider: 'openai',\n displayName: 'GPT-4o Mini',\n costPer1MTokens: 1.0, // Very cheap\n contextWindow: 128000,\n skills: {\n 'code-generation': 72,\n 'code-review': 68,\n debugging: 65,\n planning: 60,\n documentation: 70,\n testing: 65,\n security: 55,\n performance: 60,\n synthesis: 62,\n speed: 92,\n 'context-length': 75,\n },\n notes: 'Budget option, good for simple tasks',\n },\n\n // ═══════════════════════════════════════════════════════════════════════════\n // GOOGLE MODELS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'gemini-3-pro-preview': {\n model: 'gemini-3-pro-preview',\n provider: 'google',\n displayName: 'Gemini 3 Pro',\n costPer1MTokens: 12.0, // $4.2 in / $18.9 out\n contextWindow: 1000000, // 1M context!\n skills: {\n 'code-generation': 90, // 2439 Elo LiveCodeBench Pro (first >1500 on LMArena)\n 'code-review': 88,\n debugging: 85,\n planning: 85,\n documentation: 88,\n testing: 85, // ~95% AIME 2025\n security: 78,\n performance: 85, // Strong multimodal\n synthesis: 90, // Best for combining large codebases\n speed: 80,\n 'context-length': 100, // Best context - 1M tokens\n },\n notes: 'First to exceed 1500 Elo on LMArena. Best for large codebase analysis with 1M context.',\n },\n\n 'gemini-3-flash-preview': {\n model: 'gemini-3-flash-preview',\n provider: 'google',\n displayName: 'Gemini 3 Flash',\n costPer1MTokens: 0.5, // Very cheap\n contextWindow: 1000000,\n skills: {\n 'code-generation': 75,\n 'code-review': 70,\n debugging: 68,\n planning: 62,\n documentation: 72,\n testing: 68,\n security: 55,\n performance: 65,\n synthesis: 70,\n speed: 98, // Fastest overall\n 'context-length': 100,\n },\n notes: 'Extremely fast and cheap, huge context, great for exploration',\n },\n\n 'gemini-2.5-pro': {\n model: 'gemini-2.5-pro',\n provider: 'google',\n displayName: 'Gemini 2.5 Pro',\n costPer1MTokens: 12.0,\n contextWindow: 1000000,\n skills: {\n 'code-generation': 92,\n 'code-review': 90,\n debugging: 88,\n planning: 88,\n documentation: 90,\n testing: 87,\n security: 82,\n performance: 88,\n synthesis: 92,\n speed: 75,\n 'context-length': 100,\n },\n notes: 'Advanced reasoning and code capabilities with 1M context',\n },\n\n 'gemini-2.5-flash': {\n model: 'gemini-2.5-flash',\n provider: 'google',\n displayName: 'Gemini 2.5 Flash',\n costPer1MTokens: 0.6,\n contextWindow: 1000000,\n skills: {\n 'code-generation': 78,\n 'code-review': 73,\n debugging: 70,\n planning: 65,\n documentation: 75,\n testing: 70,\n security: 58,\n performance: 68,\n synthesis: 73,\n speed: 95,\n 'context-length': 100,\n },\n notes: 'Fast and efficient with large context support',\n },\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Z.AI MODELS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'glm-4.7': {\n model: 'glm-4.7',\n provider: 'zai',\n displayName: 'GLM 4.7',\n costPer1MTokens: 5.0,\n contextWindow: 200000, // 200K context, 128K output\n skills: {\n 'code-generation': 88, // 73.8% SWE-bench, 84.9 LiveCodeBench v6 (open-source SOTA)\n 'code-review': 85,\n debugging: 85, // Strong debugging with Interleaved Thinking\n planning: 82, // 95.7% AIME 2025 (beats Gemini 3 & GPT-5.1)\n documentation: 80,\n testing: 82, // 87.4 τ²-Bench (SOTA for tool use)\n security: 72,\n performance: 78,\n synthesis: 85, // Preserved Thinking retains context across turns\n speed: 80,\n 'context-length': 95, // 200K context\n },\n notes: 'Top open-source for agentic coding. 73.8% SWE-bench, best tool use. 400B params with Interleaved Thinking.',\n },\n\n 'glm-4.7-flash': {\n model: 'glm-4.7-flash',\n provider: 'zai',\n displayName: 'GLM 4.7 Flash',\n costPer1MTokens: 1.5,\n contextWindow: 128000,\n skills: {\n 'code-generation': 72,\n 'code-review': 68,\n debugging: 65,\n planning: 62,\n documentation: 70,\n testing: 65,\n security: 55,\n performance: 62,\n synthesis: 65,\n speed: 92, // Fast inference\n 'context-length': 75,\n },\n notes: 'Fast and affordable. Good for quick iterations and exploration.',\n },\n\n // ═══════════════════════════════════════════════════════════════════════════\n // KIMI MODELS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'kimi-k2': {\n model: 'kimi-k2',\n provider: 'kimi',\n displayName: 'Kimi K2',\n costPer1MTokens: 1.4, // $0.16 in / $2.63 out → very cheap\n contextWindow: 131000,\n skills: {\n 'code-generation': 82, // 65.8% SWE-bench (beats GPT-4.1 at 54.6%)\n 'code-review': 80,\n debugging: 78,\n planning: 75,\n documentation: 80,\n testing: 75,\n security: 70,\n performance: 72,\n synthesis: 78,\n speed: 80,\n 'context-length': 75,\n },\n notes: 'Strong value: 65.8% SWE-bench at very low cost. Good for routine tasks.',\n },\n\n 'kimi-k2.5': {\n model: 'kimi-k2.5',\n provider: 'kimi',\n displayName: 'Kimi K2.5',\n costPer1MTokens: 8.0, // ~5.1x cheaper than GPT-5.2\n contextWindow: 256000,\n skills: {\n 'code-generation': 92, // 76.8% SWE-bench, 85 LiveCodeBench v6\n 'code-review': 90,\n debugging: 90, // Strong analytical capabilities\n planning: 88, // User confirms \"highly capable\"\n documentation: 88,\n testing: 88, // 92% coding accuracy\n security: 82,\n performance: 85,\n synthesis: 92, // Can coordinate 100 sub-agents, 1500 tool calls\n speed: 75, // MoE: 1T total params, 32B active\n 'context-length': 98, // 256K context\n },\n notes: 'Best open-source coding model. 5x cheaper than GPT-5.2. Excellent for frontend dev and multi-agent orchestration.',\n },\n};\n\n/**\n * Get capability profile for a model\n */\nexport function getModelCapability(model: ModelId): ModelCapability {\n return MODEL_CAPABILITIES[model];\n}\n\n/**\n * Get all models sorted by a specific skill (descending)\n */\nexport function getModelsBySkill(skill: SkillDimension): ModelId[] {\n return (Object.keys(MODEL_CAPABILITIES) as ModelId[]).sort(\n (a, b) => MODEL_CAPABILITIES[b].skills[skill] - MODEL_CAPABILITIES[a].skills[skill]\n );\n}\n\n/**\n * Get all models for a provider\n */\nexport function getModelsForProvider(\n provider: ModelCapability['provider']\n): ModelId[] {\n return (Object.keys(MODEL_CAPABILITIES) as ModelId[]).filter(\n (model) => MODEL_CAPABILITIES[model].provider === provider\n );\n}\n\n/**\n * Get cheapest models (sorted by cost ascending)\n */\nexport function getCheapestModels(): ModelId[] {\n return (Object.keys(MODEL_CAPABILITIES) as ModelId[]).sort(\n (a, b) => MODEL_CAPABILITIES[a].costPer1MTokens - MODEL_CAPABILITIES[b].costPer1MTokens\n );\n}\n\n/**\n * Calculate cost efficiency score for a skill\n * Higher = better value (skill score / cost)\n */\nexport function getValueScore(model: ModelId, skill: SkillDimension): number {\n const cap = MODEL_CAPABILITIES[model];\n return cap.skills[skill] / Math.log10(cap.costPer1MTokens + 1);\n}\n\n/**\n * Get all skill dimensions\n */\nexport function getAllSkillDimensions(): SkillDimension[] {\n return [\n 'code-generation',\n 'code-review',\n 'debugging',\n 'planning',\n 'documentation',\n 'testing',\n 'security',\n 'performance',\n 'synthesis',\n 'speed',\n 'context-length',\n ];\n}\n","/**\n * YAML Configuration Loader\n *\n * Loads and merges configuration from:\n * 1. Global config: ~/.panopticon/config.yaml\n * 2. Per-project config: .panopticon.yaml (project root)\n *\n * Uses smart (capability-based) model selection - no legacy presets.\n */\n\nimport { readFileSync, existsSync, writeFileSync, copyFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport yaml from 'js-yaml';\nimport { WorkTypeId } from './work-types.js';\nimport { ModelId } from './settings.js';\nimport { ModelProvider } from './model-fallback.js';\nimport { MODEL_DEPRECATIONS, resolveModelId } from './model-capabilities.js';\n\n/**\n * Provider configuration (enable/disable + API keys)\n */\nexport interface ProviderConfig {\n /** Whether this provider is enabled */\n enabled: boolean;\n /** API key (optional, can use env var) */\n api_key?: string;\n}\n\n/**\n * Shadow mode configuration\n */\nexport interface ShadowConfig {\n /** Global shadow mode default */\n enabled?: boolean;\n\n /** Per-tracker overrides */\n trackers?: {\n linear?: boolean;\n github?: boolean;\n gitlab?: boolean;\n rally?: boolean;\n };\n}\n\n/**\n * Complete configuration structure (YAML schema)\n */\nexport interface YamlConfig {\n /** Model configuration */\n models?: {\n /** Provider enable/disable and API keys */\n providers?: {\n anthropic?: ProviderConfig | boolean;\n openai?: ProviderConfig | boolean;\n google?: ProviderConfig | boolean;\n zai?: ProviderConfig | boolean;\n kimi?: ProviderConfig | boolean;\n };\n\n /** Per-work-type overrides (explicit model for specific tasks) */\n overrides?: Partial<Record<WorkTypeId, ModelId>>;\n\n /** Gemini thinking level (1-4) */\n gemini_thinking_level?: 1 | 2 | 3 | 4;\n };\n\n /** Legacy API keys (for backward compatibility) */\n api_keys?: {\n openai?: string;\n google?: string;\n zai?: string;\n kimi?: string;\n };\n\n /** Tracker API keys (override environment variables) */\n tracker_keys?: {\n linear?: string;\n github?: string;\n gitlab?: string;\n rally?: string;\n };\n\n /** Shadow mode configuration */\n shadow?: ShadowConfig;\n}\n\n/**\n * Normalized shadow configuration\n */\nexport interface NormalizedShadowConfig {\n /** Global shadow mode enabled */\n enabled: boolean;\n\n /** Per-tracker overrides */\n trackers: {\n linear: boolean;\n github: boolean;\n gitlab: boolean;\n rally: boolean;\n };\n}\n\n/**\n * Normalized configuration (after loading and merging)\n */\nexport interface NormalizedConfig {\n /** Enabled providers */\n enabledProviders: Set<ModelProvider>;\n\n /** API keys by provider */\n apiKeys: {\n openai?: string;\n google?: string;\n zai?: string;\n kimi?: string;\n };\n\n /** Per-work-type overrides */\n overrides: Partial<Record<WorkTypeId, ModelId>>;\n\n /** Gemini thinking level */\n geminiThinkingLevel: 1 | 2 | 3 | 4;\n\n /** Tracker API keys */\n trackerKeys: {\n linear?: string;\n github?: string;\n gitlab?: string;\n rally?: string;\n };\n\n /** Shadow mode configuration */\n shadow: NormalizedShadowConfig;\n}\n\n/**\n * Model ID migration result\n *\n * Returned when deprecated model IDs are automatically migrated\n * during config load.\n */\nexport interface MigrationResult {\n /** List of migrated model IDs */\n migrated: Array<{\n /** Work type that was migrated */\n workType: WorkTypeId;\n /** Old (deprecated) model ID */\n from: string;\n /** New (current) model ID */\n to: string;\n }>;\n /** Whether config.yaml was backed up before migration */\n backedUp: boolean;\n}\n\n/**\n * Config load result (config + optional migration info)\n */\nexport interface ConfigLoadResult {\n /** Normalized configuration */\n config: NormalizedConfig;\n /** Migration result (if any deprecated models were migrated) */\n migration?: MigrationResult;\n}\n\n/**\n * Default configuration (used when no config files exist)\n */\nconst DEFAULT_CONFIG: NormalizedConfig = {\n enabledProviders: new Set(['anthropic']), // Only Anthropic by default\n apiKeys: {},\n overrides: {},\n geminiThinkingLevel: 3,\n trackerKeys: {},\n shadow: {\n enabled: false,\n trackers: {\n linear: false,\n github: false,\n gitlab: false,\n rally: false,\n },\n },\n};\n\n/**\n * Path to global config file\n */\nconst GLOBAL_CONFIG_PATH = join(homedir(), '.panopticon', 'config.yaml');\n\n/**\n * Normalize a provider config (handle both boolean and object forms)\n */\nfunction normalizeProviderConfig(\n providerConfig: ProviderConfig | boolean | undefined,\n fallbackKey?: string\n): { enabled: boolean; api_key?: string } {\n if (providerConfig === undefined) {\n return { enabled: false };\n }\n\n if (typeof providerConfig === 'boolean') {\n return { enabled: providerConfig, api_key: fallbackKey };\n }\n\n return {\n enabled: providerConfig.enabled,\n api_key: providerConfig.api_key || fallbackKey,\n };\n}\n\n/**\n * Resolve environment variables in config values.\n * If the env var is not set, returns the original reference (e.g., \"$OPENAI_API_KEY\")\n * so the UI can show that it's configured via env var but not resolved.\n */\nfunction resolveEnvVar(value: string | undefined): string | undefined {\n if (!value) return undefined;\n\n // Replace $VAR_NAME or ${VAR_NAME} with environment variable\n // If env var is not set, keep the original reference\n return value.replace(/\\$\\{?([A-Z_][A-Z0-9_]*)\\}?/g, (match, varName) => {\n const envValue = process.env[varName];\n return envValue !== undefined ? envValue : match; // Keep $VAR_NAME if not set\n });\n}\n\n/**\n * Load and parse a YAML config file\n */\nfunction loadYamlFile(filePath: string): YamlConfig | null {\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n const parsed = yaml.load(content) as YamlConfig;\n return parsed || {};\n } catch (error) {\n console.error(`Error loading YAML config from ${filePath}:`, error);\n return null;\n }\n}\n\n/**\n * Find project root by looking for .git directory\n */\nfunction findProjectRoot(startDir: string = process.cwd()): string | null {\n let currentDir = startDir;\n\n while (currentDir !== '/') {\n if (existsSync(join(currentDir, '.git'))) {\n return currentDir;\n }\n currentDir = join(currentDir, '..');\n }\n\n return null;\n}\n\n/**\n * Load per-project config (.panopticon.yaml in project root)\n */\nfunction loadProjectConfig(): YamlConfig | null {\n const projectRoot = findProjectRoot();\n if (!projectRoot) {\n return null;\n }\n\n const projectConfigPath = join(projectRoot, '.panopticon.yaml');\n return loadYamlFile(projectConfigPath);\n}\n\n/**\n * Load global config (~/.panopticon/config.yaml)\n */\nfunction loadGlobalConfig(): YamlConfig | null {\n return loadYamlFile(GLOBAL_CONFIG_PATH);\n}\n\n/**\n * Merge shadow configuration from multiple sources\n */\nfunction mergeShadowConfig(\n result: NormalizedShadowConfig,\n config: YamlConfig | null\n): void {\n if (!config?.shadow) return;\n\n // Merge global enabled flag\n if (config.shadow.enabled !== undefined) {\n result.enabled = config.shadow.enabled;\n }\n\n // Merge per-tracker overrides\n if (config.shadow.trackers) {\n if (config.shadow.trackers.linear !== undefined) {\n result.trackers.linear = config.shadow.trackers.linear;\n }\n if (config.shadow.trackers.github !== undefined) {\n result.trackers.github = config.shadow.trackers.github;\n }\n if (config.shadow.trackers.gitlab !== undefined) {\n result.trackers.gitlab = config.shadow.trackers.gitlab;\n }\n if (config.shadow.trackers.rally !== undefined) {\n result.trackers.rally = config.shadow.trackers.rally;\n }\n }\n}\n\n/**\n * Merge multiple configs with precedence: project > global > defaults\n */\nfunction mergeConfigs(...configs: (YamlConfig | null)[]): NormalizedConfig {\n const result: NormalizedConfig = {\n ...DEFAULT_CONFIG,\n enabledProviders: new Set(DEFAULT_CONFIG.enabledProviders),\n shadow: {\n enabled: DEFAULT_CONFIG.shadow.enabled,\n trackers: { ...DEFAULT_CONFIG.shadow.trackers },\n },\n };\n\n // Filter out null configs\n const validConfigs = configs.filter((c): c is YamlConfig => c !== null);\n\n // Merge in reverse order (lowest precedence first)\n for (const config of validConfigs.reverse()) {\n // Merge providers\n if (config.models?.providers) {\n const providers = config.models.providers;\n const legacyKeys = config.api_keys || {};\n\n // Anthropic (always enabled)\n result.enabledProviders.add('anthropic');\n\n // OpenAI\n const openai = normalizeProviderConfig(providers.openai, legacyKeys.openai);\n if (openai.enabled) {\n result.enabledProviders.add('openai');\n if (openai.api_key) {\n result.apiKeys.openai = resolveEnvVar(openai.api_key);\n }\n }\n\n // Google\n const google = normalizeProviderConfig(providers.google, legacyKeys.google);\n if (google.enabled) {\n result.enabledProviders.add('google');\n if (google.api_key) {\n result.apiKeys.google = resolveEnvVar(google.api_key);\n }\n }\n\n // Z.AI\n const zai = normalizeProviderConfig(providers.zai, legacyKeys.zai);\n if (zai.enabled) {\n result.enabledProviders.add('zai');\n if (zai.api_key) {\n result.apiKeys.zai = resolveEnvVar(zai.api_key);\n }\n }\n\n // Kimi\n const kimi = normalizeProviderConfig(providers.kimi, legacyKeys.kimi);\n if (kimi.enabled) {\n result.enabledProviders.add('kimi');\n if (kimi.api_key) {\n result.apiKeys.kimi = resolveEnvVar(kimi.api_key);\n }\n }\n }\n\n // Merge legacy API keys (for backward compatibility)\n if (config.api_keys) {\n if (config.api_keys.openai) {\n result.apiKeys.openai = resolveEnvVar(config.api_keys.openai);\n result.enabledProviders.add('openai');\n }\n if (config.api_keys.google) {\n result.apiKeys.google = resolveEnvVar(config.api_keys.google);\n result.enabledProviders.add('google');\n }\n if (config.api_keys.zai) {\n result.apiKeys.zai = resolveEnvVar(config.api_keys.zai);\n result.enabledProviders.add('zai');\n }\n if (config.api_keys.kimi) {\n result.apiKeys.kimi = resolveEnvVar(config.api_keys.kimi);\n result.enabledProviders.add('kimi');\n }\n }\n\n // Merge overrides\n if (config.models?.overrides) {\n result.overrides = {\n ...result.overrides,\n ...config.models.overrides,\n };\n }\n\n // Merge Gemini thinking level\n if (config.models?.gemini_thinking_level) {\n result.geminiThinkingLevel = config.models.gemini_thinking_level;\n }\n\n // Merge tracker keys\n if (config.tracker_keys) {\n if (config.tracker_keys.linear) {\n result.trackerKeys.linear = resolveEnvVar(config.tracker_keys.linear);\n }\n if (config.tracker_keys.github) {\n result.trackerKeys.github = resolveEnvVar(config.tracker_keys.github);\n }\n if (config.tracker_keys.gitlab) {\n result.trackerKeys.gitlab = resolveEnvVar(config.tracker_keys.gitlab);\n }\n if (config.tracker_keys.rally) {\n result.trackerKeys.rally = resolveEnvVar(config.tracker_keys.rally);\n }\n }\n\n // Merge shadow configuration\n mergeShadowConfig(result.shadow, config);\n }\n\n return result;\n}\n\n/**\n * Detect deprecated model IDs in config overrides\n *\n * Returns array of migrations to perform, or empty array if none found.\n */\nfunction detectDeprecatedModels(config: YamlConfig | null): Array<{\n workType: WorkTypeId;\n from: string;\n to: string;\n}> {\n if (!config?.models?.overrides) {\n return [];\n }\n\n const migrations: Array<{ workType: WorkTypeId; from: string; to: string }> = [];\n\n for (const [workType, modelId] of Object.entries(config.models.overrides)) {\n if (modelId && MODEL_DEPRECATIONS[modelId]) {\n migrations.push({\n workType: workType as WorkTypeId,\n from: modelId,\n to: MODEL_DEPRECATIONS[modelId],\n });\n }\n }\n\n return migrations;\n}\n\n/**\n * Apply deprecation migrations to a YamlConfig (in-place)\n */\nfunction applyMigrations(\n config: YamlConfig,\n migrations: Array<{ workType: WorkTypeId; from: string; to: string }>\n): void {\n if (!config.models) {\n config.models = {};\n }\n if (!config.models.overrides) {\n config.models.overrides = {};\n }\n\n for (const { workType, to } of migrations) {\n config.models.overrides[workType] = to as ModelId;\n }\n}\n\n/**\n * Create backup of global config file\n */\nfunction backupGlobalConfig(): boolean {\n try {\n const backupPath = `${GLOBAL_CONFIG_PATH}.bak`;\n copyFileSync(GLOBAL_CONFIG_PATH, backupPath);\n console.log(`✓ Backed up config.yaml → config.yaml.bak`);\n return true;\n } catch (error) {\n console.error(`Failed to create config backup:`, error);\n return false;\n }\n}\n\n/**\n * Write YamlConfig back to global config file\n */\nfunction writeGlobalConfig(config: YamlConfig): void {\n const yamlContent = yaml.dump(config, {\n indent: 2,\n lineWidth: 100,\n noRefs: true,\n });\n\n writeFileSync(GLOBAL_CONFIG_PATH, yamlContent, 'utf-8');\n}\n\n/**\n * Load complete configuration (global + project + defaults)\n * Also loads API keys from environment variables as fallback\n *\n * IMPORTANT: This function may modify config.yaml if deprecated model IDs\n * are detected. A backup is created before any modifications.\n */\nexport function loadConfig(): ConfigLoadResult {\n let globalConfig = loadGlobalConfig();\n const projectConfig = loadProjectConfig();\n\n // Check for deprecated models in global config\n let migrationResult: MigrationResult | undefined;\n if (globalConfig && hasGlobalConfig()) {\n const migrations = detectDeprecatedModels(globalConfig);\n\n if (migrations.length > 0) {\n // Create backup\n const backedUp = backupGlobalConfig();\n\n // Apply migrations to global config\n applyMigrations(globalConfig, migrations);\n\n // Write migrated config back to disk\n writeGlobalConfig(globalConfig);\n\n // Log migrations\n console.log('\\n🔄 Model ID Migration:');\n for (const { workType, from, to } of migrations) {\n console.log(` ${workType}: ${from} → ${to}`);\n }\n console.log('');\n\n migrationResult = { migrated: migrations, backedUp };\n }\n }\n\n const config = mergeConfigs(projectConfig, globalConfig);\n\n // Load API keys from environment variables as fallback\n // This allows using ~/.panopticon.env for API keys\n if (process.env.OPENAI_API_KEY && !config.apiKeys.openai) {\n config.apiKeys.openai = process.env.OPENAI_API_KEY;\n config.enabledProviders.add('openai');\n }\n if (process.env.GOOGLE_API_KEY && !config.apiKeys.google) {\n config.apiKeys.google = process.env.GOOGLE_API_KEY;\n config.enabledProviders.add('google');\n }\n if (process.env.ZAI_API_KEY && !config.apiKeys.zai) {\n config.apiKeys.zai = process.env.ZAI_API_KEY;\n config.enabledProviders.add('zai');\n }\n if (process.env.KIMI_API_KEY && !config.apiKeys.kimi) {\n config.apiKeys.kimi = process.env.KIMI_API_KEY;\n config.enabledProviders.add('kimi');\n }\n\n // Load tracker API keys from environment variables as fallback\n if (process.env.LINEAR_API_KEY && !config.trackerKeys.linear) {\n config.trackerKeys.linear = process.env.LINEAR_API_KEY;\n }\n if (process.env.GITHUB_TOKEN && !config.trackerKeys.github) {\n config.trackerKeys.github = process.env.GITHUB_TOKEN;\n }\n if (process.env.GITLAB_TOKEN && !config.trackerKeys.gitlab) {\n config.trackerKeys.gitlab = process.env.GITLAB_TOKEN;\n }\n if (process.env.RALLY_API_KEY && !config.trackerKeys.rally) {\n config.trackerKeys.rally = process.env.RALLY_API_KEY;\n }\n\n // Load shadow mode from environment as fallback\n // Environment variable takes precedence over config file\n if (process.env.SHADOW_MODE !== undefined) {\n const envShadowMode = ['true', '1', 'yes'].includes(process.env.SHADOW_MODE.toLowerCase());\n config.shadow.enabled = envShadowMode;\n }\n\n return { config, migration: migrationResult };\n}\n\n/**\n * Check if a project-level config exists\n */\nexport function hasProjectConfig(): boolean {\n const projectRoot = findProjectRoot();\n if (!projectRoot) return false;\n return existsSync(join(projectRoot, '.panopticon.yaml'));\n}\n\n/**\n * Check if global config exists\n */\nexport function hasGlobalConfig(): boolean {\n return existsSync(GLOBAL_CONFIG_PATH);\n}\n\n/**\n * Get path to global config file\n */\nexport function getGlobalConfigPath(): string {\n return GLOBAL_CONFIG_PATH;\n}\n\n/**\n * Get path to project config file (null if not in a project)\n */\nexport function getProjectConfigPath(): string | null {\n const projectRoot = findProjectRoot();\n if (!projectRoot) return null;\n return join(projectRoot, '.panopticon.yaml');\n}\n"],"mappings":"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","names":["existsSync","readFileSync","writeFileSync","readFileSync","existsSync","writeFileSync","join"]}
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  init_projects,
7
7
  loadProjectsConfig
8
- } from "./chunk-OMNXYPXC.js";
8
+ } from "./chunk-2V4NF7J2.js";
9
9
  import {
10
10
  SOURCE_TRAEFIK_TEMPLATES,
11
11
  TRAEFIK_CERTS_DIR,
@@ -30,7 +30,7 @@ function generatePanopticonTraefikConfig() {
30
30
  if (!existsSync(templatePath)) {
31
31
  return false;
32
32
  }
33
- const config = loadConfig();
33
+ const { config } = loadConfig();
34
34
  const placeholders = {
35
35
  TRAEFIK_DOMAIN: config.traefik?.domain || "pan.localhost",
36
36
  DASHBOARD_PORT: String(config.dashboard.port),
@@ -151,4 +151,4 @@ export {
151
151
  ensureProjectCerts,
152
152
  cleanupStaleTlsSections
153
153
  };
154
- //# sourceMappingURL=chunk-RBUO57TC.js.map
154
+ //# sourceMappingURL=chunk-NLQRED36.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/traefik.ts"],"sourcesContent":["/**\n * Traefik Configuration Generator\n *\n * Generates the Panopticon dashboard Traefik routing config\n * from a template, substituting values from config.toml.\n * Also generates TLS certificate configuration from discovered certs.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync, readdirSync } from 'fs';\nimport { join, basename } from 'path';\nimport { execSync } from 'child_process';\nimport { TRAEFIK_DYNAMIC_DIR, TRAEFIK_CERTS_DIR, TRAEFIK_DIR, SOURCE_TRAEFIK_TEMPLATES } from './paths.js';\nimport { loadConfig } from './config.js';\nimport { loadProjectsConfig } from './projects.js';\n\n/**\n * Generate panopticon.yml from template using current config values.\n * Safe to call multiple times (idempotent).\n * Returns true if file was written, false if template not found.\n */\nexport function generatePanopticonTraefikConfig(): boolean {\n const templatePath = join(SOURCE_TRAEFIK_TEMPLATES, 'dynamic', 'panopticon.yml.template');\n if (!existsSync(templatePath)) {\n return false;\n }\n\n const { config } = loadConfig();\n const placeholders: Record<string, string> = {\n TRAEFIK_DOMAIN: config.traefik?.domain || 'pan.localhost',\n DASHBOARD_PORT: String(config.dashboard.port),\n DASHBOARD_API_PORT: String(config.dashboard.api_port),\n };\n\n let content = readFileSync(templatePath, 'utf-8');\n for (const [key, value] of Object.entries(placeholders)) {\n content = content.replace(new RegExp(`\\\\{\\\\{${key}\\\\}\\\\}`, 'g'), value);\n }\n\n mkdirSync(TRAEFIK_DYNAMIC_DIR, { recursive: true });\n const outputPath = join(TRAEFIK_DYNAMIC_DIR, 'panopticon.yml');\n writeFileSync(outputPath, content, 'utf-8');\n return true;\n}\n\n/**\n * Remove any accidentally-copied .template files from the runtime Traefik dir.\n * Called after copyDirectoryRecursive in pan install.\n */\nexport function cleanupTemplateFiles(): void {\n const copiedTemplate = join(TRAEFIK_DYNAMIC_DIR, 'panopticon.yml.template');\n if (existsSync(copiedTemplate)) {\n unlinkSync(copiedTemplate);\n }\n}\n\n/**\n * Generate tls.yml from all discovered certificate files in the certs directory.\n *\n * Traefik v3 ignores `tls:` sections when they appear in the same dynamic config\n * file as `http:` routers/services. This function creates a dedicated tls.yml file\n * that Traefik's file provider will pick up separately.\n *\n * The first cert found (pan.localhost) is used as the default certificate.\n * All certs are listed in the certificates array for SNI matching.\n *\n * Safe to call multiple times (idempotent).\n * Returns true if file was written, false if no certs found.\n */\nexport function generateTlsConfig(): boolean {\n if (!existsSync(TRAEFIK_CERTS_DIR)) {\n return false;\n }\n\n // Scan for cert files (exclude -key.pem files)\n const files = readdirSync(TRAEFIK_CERTS_DIR);\n const certFiles = files.filter(f => f.endsWith('.pem') && !f.endsWith('-key.pem'));\n\n if (certFiles.length === 0) {\n return false;\n }\n\n // Pair each cert with its key file\n const certPairs: Array<{ certFile: string; keyFile: string }> = [];\n for (const certFile of certFiles) {\n const keyFile = certFile.replace('.pem', '-key.pem');\n if (files.includes(keyFile)) {\n certPairs.push({\n certFile: `/etc/traefik/certs/${certFile}`,\n keyFile: `/etc/traefik/certs/${keyFile}`,\n });\n }\n }\n\n if (certPairs.length === 0) {\n return false;\n }\n\n // Use the pan.localhost cert as default, fall back to first cert\n const defaultCert = certPairs.find(p => p.certFile.includes('pan.localhost')) || certPairs[0];\n\n // Build YAML content\n let yaml = '# Auto-generated TLS configuration — do not edit manually\\n';\n yaml += '# Generated by: pan up / pan install\\n';\n yaml += '# Traefik v3 requires TLS config in a separate dynamic config file\\n\\n';\n yaml += 'tls:\\n';\n yaml += ' stores:\\n';\n yaml += ' default:\\n';\n yaml += ' defaultCertificate:\\n';\n yaml += ` certFile: ${defaultCert.certFile}\\n`;\n yaml += ` keyFile: ${defaultCert.keyFile}\\n`;\n yaml += ' certificates:\\n';\n for (const pair of certPairs) {\n yaml += ` - certFile: ${pair.certFile}\\n`;\n yaml += ` keyFile: ${pair.keyFile}\\n`;\n }\n\n mkdirSync(TRAEFIK_DYNAMIC_DIR, { recursive: true });\n const outputPath = join(TRAEFIK_DYNAMIC_DIR, 'tls.yml');\n writeFileSync(outputPath, yaml, 'utf-8');\n return true;\n}\n\n/**\n * Ensure wildcard certificates exist for all registered projects that have DNS domains.\n *\n * Scans projects.yaml for projects with workspace.dns.domain, and generates\n * mkcert wildcard certs for any that don't already have certs in the Traefik\n * certs directory.\n *\n * Returns array of domains that had certs generated.\n */\nexport function ensureProjectCerts(): string[] {\n // Check mkcert is available\n try {\n execSync('which mkcert', { stdio: 'pipe' });\n } catch {\n return [];\n }\n\n const projectsConfig = loadProjectsConfig();\n const generated: string[] = [];\n\n for (const [, project] of Object.entries(projectsConfig.projects)) {\n const domain = project.workspace?.dns?.domain;\n if (!domain) continue;\n\n const certFile = join(TRAEFIK_CERTS_DIR, `_wildcard.${domain}.pem`);\n const keyFile = join(TRAEFIK_CERTS_DIR, `_wildcard.${domain}-key.pem`);\n\n if (existsSync(certFile) && existsSync(keyFile)) {\n continue;\n }\n\n // Generate cert for this project's domain\n mkdirSync(TRAEFIK_CERTS_DIR, { recursive: true });\n try {\n execSync(\n `mkcert -cert-file \"${certFile}\" -key-file \"${keyFile}\" \"${domain}\" \"*.${domain}\" 2>/dev/null`,\n { stdio: 'pipe' }\n );\n generated.push(domain);\n } catch {\n // mkcert failed — skip this domain\n }\n }\n\n return generated;\n}\n\n/**\n * Remove stale `tls:` sections from runtime config files.\n *\n * Traefik v3 ignores tls: in static config (traefik.yml) and in dynamic\n * config files that also contain http: routers. This function strips those\n * dead sections to avoid confusion.\n *\n * Called during `pan up` to clean up configs from older Panopticon versions.\n */\nexport function cleanupStaleTlsSections(): void {\n // Clean static config (traefik.yml)\n const staticConfig = join(TRAEFIK_DIR, 'traefik.yml');\n if (existsSync(staticConfig)) {\n const content = readFileSync(staticConfig, 'utf-8');\n // Remove tls: section at the end of the file\n const cleaned = content.replace(/\\n# TLS Configuration\\ntls:\\n(?: .*\\n)*/g, '\\n');\n if (cleaned !== content) {\n writeFileSync(staticConfig, cleaned, 'utf-8');\n }\n }\n\n // Clean dynamic panopticon.yml (regenerated from template, but also clean runtime copy)\n const dynamicConfig = join(TRAEFIK_DYNAMIC_DIR, 'panopticon.yml');\n if (existsSync(dynamicConfig)) {\n const content = readFileSync(dynamicConfig, 'utf-8');\n // Remove standalone tls: section (not nested under http: routers)\n const cleaned = content.replace(/\\ntls:\\n (?:stores|certificates):\\n(?: .*\\n)*/g, '\\n');\n if (cleaned !== content) {\n writeFileSync(dynamicConfig, cleaned, 'utf-8');\n }\n }\n}\n"],"mappings":"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","names":[]}