panopticon-cli 0.4.32 → 0.5.0

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 (142) hide show
  1. package/README.md +96 -210
  2. package/dist/{agents-BDFHF4T3.js → agents-E43Y3HNU.js} +10 -7
  3. package/dist/chunk-7SN4L4PH.js +150 -0
  4. package/dist/chunk-7SN4L4PH.js.map +1 -0
  5. package/dist/{chunk-2NIAOCIC.js → chunk-AAFQANKW.js} +358 -97
  6. package/dist/chunk-AAFQANKW.js.map +1 -0
  7. package/dist/chunk-AQXETQHW.js +113 -0
  8. package/dist/chunk-AQXETQHW.js.map +1 -0
  9. package/dist/chunk-B3PF6JPQ.js +212 -0
  10. package/dist/chunk-B3PF6JPQ.js.map +1 -0
  11. package/dist/chunk-CFCUOV3Q.js +669 -0
  12. package/dist/chunk-CFCUOV3Q.js.map +1 -0
  13. package/dist/chunk-CWELWPWQ.js +32 -0
  14. package/dist/chunk-CWELWPWQ.js.map +1 -0
  15. package/dist/chunk-DI7ABPNQ.js +352 -0
  16. package/dist/chunk-DI7ABPNQ.js.map +1 -0
  17. package/dist/{chunk-VU4FLXV5.js → chunk-FQ66DECN.js} +31 -4
  18. package/dist/chunk-FQ66DECN.js.map +1 -0
  19. package/dist/{chunk-VIWUCJ4V.js → chunk-FTCPTHIJ.js} +57 -432
  20. package/dist/chunk-FTCPTHIJ.js.map +1 -0
  21. package/dist/{review-status-GWQYY77L.js → chunk-GFP3PIPB.js} +14 -7
  22. package/dist/chunk-GFP3PIPB.js.map +1 -0
  23. package/dist/chunk-GR6ZZMCX.js +816 -0
  24. package/dist/chunk-GR6ZZMCX.js.map +1 -0
  25. package/dist/chunk-HJSM6E6U.js +1038 -0
  26. package/dist/chunk-HJSM6E6U.js.map +1 -0
  27. package/dist/{chunk-XP2DXWYP.js → chunk-HZT2AOPN.js} +164 -39
  28. package/dist/chunk-HZT2AOPN.js.map +1 -0
  29. package/dist/chunk-JQBV3Q2W.js +29 -0
  30. package/dist/chunk-JQBV3Q2W.js.map +1 -0
  31. package/dist/{chunk-BWGFN44T.js → chunk-JT4O4YVM.js} +28 -16
  32. package/dist/chunk-JT4O4YVM.js.map +1 -0
  33. package/dist/chunk-NTO3EDB3.js +600 -0
  34. package/dist/chunk-NTO3EDB3.js.map +1 -0
  35. package/dist/{chunk-JY7R7V4G.js → chunk-OMNXYPXC.js} +2 -2
  36. package/dist/chunk-OMNXYPXC.js.map +1 -0
  37. package/dist/chunk-PELXV435.js +215 -0
  38. package/dist/chunk-PELXV435.js.map +1 -0
  39. package/dist/chunk-PPRFKTVC.js +154 -0
  40. package/dist/chunk-PPRFKTVC.js.map +1 -0
  41. package/dist/chunk-WQG2TYCB.js +677 -0
  42. package/dist/chunk-WQG2TYCB.js.map +1 -0
  43. package/dist/{chunk-HCTJFIJJ.js → chunk-YLPSQAM2.js} +2 -2
  44. package/dist/{chunk-HCTJFIJJ.js.map → chunk-YLPSQAM2.js.map} +1 -1
  45. package/dist/{chunk-6HXKTOD7.js → chunk-ZTFNYOC7.js} +53 -38
  46. package/dist/chunk-ZTFNYOC7.js.map +1 -0
  47. package/dist/cli/index.js +5103 -3165
  48. package/dist/cli/index.js.map +1 -1
  49. package/dist/{config-BOAMSKTF.js → config-4CJNUE3O.js} +7 -3
  50. package/dist/dashboard/prompts/merge-agent.md +217 -0
  51. package/dist/dashboard/prompts/review-agent.md +409 -0
  52. package/dist/dashboard/prompts/sync-main.md +84 -0
  53. package/dist/dashboard/prompts/test-agent.md +283 -0
  54. package/dist/dashboard/prompts/work-agent.md +249 -0
  55. package/dist/dashboard/public/assets/index-BxpjweAL.css +32 -0
  56. package/dist/dashboard/public/assets/index-DQHkwvvJ.js +743 -0
  57. package/dist/dashboard/public/index.html +2 -2
  58. package/dist/dashboard/server.js +17619 -4044
  59. package/dist/{dns-L3L2BB27.js → dns-7BDJSD3E.js} +4 -2
  60. package/dist/{feedback-writer-AAKF5BTK.js → feedback-writer-LVZ5TFYZ.js} +8 -4
  61. package/dist/feedback-writer-LVZ5TFYZ.js.map +1 -0
  62. package/dist/hume-WMAUBBV2.js +13 -0
  63. package/dist/index.d.ts +162 -40
  64. package/dist/index.js +67 -23
  65. package/dist/index.js.map +1 -1
  66. package/dist/{projects-VXRUCMLM.js → projects-JEIVIYC6.js} +3 -3
  67. package/dist/rally-RKFSWC7E.js +10 -0
  68. package/dist/{remote-agents-Z3R2A5BN.js → remote-agents-TFSMW7GN.js} +2 -2
  69. package/dist/{remote-workspace-2G6V2KNP.js → remote-workspace-AHVHQEES.js} +8 -8
  70. package/dist/review-status-EPFG4XM7.js +19 -0
  71. package/dist/shadow-state-5MDP6YXH.js +30 -0
  72. package/dist/shadow-state-5MDP6YXH.js.map +1 -0
  73. package/dist/{specialist-context-N32QBNNQ.js → specialist-context-ZC6A4M3I.js} +8 -7
  74. package/dist/{specialist-context-N32QBNNQ.js.map → specialist-context-ZC6A4M3I.js.map} +1 -1
  75. package/dist/{specialist-logs-GF3YV4KL.js → specialist-logs-KLGJCEUL.js} +7 -6
  76. package/dist/specialist-logs-KLGJCEUL.js.map +1 -0
  77. package/dist/{specialists-JBIW6MP4.js → specialists-O4HWDJL5.js} +7 -6
  78. package/dist/specialists-O4HWDJL5.js.map +1 -0
  79. package/dist/tldr-daemon-T3THOUGT.js +21 -0
  80. package/dist/tldr-daemon-T3THOUGT.js.map +1 -0
  81. package/dist/traefik-QN7R5I6V.js +19 -0
  82. package/dist/traefik-QN7R5I6V.js.map +1 -0
  83. package/dist/tunnel-W2GZBLEV.js +13 -0
  84. package/dist/tunnel-W2GZBLEV.js.map +1 -0
  85. package/dist/workspace-manager-IE4JL2JP.js +22 -0
  86. package/dist/workspace-manager-IE4JL2JP.js.map +1 -0
  87. package/package.json +2 -2
  88. package/scripts/heartbeat-hook +37 -10
  89. package/scripts/patches/llm-tldr-tsx-support.py +109 -0
  90. package/scripts/pre-tool-hook +26 -15
  91. package/scripts/record-cost-event.js +177 -43
  92. package/scripts/record-cost-event.ts +87 -3
  93. package/scripts/statusline.sh +169 -0
  94. package/scripts/stop-hook +21 -11
  95. package/scripts/tldr-post-edit +72 -0
  96. package/scripts/tldr-read-enforcer +275 -0
  97. package/scripts/work-agent-stop-hook +137 -0
  98. package/skills/check-merged/SKILL.md +143 -0
  99. package/skills/crash-investigation/SKILL.md +301 -0
  100. package/skills/github-cli/SKILL.md +185 -0
  101. package/skills/myn-standards/SKILL.md +351 -0
  102. package/skills/pan-reopen/SKILL.md +65 -0
  103. package/skills/pan-sync-main/SKILL.md +87 -0
  104. package/skills/pan-tldr/SKILL.md +149 -0
  105. package/skills/react-best-practices/SKILL.md +125 -0
  106. package/skills/spec-readiness/REPORT-TEMPLATE.md +158 -0
  107. package/skills/spec-readiness/SCORING-REFERENCE.md +369 -0
  108. package/skills/spec-readiness/SKILL.md +400 -0
  109. package/skills/spec-readiness-setup/SKILL.md +361 -0
  110. package/skills/workspace-status/SKILL.md +56 -0
  111. package/skills/write-spec/SKILL.md +138 -0
  112. package/templates/traefik/dynamic/panopticon.yml.template +0 -5
  113. package/templates/traefik/traefik.yml +0 -8
  114. package/dist/chunk-2NIAOCIC.js.map +0 -1
  115. package/dist/chunk-3XAB4IXF.js +0 -51
  116. package/dist/chunk-3XAB4IXF.js.map +0 -1
  117. package/dist/chunk-6HXKTOD7.js.map +0 -1
  118. package/dist/chunk-BBCUK6N2.js +0 -241
  119. package/dist/chunk-BBCUK6N2.js.map +0 -1
  120. package/dist/chunk-BWGFN44T.js.map +0 -1
  121. package/dist/chunk-ELK6Q7QI.js +0 -545
  122. package/dist/chunk-ELK6Q7QI.js.map +0 -1
  123. package/dist/chunk-JY7R7V4G.js.map +0 -1
  124. package/dist/chunk-LYSBSZYV.js +0 -1523
  125. package/dist/chunk-LYSBSZYV.js.map +0 -1
  126. package/dist/chunk-VIWUCJ4V.js.map +0 -1
  127. package/dist/chunk-VU4FLXV5.js.map +0 -1
  128. package/dist/chunk-XP2DXWYP.js.map +0 -1
  129. package/dist/dashboard/public/assets/index-C7X6LP5Z.css +0 -32
  130. package/dist/dashboard/public/assets/index-ClYqpcAJ.js +0 -645
  131. package/dist/feedback-writer-AAKF5BTK.js.map +0 -1
  132. package/dist/review-status-GWQYY77L.js.map +0 -1
  133. package/dist/traefik-CUJM6K5Z.js +0 -12
  134. /package/dist/{agents-BDFHF4T3.js.map → agents-E43Y3HNU.js.map} +0 -0
  135. /package/dist/{config-BOAMSKTF.js.map → config-4CJNUE3O.js.map} +0 -0
  136. /package/dist/{dns-L3L2BB27.js.map → dns-7BDJSD3E.js.map} +0 -0
  137. /package/dist/{projects-VXRUCMLM.js.map → hume-WMAUBBV2.js.map} +0 -0
  138. /package/dist/{remote-agents-Z3R2A5BN.js.map → projects-JEIVIYC6.js.map} +0 -0
  139. /package/dist/{specialist-logs-GF3YV4KL.js.map → rally-RKFSWC7E.js.map} +0 -0
  140. /package/dist/{specialists-JBIW6MP4.js.map → remote-agents-TFSMW7GN.js.map} +0 -0
  141. /package/dist/{remote-workspace-2G6V2KNP.js.map → remote-workspace-AHVHQEES.js.map} +0 -0
  142. /package/dist/{traefik-CUJM6K5Z.js.map → review-status-EPFG4XM7.js.map} +0 -0
@@ -1,241 +0,0 @@
1
- import {
2
- __esm,
3
- init_esm_shims
4
- } from "./chunk-ZHC57RCV.js";
5
-
6
- // src/lib/config-yaml.ts
7
- import { readFileSync, existsSync } from "fs";
8
- import { join } from "path";
9
- import { homedir } from "os";
10
- import yaml from "js-yaml";
11
- function normalizeProviderConfig(providerConfig, fallbackKey) {
12
- if (providerConfig === void 0) {
13
- return { enabled: false };
14
- }
15
- if (typeof providerConfig === "boolean") {
16
- return { enabled: providerConfig, api_key: fallbackKey };
17
- }
18
- return {
19
- enabled: providerConfig.enabled,
20
- api_key: providerConfig.api_key || fallbackKey
21
- };
22
- }
23
- function resolveEnvVar(value) {
24
- if (!value) return void 0;
25
- return value.replace(/\$\{?([A-Z_][A-Z0-9_]*)\}?/g, (match, varName) => {
26
- const envValue = process.env[varName];
27
- return envValue !== void 0 ? envValue : match;
28
- });
29
- }
30
- function loadYamlFile(filePath) {
31
- if (!existsSync(filePath)) {
32
- return null;
33
- }
34
- try {
35
- const content = readFileSync(filePath, "utf-8");
36
- const parsed = yaml.load(content);
37
- return parsed || {};
38
- } catch (error) {
39
- console.error(`Error loading YAML config from ${filePath}:`, error);
40
- return null;
41
- }
42
- }
43
- function findProjectRoot(startDir = process.cwd()) {
44
- let currentDir = startDir;
45
- while (currentDir !== "/") {
46
- if (existsSync(join(currentDir, ".git"))) {
47
- return currentDir;
48
- }
49
- currentDir = join(currentDir, "..");
50
- }
51
- return null;
52
- }
53
- function loadProjectConfig() {
54
- const projectRoot = findProjectRoot();
55
- if (!projectRoot) {
56
- return null;
57
- }
58
- const projectConfigPath = join(projectRoot, ".panopticon.yaml");
59
- return loadYamlFile(projectConfigPath);
60
- }
61
- function loadGlobalConfig() {
62
- return loadYamlFile(GLOBAL_CONFIG_PATH);
63
- }
64
- function mergeShadowConfig(result, config) {
65
- if (!config?.shadow) return;
66
- if (config.shadow.enabled !== void 0) {
67
- result.enabled = config.shadow.enabled;
68
- }
69
- if (config.shadow.trackers) {
70
- if (config.shadow.trackers.linear !== void 0) {
71
- result.trackers.linear = config.shadow.trackers.linear;
72
- }
73
- if (config.shadow.trackers.github !== void 0) {
74
- result.trackers.github = config.shadow.trackers.github;
75
- }
76
- if (config.shadow.trackers.gitlab !== void 0) {
77
- result.trackers.gitlab = config.shadow.trackers.gitlab;
78
- }
79
- if (config.shadow.trackers.rally !== void 0) {
80
- result.trackers.rally = config.shadow.trackers.rally;
81
- }
82
- }
83
- }
84
- function mergeConfigs(...configs) {
85
- const result = {
86
- ...DEFAULT_CONFIG,
87
- enabledProviders: new Set(DEFAULT_CONFIG.enabledProviders),
88
- shadow: {
89
- enabled: DEFAULT_CONFIG.shadow.enabled,
90
- trackers: { ...DEFAULT_CONFIG.shadow.trackers }
91
- }
92
- };
93
- const validConfigs = configs.filter((c) => c !== null);
94
- for (const config of validConfigs.reverse()) {
95
- if (config.models?.providers) {
96
- const providers = config.models.providers;
97
- const legacyKeys = config.api_keys || {};
98
- result.enabledProviders.add("anthropic");
99
- const openai = normalizeProviderConfig(providers.openai, legacyKeys.openai);
100
- if (openai.enabled) {
101
- result.enabledProviders.add("openai");
102
- if (openai.api_key) {
103
- result.apiKeys.openai = resolveEnvVar(openai.api_key);
104
- }
105
- }
106
- const google = normalizeProviderConfig(providers.google, legacyKeys.google);
107
- if (google.enabled) {
108
- result.enabledProviders.add("google");
109
- if (google.api_key) {
110
- result.apiKeys.google = resolveEnvVar(google.api_key);
111
- }
112
- }
113
- const zai = normalizeProviderConfig(providers.zai, legacyKeys.zai);
114
- if (zai.enabled) {
115
- result.enabledProviders.add("zai");
116
- if (zai.api_key) {
117
- result.apiKeys.zai = resolveEnvVar(zai.api_key);
118
- }
119
- }
120
- const kimi = normalizeProviderConfig(providers.kimi, legacyKeys.kimi);
121
- if (kimi.enabled) {
122
- result.enabledProviders.add("kimi");
123
- if (kimi.api_key) {
124
- result.apiKeys.kimi = resolveEnvVar(kimi.api_key);
125
- }
126
- }
127
- }
128
- if (config.api_keys) {
129
- if (config.api_keys.openai) {
130
- result.apiKeys.openai = resolveEnvVar(config.api_keys.openai);
131
- result.enabledProviders.add("openai");
132
- }
133
- if (config.api_keys.google) {
134
- result.apiKeys.google = resolveEnvVar(config.api_keys.google);
135
- result.enabledProviders.add("google");
136
- }
137
- if (config.api_keys.zai) {
138
- result.apiKeys.zai = resolveEnvVar(config.api_keys.zai);
139
- result.enabledProviders.add("zai");
140
- }
141
- if (config.api_keys.kimi) {
142
- result.apiKeys.kimi = resolveEnvVar(config.api_keys.kimi);
143
- result.enabledProviders.add("kimi");
144
- }
145
- }
146
- if (config.models?.overrides) {
147
- result.overrides = {
148
- ...result.overrides,
149
- ...config.models.overrides
150
- };
151
- }
152
- if (config.models?.gemini_thinking_level) {
153
- result.geminiThinkingLevel = config.models.gemini_thinking_level;
154
- }
155
- if (config.tracker_keys) {
156
- if (config.tracker_keys.linear) {
157
- result.trackerKeys.linear = resolveEnvVar(config.tracker_keys.linear);
158
- }
159
- if (config.tracker_keys.github) {
160
- result.trackerKeys.github = resolveEnvVar(config.tracker_keys.github);
161
- }
162
- if (config.tracker_keys.gitlab) {
163
- result.trackerKeys.gitlab = resolveEnvVar(config.tracker_keys.gitlab);
164
- }
165
- if (config.tracker_keys.rally) {
166
- result.trackerKeys.rally = resolveEnvVar(config.tracker_keys.rally);
167
- }
168
- }
169
- mergeShadowConfig(result.shadow, config);
170
- }
171
- return result;
172
- }
173
- function loadConfig() {
174
- const globalConfig = loadGlobalConfig();
175
- const projectConfig = loadProjectConfig();
176
- const config = mergeConfigs(projectConfig, globalConfig);
177
- if (process.env.OPENAI_API_KEY && !config.apiKeys.openai) {
178
- config.apiKeys.openai = process.env.OPENAI_API_KEY;
179
- config.enabledProviders.add("openai");
180
- }
181
- if (process.env.GOOGLE_API_KEY && !config.apiKeys.google) {
182
- config.apiKeys.google = process.env.GOOGLE_API_KEY;
183
- config.enabledProviders.add("google");
184
- }
185
- if (process.env.ZAI_API_KEY && !config.apiKeys.zai) {
186
- config.apiKeys.zai = process.env.ZAI_API_KEY;
187
- config.enabledProviders.add("zai");
188
- }
189
- if (process.env.KIMI_API_KEY && !config.apiKeys.kimi) {
190
- config.apiKeys.kimi = process.env.KIMI_API_KEY;
191
- config.enabledProviders.add("kimi");
192
- }
193
- if (process.env.LINEAR_API_KEY && !config.trackerKeys.linear) {
194
- config.trackerKeys.linear = process.env.LINEAR_API_KEY;
195
- }
196
- if (process.env.GITHUB_TOKEN && !config.trackerKeys.github) {
197
- config.trackerKeys.github = process.env.GITHUB_TOKEN;
198
- }
199
- if (process.env.GITLAB_TOKEN && !config.trackerKeys.gitlab) {
200
- config.trackerKeys.gitlab = process.env.GITLAB_TOKEN;
201
- }
202
- if (process.env.RALLY_API_KEY && !config.trackerKeys.rally) {
203
- config.trackerKeys.rally = process.env.RALLY_API_KEY;
204
- }
205
- if (process.env.SHADOW_MODE !== void 0) {
206
- const envShadowMode = ["true", "1", "yes"].includes(process.env.SHADOW_MODE.toLowerCase());
207
- config.shadow.enabled = envShadowMode;
208
- }
209
- return config;
210
- }
211
- var DEFAULT_CONFIG, GLOBAL_CONFIG_PATH;
212
- var init_config_yaml = __esm({
213
- "src/lib/config-yaml.ts"() {
214
- "use strict";
215
- init_esm_shims();
216
- DEFAULT_CONFIG = {
217
- enabledProviders: /* @__PURE__ */ new Set(["anthropic"]),
218
- // Only Anthropic by default
219
- apiKeys: {},
220
- overrides: {},
221
- geminiThinkingLevel: 3,
222
- trackerKeys: {},
223
- shadow: {
224
- enabled: false,
225
- trackers: {
226
- linear: false,
227
- github: false,
228
- gitlab: false,
229
- rally: false
230
- }
231
- }
232
- };
233
- GLOBAL_CONFIG_PATH = join(homedir(), ".panopticon", "config.yaml");
234
- }
235
- });
236
-
237
- export {
238
- loadConfig,
239
- init_config_yaml
240
- };
241
- //# sourceMappingURL=chunk-BBCUK6N2.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/config-yaml.ts"],"sourcesContent":["/**\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 } 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';\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 * 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 * Load complete configuration (global + project + defaults)\n * Also loads API keys from environment variables as fallback\n */\nexport function loadConfig(): NormalizedConfig {\n const globalConfig = loadGlobalConfig();\n const projectConfig = loadProjectConfig();\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;\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":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/dns.ts","../src/lib/platform.ts"],"sourcesContent":["/**\n * DNS Management\n *\n * Centralized DNS entry management for Panopticon.\n * Supports three sync methods:\n * - wsl2hosts: WSL2 → Windows hosts file sync via PowerShell scheduled task\n * - hosts_file: Direct /etc/hosts manipulation with managed block markers\n * - dnsmasq: System-wide dnsmasq configuration (Linux/macOS)\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { exec, execSync } from 'child_process';\nimport { promisify } from 'util';\nimport { detectPlatform } from './platform.js';\n\nconst execAsync = promisify(exec);\n\nexport type DnsSyncMethod = 'wsl2hosts' | 'hosts_file' | 'dnsmasq';\n\n// ---- Detection ----\n\n/**\n * Detect the best DNS sync method for the current platform.\n */\nexport function detectDnsSyncMethod(): DnsSyncMethod {\n const plat = detectPlatform();\n switch (plat) {\n case 'wsl':\n return 'wsl2hosts';\n case 'darwin':\n return isDnsmasqInstalled() ? 'dnsmasq' : 'hosts_file';\n case 'linux':\n return isDnsmasqInstalled() ? 'dnsmasq' : 'hosts_file';\n default:\n return 'hosts_file';\n }\n}\n\n/**\n * Check if dnsmasq is installed.\n * Note: Uses execSync intentionally — this only runs in CLI context (pan install/up),\n * never from the dashboard server.\n */\nfunction isDnsmasqInstalled(): boolean {\n try {\n execSync('which dnsmasq', { stdio: 'pipe' });\n return true;\n } catch {\n return false;\n }\n}\n\n// ---- wsl2hosts method ----\n\nexport function addWsl2HostEntry(hostname: string): boolean {\n const wsl2hostsPath = join(homedir(), '.wsl2hosts');\n\n try {\n let content = '';\n if (existsSync(wsl2hostsPath)) {\n content = readFileSync(wsl2hostsPath, 'utf-8');\n }\n\n if (!content.includes(hostname)) {\n writeFileSync(wsl2hostsPath, content + (content.endsWith('\\n') ? '' : '\\n') + hostname + '\\n');\n }\n return true;\n } catch {\n return false;\n }\n}\n\nexport function removeWsl2HostEntry(hostname: string): boolean {\n const wsl2hostsPath = join(homedir(), '.wsl2hosts');\n\n try {\n if (!existsSync(wsl2hostsPath)) return true;\n\n const content = readFileSync(wsl2hostsPath, 'utf-8');\n const lines = content.split('\\n').filter(line => line.trim() !== hostname);\n writeFileSync(wsl2hostsPath, lines.join('\\n'));\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function syncDnsToWindows(): Promise<boolean> {\n try {\n await execAsync('powershell.exe -Command \"Start-ScheduledTask -TaskName \\'PanopticonWsl2HostsSync\\'\"');\n return true;\n } catch {\n // Fall back to legacy task name\n try {\n await execAsync('powershell.exe -Command \"Start-ScheduledTask -TaskName \\'SyncMynHosts\\'\"');\n return true;\n } catch {\n return false;\n }\n }\n}\n\n// ---- hosts_file method ----\n\nconst HOSTS_FILE = '/etc/hosts';\nconst MARKER_START = '# BEGIN panopticon managed entries';\nconst MARKER_END = '# END panopticon managed entries';\n\nexport function addHostsFileEntry(hostname: string, ip: string = '127.0.0.1'): boolean {\n try {\n let content = existsSync(HOSTS_FILE) ? readFileSync(HOSTS_FILE, 'utf-8') : '';\n const entry = `${ip}\\t${hostname}`;\n\n // Already present anywhere in file\n if (content.includes(`\\t${hostname}`) || content.includes(` ${hostname}`)) return true;\n\n const startIdx = content.indexOf(MARKER_START);\n const endIdx = content.indexOf(MARKER_END);\n\n if (startIdx !== -1 && endIdx !== -1) {\n // Managed block exists — insert entry before MARKER_END\n const before = content.substring(0, endIdx);\n const after = content.substring(endIdx);\n content = before + entry + '\\n' + after;\n } else {\n // Create managed block at end\n content = content.trimEnd() + '\\n\\n' + MARKER_START + '\\n' + entry + '\\n' + MARKER_END + '\\n';\n }\n\n writeFileSync(HOSTS_FILE, content);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function removeHostsFileEntry(hostname: string): boolean {\n try {\n if (!existsSync(HOSTS_FILE)) return true;\n\n const content = readFileSync(HOSTS_FILE, 'utf-8');\n const lines = content.split('\\n').filter(line => {\n const parts = line.trim().split(/\\s+/);\n return parts[1] !== hostname;\n });\n writeFileSync(HOSTS_FILE, lines.join('\\n'));\n return true;\n } catch {\n return false;\n }\n}\n\n// ---- dnsmasq method ----\n\nfunction getDnsmasqConfigDir(): string {\n const plat = detectPlatform();\n if (plat === 'darwin') {\n // Homebrew Intel location; Apple Silicon uses /opt/homebrew/etc/dnsmasq.d\n const brewPrefix = existsSync('/opt/homebrew') ? '/opt/homebrew' : '/usr/local';\n return `${brewPrefix}/etc/dnsmasq.d`;\n }\n return '/etc/dnsmasq.d';\n}\n\nconst PANOPTICON_DNSMASQ_CONF = 'panopticon.conf';\n\nexport function addDnsmasqEntry(hostname: string, ip: string = '127.0.0.1'): boolean {\n try {\n const configDir = getDnsmasqConfigDir();\n mkdirSync(configDir, { recursive: true });\n const confPath = join(configDir, PANOPTICON_DNSMASQ_CONF);\n\n let content = '';\n if (existsSync(confPath)) {\n content = readFileSync(confPath, 'utf-8');\n }\n\n const entry = `address=/${hostname}/${ip}`;\n if (content.includes(entry)) return true;\n\n content = content.trimEnd() + (content.length > 0 ? '\\n' : '') + entry + '\\n';\n writeFileSync(confPath, content);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function removeDnsmasqEntry(hostname: string): boolean {\n try {\n const configDir = getDnsmasqConfigDir();\n const confPath = join(configDir, PANOPTICON_DNSMASQ_CONF);\n if (!existsSync(confPath)) return true;\n\n const content = readFileSync(confPath, 'utf-8');\n const lines = content.split('\\n').filter(line => !line.includes(`/${hostname}/`));\n writeFileSync(confPath, lines.join('\\n'));\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function restartDnsmasq(): Promise<boolean> {\n const plat = detectPlatform();\n try {\n if (plat === 'darwin') {\n await execAsync('brew services restart dnsmasq');\n } else {\n await execAsync('sudo systemctl restart dnsmasq');\n }\n return true;\n } catch {\n return false;\n }\n}\n\n// ---- Unified interface ----\n\n/**\n * Add a DNS entry using the specified sync method.\n */\nexport function addDnsEntry(method: DnsSyncMethod, hostname: string): boolean {\n switch (method) {\n case 'wsl2hosts':\n return addWsl2HostEntry(hostname);\n case 'hosts_file':\n return addHostsFileEntry(hostname);\n case 'dnsmasq':\n return addDnsmasqEntry(hostname);\n }\n}\n\n/**\n * Remove a DNS entry using the specified sync method.\n */\nexport function removeDnsEntry(method: DnsSyncMethod, hostname: string): boolean {\n switch (method) {\n case 'wsl2hosts':\n return removeWsl2HostEntry(hostname);\n case 'hosts_file':\n return removeHostsFileEntry(hostname);\n case 'dnsmasq':\n return removeDnsmasqEntry(hostname);\n }\n}\n\n/**\n * Ensure the base Panopticon domain is resolvable.\n * Called during `pan install` and `pan up`.\n */\nexport function ensureBaseDomain(method: DnsSyncMethod, domain: string = 'pan.localhost'): boolean {\n return addDnsEntry(method, domain);\n}\n","/**\n * Platform Detection\n *\n * Shared platform detection utility. Distinguishes between\n * native Linux, macOS, Windows, and WSL2.\n */\n\nimport { readFileSync } from 'fs';\nimport { platform } from 'os';\n\nexport type Platform = 'linux' | 'darwin' | 'win32' | 'wsl';\n\nexport function detectPlatform(): Platform {\n const os = platform();\n if (os === 'linux') {\n // Check for WSL\n try {\n const release = readFileSync('/proc/version', 'utf8').toLowerCase();\n if (release.includes('microsoft') || release.includes('wsl')) {\n return 'wsl';\n }\n } catch {}\n return 'linux';\n }\n return os as 'darwin' | 'win32';\n}\n"],"mappings":"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","names":["readFileSync","readFileSync"]}