panopticon-cli 0.5.0 → 0.5.3
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.
- package/dist/{agents-E43Y3HNU.js → agents-DMPT32H7.js} +7 -5
- package/dist/{chunk-AAFQANKW.js → chunk-2V2DQ3IX.js} +387 -110
- package/dist/chunk-2V2DQ3IX.js.map +1 -0
- package/dist/{chunk-GR6ZZMCX.js → chunk-4HST45MO.js} +15 -12
- package/dist/chunk-4HST45MO.js.map +1 -0
- package/dist/{chunk-YLPSQAM2.js → chunk-565HZ6VV.js} +2 -2
- package/dist/chunk-6N2KBSJA.js +452 -0
- package/dist/chunk-6N2KBSJA.js.map +1 -0
- package/dist/{chunk-HZT2AOPN.js → chunk-D67AQTHF.js} +44 -19
- package/dist/chunk-D67AQTHF.js.map +1 -0
- package/dist/{chunk-WQG2TYCB.js → chunk-DFNVHK3N.js} +2 -2
- package/dist/{chunk-7SN4L4PH.js → chunk-HOGYHJ2G.js} +2 -2
- package/dist/{chunk-FTCPTHIJ.js → chunk-HRU7S4TA.js} +24 -7
- package/dist/chunk-HRU7S4TA.js.map +1 -0
- package/dist/{chunk-PPRFKTVC.js → chunk-KBHRXV5T.js} +3 -3
- package/dist/chunk-KBHRXV5T.js.map +1 -0
- package/dist/{chunk-CWELWPWQ.js → chunk-MOPGR3CL.js} +1 -1
- package/dist/chunk-MOPGR3CL.js.map +1 -0
- package/dist/{chunk-OMNXYPXC.js → chunk-RLZQB7HS.js} +15 -2
- package/dist/chunk-RLZQB7HS.js.map +1 -0
- package/dist/{chunk-NTO3EDB3.js → chunk-T7BBPDEJ.js} +56 -13
- package/dist/chunk-T7BBPDEJ.js.map +1 -0
- package/dist/chunk-USYP2SBE.js +317 -0
- package/dist/chunk-USYP2SBE.js.map +1 -0
- package/dist/{chunk-JM6V62LT.js → chunk-ZDNQFWR5.js} +2 -2
- package/dist/{chunk-JM6V62LT.js.map → chunk-ZDNQFWR5.js.map} +1 -1
- package/dist/{chunk-HJSM6E6U.js → chunk-ZP6EWSZV.js} +29 -322
- package/dist/chunk-ZP6EWSZV.js.map +1 -0
- package/dist/{chunk-PELXV435.js → chunk-ZTYHZMEC.js} +2 -2
- package/dist/chunk-ZTYHZMEC.js.map +1 -0
- package/dist/cli/index.js +1612 -925
- package/dist/cli/index.js.map +1 -1
- package/dist/config-yaml-OVZLKFMA.js +18 -0
- package/dist/dashboard/prompts/merge-agent.md +7 -5
- package/dist/dashboard/prompts/review-agent.md +12 -1
- package/dist/dashboard/prompts/test-agent.md +3 -1
- package/dist/dashboard/public/assets/index-BJKEp64j.css +32 -0
- package/dist/dashboard/public/assets/index-CgJjqjAV.js +767 -0
- package/dist/dashboard/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-400-normal-BnQMeOim.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-600-normal-BflQw4A9.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-600-normal-DjKNqYRj.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-400-normal-CfP_5XZW.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-400-normal-DRPE3kg4.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-600-normal-DxxdqCpr.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-600-normal-VcznFIpX.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-400-normal-B7xT_GF5.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-400-normal-BIWiOVfw.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-600-normal-D6zpsUhD.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-600-normal-DUi7WF5p.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
- package/dist/dashboard/public/index.html +5 -3
- package/dist/dashboard/server.js +5196 -2859
- package/dist/{feedback-writer-LVZ5TFYZ.js → feedback-writer-T43PI5S2.js} +2 -2
- package/dist/{hume-WMAUBBV2.js → hume-CKJJ3OUU.js} +3 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/{projects-JEIVIYC6.js → projects-KVM3MN3Y.js} +4 -2
- package/dist/{remote-agents-TFSMW7GN.js → remote-agents-ULPD6C5U.js} +3 -3
- package/dist/{remote-workspace-AHVHQEES.js → remote-workspace-XX6ARE6I.js} +3 -3
- package/dist/{review-status-EPFG4XM7.js → review-status-XKUKZF6J.js} +3 -2
- package/dist/{specialist-context-ZC6A4M3I.js → specialist-context-53AWO6AE.js} +6 -5
- package/dist/{specialist-context-ZC6A4M3I.js.map → specialist-context-53AWO6AE.js.map} +1 -1
- package/dist/{specialist-logs-KLGJCEUL.js → specialist-logs-QREUJ4HN.js} +6 -5
- package/dist/{specialists-O4HWDJL5.js → specialists-2DBBXRCK.js} +6 -5
- package/dist/{traefik-QN7R5I6V.js → traefik-5GL3Q7DJ.js} +3 -3
- package/dist/{tunnel-W2GZBLEV.js → tunnel-BKC7KLBX.js} +3 -3
- package/dist/{workspace-manager-IE4JL2JP.js → workspace-manager-ALBR62AS.js} +5 -5
- package/dist/workspace-manager-ALBR62AS.js.map +1 -0
- package/package.json +1 -1
- package/scripts/record-cost-event.js +8429 -47
- package/scripts/work-agent-stop-hook +221 -24
- package/skills/pan-new-project/SKILL.md +304 -0
- package/dist/chunk-AAFQANKW.js.map +0 -1
- package/dist/chunk-CWELWPWQ.js.map +0 -1
- package/dist/chunk-FTCPTHIJ.js.map +0 -1
- package/dist/chunk-GFP3PIPB.js +0 -96
- package/dist/chunk-GFP3PIPB.js.map +0 -1
- package/dist/chunk-GR6ZZMCX.js.map +0 -1
- package/dist/chunk-HJSM6E6U.js.map +0 -1
- package/dist/chunk-HZT2AOPN.js.map +0 -1
- package/dist/chunk-NTO3EDB3.js.map +0 -1
- package/dist/chunk-OMNXYPXC.js.map +0 -1
- package/dist/chunk-PELXV435.js.map +0 -1
- package/dist/chunk-PPRFKTVC.js.map +0 -1
- package/dist/dashboard/public/assets/index-BxpjweAL.css +0 -32
- package/dist/dashboard/public/assets/index-DQHkwvvJ.js +0 -743
- /package/dist/{agents-E43Y3HNU.js.map → agents-DMPT32H7.js.map} +0 -0
- /package/dist/{chunk-YLPSQAM2.js.map → chunk-565HZ6VV.js.map} +0 -0
- /package/dist/{chunk-WQG2TYCB.js.map → chunk-DFNVHK3N.js.map} +0 -0
- /package/dist/{chunk-7SN4L4PH.js.map → chunk-HOGYHJ2G.js.map} +0 -0
- /package/dist/{hume-WMAUBBV2.js.map → config-yaml-OVZLKFMA.js.map} +0 -0
- /package/dist/{feedback-writer-LVZ5TFYZ.js.map → feedback-writer-T43PI5S2.js.map} +0 -0
- /package/dist/{projects-JEIVIYC6.js.map → hume-CKJJ3OUU.js.map} +0 -0
- /package/dist/{remote-agents-TFSMW7GN.js.map → projects-KVM3MN3Y.js.map} +0 -0
- /package/dist/{review-status-EPFG4XM7.js.map → remote-agents-ULPD6C5U.js.map} +0 -0
- /package/dist/{remote-workspace-AHVHQEES.js.map → remote-workspace-XX6ARE6I.js.map} +0 -0
- /package/dist/{specialist-logs-KLGJCEUL.js.map → review-status-XKUKZF6J.js.map} +0 -0
- /package/dist/{specialists-O4HWDJL5.js.map → specialist-logs-QREUJ4HN.js.map} +0 -0
- /package/dist/{traefik-QN7R5I6V.js.map → specialists-2DBBXRCK.js.map} +0 -0
- /package/dist/{tunnel-W2GZBLEV.js.map → traefik-5GL3Q7DJ.js.map} +0 -0
- /package/dist/{workspace-manager-IE4JL2JP.js.map → tunnel-BKC7KLBX.js.map} +0 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SETTINGS_FILE,
|
|
3
|
+
init_paths
|
|
4
|
+
} from "./chunk-ZTFNYOC7.js";
|
|
5
|
+
import {
|
|
6
|
+
__esm,
|
|
7
|
+
init_esm_shims
|
|
8
|
+
} from "./chunk-ZHC57RCV.js";
|
|
9
|
+
|
|
10
|
+
// src/lib/settings.ts
|
|
11
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
12
|
+
function deepMerge(defaults, overrides) {
|
|
13
|
+
const result = { ...defaults };
|
|
14
|
+
for (const key of Object.keys(overrides)) {
|
|
15
|
+
const defaultVal = defaults[key];
|
|
16
|
+
const overrideVal = overrides[key];
|
|
17
|
+
if (overrideVal === void 0) continue;
|
|
18
|
+
if (typeof defaultVal === "object" && defaultVal !== null && !Array.isArray(defaultVal) && typeof overrideVal === "object" && overrideVal !== null && !Array.isArray(overrideVal)) {
|
|
19
|
+
result[key] = deepMerge(defaultVal, overrideVal);
|
|
20
|
+
} else {
|
|
21
|
+
result[key] = overrideVal;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
function loadSettings() {
|
|
27
|
+
let settings;
|
|
28
|
+
if (!existsSync(SETTINGS_FILE)) {
|
|
29
|
+
settings = getDefaultSettings();
|
|
30
|
+
} else {
|
|
31
|
+
try {
|
|
32
|
+
const content = readFileSync(SETTINGS_FILE, "utf8");
|
|
33
|
+
const parsed = JSON.parse(content);
|
|
34
|
+
settings = deepMerge(DEFAULT_SETTINGS, parsed);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error("Warning: Failed to parse settings.json, using defaults");
|
|
37
|
+
settings = getDefaultSettings();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const envApiKeys = {};
|
|
41
|
+
if (process.env.OPENAI_API_KEY) envApiKeys.openai = process.env.OPENAI_API_KEY;
|
|
42
|
+
if (process.env.GOOGLE_API_KEY) envApiKeys.google = process.env.GOOGLE_API_KEY;
|
|
43
|
+
if (process.env.ZAI_API_KEY) envApiKeys.zai = process.env.ZAI_API_KEY;
|
|
44
|
+
if (process.env.KIMI_API_KEY) envApiKeys.kimi = process.env.KIMI_API_KEY;
|
|
45
|
+
settings.api_keys = {
|
|
46
|
+
...envApiKeys,
|
|
47
|
+
...settings.api_keys
|
|
48
|
+
};
|
|
49
|
+
return settings;
|
|
50
|
+
}
|
|
51
|
+
function saveSettings(settings) {
|
|
52
|
+
const content = JSON.stringify(settings, null, 2);
|
|
53
|
+
writeFileSync(SETTINGS_FILE, content, "utf8");
|
|
54
|
+
}
|
|
55
|
+
function validateSettings(settings) {
|
|
56
|
+
if (!settings.models) {
|
|
57
|
+
return "Missing models configuration";
|
|
58
|
+
}
|
|
59
|
+
if (!settings.models.specialists) {
|
|
60
|
+
return "Missing specialists configuration";
|
|
61
|
+
}
|
|
62
|
+
const specialists = settings.models.specialists;
|
|
63
|
+
if (!specialists.review_agent || !specialists.test_agent || !specialists.merge_agent) {
|
|
64
|
+
return "Missing specialist agent model configuration";
|
|
65
|
+
}
|
|
66
|
+
if (!settings.models.complexity) {
|
|
67
|
+
return "Missing complexity configuration";
|
|
68
|
+
}
|
|
69
|
+
const complexity = settings.models.complexity;
|
|
70
|
+
const requiredLevels = ["trivial", "simple", "medium", "complex", "expert"];
|
|
71
|
+
for (const level of requiredLevels) {
|
|
72
|
+
if (!complexity[level]) {
|
|
73
|
+
return `Missing complexity level: ${level}`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!settings.api_keys) {
|
|
77
|
+
return "Missing api_keys configuration";
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
function getDefaultSettings() {
|
|
82
|
+
return JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
|
|
83
|
+
}
|
|
84
|
+
function getAvailableModels(settings) {
|
|
85
|
+
const anthropicModels = [
|
|
86
|
+
"claude-opus-4-6",
|
|
87
|
+
"claude-sonnet-4-6",
|
|
88
|
+
"claude-haiku-4-5"
|
|
89
|
+
];
|
|
90
|
+
const openaiModels = settings.api_keys.openai ? ["gpt-5.2-codex", "o3-deep-research", "gpt-4o", "gpt-4o-mini"] : [];
|
|
91
|
+
const googleModels = settings.api_keys.google ? ["gemini-3-pro-preview", "gemini-3-flash-preview"] : [];
|
|
92
|
+
const zaiModels = settings.api_keys.zai ? ["glm-4.7", "glm-4.7-flash"] : [];
|
|
93
|
+
const kimiModels = settings.api_keys.kimi ? ["kimi-k2", "kimi-k2.5"] : [];
|
|
94
|
+
return {
|
|
95
|
+
anthropic: anthropicModels,
|
|
96
|
+
openai: openaiModels,
|
|
97
|
+
google: googleModels,
|
|
98
|
+
zai: zaiModels,
|
|
99
|
+
kimi: kimiModels
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function isAnthropicModel(modelId) {
|
|
103
|
+
return modelId.startsWith("claude-");
|
|
104
|
+
}
|
|
105
|
+
function getClaudeModelFlag(modelId) {
|
|
106
|
+
const modelMap = {
|
|
107
|
+
"claude-opus-4-6": "opus",
|
|
108
|
+
"claude-sonnet-4-6": "sonnet",
|
|
109
|
+
"claude-sonnet-4-5": "sonnet",
|
|
110
|
+
"claude-haiku-4-5": "haiku"
|
|
111
|
+
};
|
|
112
|
+
return modelMap[modelId] || "sonnet";
|
|
113
|
+
}
|
|
114
|
+
function getAgentCommand(modelId) {
|
|
115
|
+
if (isAnthropicModel(modelId)) {
|
|
116
|
+
return {
|
|
117
|
+
command: "claude",
|
|
118
|
+
args: ["--model", getClaudeModelFlag(modelId)]
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
command: "claude-code-router",
|
|
123
|
+
args: []
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
var DEFAULT_SETTINGS;
|
|
127
|
+
var init_settings = __esm({
|
|
128
|
+
"src/lib/settings.ts"() {
|
|
129
|
+
"use strict";
|
|
130
|
+
init_esm_shims();
|
|
131
|
+
init_paths();
|
|
132
|
+
DEFAULT_SETTINGS = {
|
|
133
|
+
models: {
|
|
134
|
+
specialists: {
|
|
135
|
+
review_agent: "claude-opus-4-6",
|
|
136
|
+
test_agent: "claude-sonnet-4-6",
|
|
137
|
+
merge_agent: "claude-sonnet-4-6"
|
|
138
|
+
},
|
|
139
|
+
status_review: "claude-opus-4-6",
|
|
140
|
+
complexity: {
|
|
141
|
+
trivial: "claude-haiku-4-5",
|
|
142
|
+
simple: "claude-haiku-4-5",
|
|
143
|
+
medium: "kimi-k2.5",
|
|
144
|
+
complex: "kimi-k2.5",
|
|
145
|
+
expert: "claude-opus-4-6"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
api_keys: {}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// src/lib/providers.ts
|
|
154
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
155
|
+
import { join } from "path";
|
|
156
|
+
function getProviderForModel(modelId) {
|
|
157
|
+
if (["claude-opus-4-6", "claude-sonnet-4-6", "claude-sonnet-4-5", "claude-haiku-4-5"].includes(modelId)) {
|
|
158
|
+
return PROVIDERS.anthropic;
|
|
159
|
+
}
|
|
160
|
+
if (["gpt-5.2-codex", "o3-deep-research", "gpt-4o", "gpt-4o-mini"].includes(modelId)) {
|
|
161
|
+
return PROVIDERS.openai;
|
|
162
|
+
}
|
|
163
|
+
if (["gemini-3-pro-preview", "gemini-3-flash-preview"].includes(modelId)) {
|
|
164
|
+
return PROVIDERS.google;
|
|
165
|
+
}
|
|
166
|
+
if (["glm-4.7", "glm-4.7-flash"].includes(modelId)) {
|
|
167
|
+
return PROVIDERS.zai;
|
|
168
|
+
}
|
|
169
|
+
if (["kimi-k2", "kimi-k2.5"].includes(modelId)) {
|
|
170
|
+
return PROVIDERS.kimi;
|
|
171
|
+
}
|
|
172
|
+
return PROVIDERS.anthropic;
|
|
173
|
+
}
|
|
174
|
+
function requiresRouter(provider) {
|
|
175
|
+
return PROVIDERS[provider].compatibility === "router";
|
|
176
|
+
}
|
|
177
|
+
function getRouterProviders() {
|
|
178
|
+
return Object.values(PROVIDERS).filter((p) => p.compatibility === "router");
|
|
179
|
+
}
|
|
180
|
+
function getDirectProviders() {
|
|
181
|
+
return Object.values(PROVIDERS).filter((p) => p.compatibility === "direct");
|
|
182
|
+
}
|
|
183
|
+
function needsRouter(apiKeys) {
|
|
184
|
+
return !!(apiKeys.openai || apiKeys.google);
|
|
185
|
+
}
|
|
186
|
+
function getProviderEnv(provider, apiKey) {
|
|
187
|
+
if (provider.compatibility === "direct") {
|
|
188
|
+
const env = {};
|
|
189
|
+
if (provider.baseUrl) {
|
|
190
|
+
env.ANTHROPIC_BASE_URL = provider.baseUrl;
|
|
191
|
+
}
|
|
192
|
+
if (provider.name !== "anthropic") {
|
|
193
|
+
if (provider.authType === "credential-file") {
|
|
194
|
+
env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
195
|
+
env.CLAUDE_CODE_API_KEY_HELPER_TTL_MS = "60000";
|
|
196
|
+
} else {
|
|
197
|
+
env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (provider.name === "zai") {
|
|
201
|
+
env.API_TIMEOUT_MS = "300000";
|
|
202
|
+
}
|
|
203
|
+
return env;
|
|
204
|
+
} else {
|
|
205
|
+
return {
|
|
206
|
+
ANTHROPIC_BASE_URL: "http://localhost:3456",
|
|
207
|
+
ANTHROPIC_AUTH_TOKEN: "router-managed"
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function setupCredentialFileAuth(provider, workspacePath) {
|
|
212
|
+
if (provider.authType !== "credential-file" || !provider.credentialHelper) return;
|
|
213
|
+
const helperPath = provider.credentialHelper.replace("~", process.env.HOME || "");
|
|
214
|
+
const claudeDir = join(workspacePath, ".claude");
|
|
215
|
+
const settingsPath = join(claudeDir, "settings.local.json");
|
|
216
|
+
if (!existsSync2(claudeDir)) {
|
|
217
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
218
|
+
}
|
|
219
|
+
let settings = {};
|
|
220
|
+
if (existsSync2(settingsPath)) {
|
|
221
|
+
try {
|
|
222
|
+
settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
|
|
223
|
+
} catch {
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
settings.apiKeyHelper = helperPath;
|
|
227
|
+
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
228
|
+
}
|
|
229
|
+
function clearCredentialFileAuth(workspacePath) {
|
|
230
|
+
const settingsPath = join(workspacePath, ".claude", "settings.local.json");
|
|
231
|
+
if (!existsSync2(settingsPath)) return;
|
|
232
|
+
try {
|
|
233
|
+
const settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
|
|
234
|
+
if (!settings.apiKeyHelper) return;
|
|
235
|
+
delete settings.apiKeyHelper;
|
|
236
|
+
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
var PROVIDERS;
|
|
241
|
+
var init_providers = __esm({
|
|
242
|
+
"src/lib/providers.ts"() {
|
|
243
|
+
"use strict";
|
|
244
|
+
init_esm_shims();
|
|
245
|
+
PROVIDERS = {
|
|
246
|
+
anthropic: {
|
|
247
|
+
name: "anthropic",
|
|
248
|
+
displayName: "Anthropic",
|
|
249
|
+
compatibility: "direct",
|
|
250
|
+
models: ["claude-opus-4-6", "claude-sonnet-4-6", "claude-sonnet-4-5", "claude-haiku-4-5"],
|
|
251
|
+
tested: true,
|
|
252
|
+
description: "Native Claude API"
|
|
253
|
+
},
|
|
254
|
+
kimi: {
|
|
255
|
+
name: "kimi",
|
|
256
|
+
displayName: "Kimi (Moonshot AI)",
|
|
257
|
+
compatibility: "direct",
|
|
258
|
+
baseUrl: "https://api.kimi.com/coding/",
|
|
259
|
+
authType: "credential-file",
|
|
260
|
+
credentialFile: "~/.kimi/credentials/kimi-code.json",
|
|
261
|
+
credentialHelper: "~/.panopticon/bin/kimi-token-helper.sh",
|
|
262
|
+
models: [],
|
|
263
|
+
// Kimi uses same model names as Anthropic
|
|
264
|
+
tested: true,
|
|
265
|
+
description: "Anthropic-compatible API via Kimi Code Plan (OAuth token refresh)"
|
|
266
|
+
},
|
|
267
|
+
zai: {
|
|
268
|
+
name: "zai",
|
|
269
|
+
displayName: "Z.AI (GLM)",
|
|
270
|
+
compatibility: "direct",
|
|
271
|
+
baseUrl: "https://api.z.ai/api/anthropic",
|
|
272
|
+
models: ["glm-4.7", "glm-4.7-flash"],
|
|
273
|
+
tested: true,
|
|
274
|
+
description: "Anthropic-compatible API, tested 2026-01-28"
|
|
275
|
+
},
|
|
276
|
+
openai: {
|
|
277
|
+
name: "openai",
|
|
278
|
+
displayName: "OpenAI",
|
|
279
|
+
compatibility: "router",
|
|
280
|
+
models: ["gpt-5.2-codex", "o3-deep-research", "gpt-4o", "gpt-4o-mini"],
|
|
281
|
+
tested: false,
|
|
282
|
+
description: "Requires claude-code-router for API translation"
|
|
283
|
+
},
|
|
284
|
+
google: {
|
|
285
|
+
name: "google",
|
|
286
|
+
displayName: "Google (Gemini)",
|
|
287
|
+
compatibility: "router",
|
|
288
|
+
models: ["gemini-3-pro-preview", "gemini-3-flash-preview"],
|
|
289
|
+
tested: false,
|
|
290
|
+
description: "Requires claude-code-router for API translation"
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
export {
|
|
297
|
+
loadSettings,
|
|
298
|
+
saveSettings,
|
|
299
|
+
validateSettings,
|
|
300
|
+
getDefaultSettings,
|
|
301
|
+
getAvailableModels,
|
|
302
|
+
isAnthropicModel,
|
|
303
|
+
getClaudeModelFlag,
|
|
304
|
+
getAgentCommand,
|
|
305
|
+
init_settings,
|
|
306
|
+
PROVIDERS,
|
|
307
|
+
getProviderForModel,
|
|
308
|
+
requiresRouter,
|
|
309
|
+
getRouterProviders,
|
|
310
|
+
getDirectProviders,
|
|
311
|
+
needsRouter,
|
|
312
|
+
getProviderEnv,
|
|
313
|
+
setupCredentialFileAuth,
|
|
314
|
+
clearCredentialFileAuth,
|
|
315
|
+
init_providers
|
|
316
|
+
};
|
|
317
|
+
//# sourceMappingURL=chunk-USYP2SBE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/settings.ts","../src/lib/providers.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"],"mappings":"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","names":["existsSync","readFileSync","writeFileSync"]}
|
|
@@ -513,7 +513,7 @@ COMPOSE_EOF`);
|
|
|
513
513
|
return true;
|
|
514
514
|
}
|
|
515
515
|
const msg = commitMessage || "Sync beads from planning session";
|
|
516
|
-
await this.ssh(vmName, `cd ${workspacePath} && git add .beads/ .planning/ 2>/dev/null || true`);
|
|
516
|
+
await this.ssh(vmName, `cd ${workspacePath} && git add .beads/ && git add -f .planning/ 2>/dev/null || true`);
|
|
517
517
|
await this.ssh(vmName, `cd ${workspacePath} && git commit -m "${msg}" 2>/dev/null || true`);
|
|
518
518
|
const branchResult = await this.ssh(vmName, `cd ${workspacePath} && git branch --show-current`);
|
|
519
519
|
const branch = branchResult.stdout.trim() || "main";
|
|
@@ -647,4 +647,4 @@ export {
|
|
|
647
647
|
createExeProvider,
|
|
648
648
|
init_exe_provider
|
|
649
649
|
};
|
|
650
|
-
//# sourceMappingURL=chunk-
|
|
650
|
+
//# sourceMappingURL=chunk-ZDNQFWR5.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/remote/exe-provider.ts"],"sourcesContent":["/**\n * exe.dev Remote Provider\n *\n * Implements the RemoteProvider interface for exe.dev cloud VMs.\n * exe.dev provides affordable dev VMs with persistent storage.\n *\n * exe.dev uses an SSH-based API:\n * - `ssh exe.dev` - Access the CLI\n * - `ssh exe.dev new` - Create a new VM\n * - `ssh exe.dev ls` - List VMs\n * - `ssh vmname.exe.xyz` - SSH into a VM\n *\n * Pricing (as of 2025):\n * - Individual: $20/month, 8GB RAM\n * - Team: $25/month/user, 8GB RAM\n * - Enterprise: $30/month/user, 16GB RAM\n *\n * @see https://exe.dev/docs\n */\n\nimport { exec, spawn } from 'child_process';\nimport { promisify } from 'util';\nimport { existsSync, readFileSync, readdirSync, statSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type {\n RemoteProvider,\n VmInfo,\n VmStatus,\n ExecResult,\n} from './interface.js';\n\nconst execAsync = promisify(exec);\n\nexport interface ExeProviderConfig {\n /** Shared infrastructure VM name (for postgres, redis, traefik) */\n infraVm?: string;\n}\n\n/**\n * Parse exe.dev ls output for VM list\n * Format appears to be: vmname (status info)\n */\nfunction parseVmList(output: string): VmInfo[] {\n const vms: VmInfo[] = [];\n const lines = output.trim().split('\\n');\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('Your VMs')) continue;\n\n // exe.dev format: \" • pan-pan-81-ws.exe.xyz - running (boldsoftware/exeuntu)\"\n // Extract VM name (without .exe.xyz suffix) and status\n const match = trimmed.match(/[•\\-]\\s*([a-z0-9-]+)\\.exe\\.xyz\\s*-\\s*(\\w+)/i);\n if (match) {\n const name = match[1];\n const statusText = match[2].toLowerCase();\n const status: VmStatus = statusText === 'running' ? 'running' :\n statusText === 'stopped' ? 'stopped' : 'unknown';\n vms.push({ name, status });\n }\n }\n\n return vms;\n}\n\n/**\n * Check if SSH key is configured for exe.dev\n */\nasync function canSshToExeDev(): Promise<boolean> {\n try {\n // Try a quick command - ssh exe.dev ls should work if authenticated\n const { stdout } = await execAsync('ssh -o BatchMode=yes -o ConnectTimeout=5 exe.dev ls 2>&1', {\n timeout: 10000,\n });\n // If we get output without permission denied, we're good\n return !stdout.includes('Permission denied') && !stdout.includes('Host key verification failed');\n } catch (error: any) {\n // Check if it's just an empty list (which is fine)\n if (error.stdout && !error.stdout.includes('Permission denied')) {\n return true;\n }\n return false;\n }\n}\n\nexport class ExeProvider implements RemoteProvider {\n readonly name = 'exe';\n private config: ExeProviderConfig;\n\n constructor(config: ExeProviderConfig = {}) {\n this.config = config;\n }\n\n /**\n * Check if user is authenticated with exe.dev\n * This checks if SSH key is configured and can connect\n */\n async isAuthenticated(): Promise<boolean> {\n return canSshToExeDev();\n }\n\n /**\n * Execute a command on the exe.dev CLI (via ssh exe.dev)\n */\n private async exeCmd(command: string): Promise<ExecResult> {\n try {\n const { stdout, stderr } = await execAsync(`ssh exe.dev ${command}`, {\n timeout: 60000,\n maxBuffer: 10 * 1024 * 1024,\n });\n return { stdout, stderr, exitCode: 0 };\n } catch (error: any) {\n return {\n stdout: error.stdout || '',\n stderr: error.stderr || error.message,\n exitCode: error.code || 1,\n };\n }\n }\n\n /**\n * Create a new VM on exe.dev\n */\n async createVm(name: string): Promise<VmInfo> {\n try {\n // exe.dev uses --name flag, and names must be valid hostnames\n const result = await this.exeCmd(`new --name=${name}`);\n\n if (result.exitCode !== 0) {\n throw new Error(`Failed to create VM: ${result.stderr}`);\n }\n\n // Wait a moment for VM to be ready\n await new Promise(resolve => setTimeout(resolve, 5000));\n\n return { name, status: 'running' };\n } catch (error: any) {\n throw new Error(`Failed to create VM ${name}: ${error.message}`);\n }\n }\n\n /**\n * Delete a VM on exe.dev\n */\n async deleteVm(name: string): Promise<void> {\n try {\n const result = await this.exeCmd(`rm ${name}`);\n\n if (result.exitCode !== 0 && !result.stderr.includes('not found')) {\n throw new Error(result.stderr);\n }\n } catch (error: any) {\n throw new Error(`Failed to delete VM ${name}: ${error.message}`);\n }\n }\n\n /**\n * List all VMs\n */\n async listVms(): Promise<VmInfo[]> {\n try {\n const result = await this.exeCmd('ls');\n\n if (result.exitCode !== 0) {\n throw new Error(result.stderr);\n }\n\n return parseVmList(result.stdout);\n } catch (error: any) {\n throw new Error(`Failed to list VMs: ${error.message}`);\n }\n }\n\n /**\n * Get VM status\n */\n async getStatus(name: string): Promise<VmStatus> {\n try {\n const vms = await this.listVms();\n const vm = vms.find(v => v.name === name);\n return vm?.status || 'unknown';\n } catch {\n return 'unknown';\n }\n }\n\n /**\n * Get detailed VM info\n */\n async getVmInfo(name: string): Promise<VmInfo | null> {\n try {\n const vms = await this.listVms();\n return vms.find(v => v.name === name) || null;\n } catch {\n return null;\n }\n }\n\n /**\n * Start a stopped VM\n * Note: exe.dev VMs are persistent and always running\n */\n async startVm(name: string): Promise<void> {\n // exe.dev VMs don't have start/stop - they're always running\n // Check if VM exists\n const status = await this.getStatus(name);\n if (status === 'unknown') {\n throw new Error(`VM ${name} not found`);\n }\n }\n\n /**\n * Stop a running VM\n * Note: exe.dev VMs are persistent - use rm to delete\n */\n async stopVm(name: string): Promise<void> {\n // exe.dev doesn't have stop - VMs are always running or deleted\n // We can implement \"stop\" as a no-op or throw\n console.warn(`exe.dev VMs cannot be stopped, only deleted. VM ${name} continues running.`);\n }\n\n /**\n * Execute a command on VM via SSH\n * SSH to vmname.exe.xyz\n */\n async ssh(vm: string, command: string): Promise<ExecResult> {\n try {\n const sshHost = `${vm}.exe.xyz`;\n const escapedCmd = command.replace(/\"/g, '\\\\\"');\n\n // Use -A for agent forwarding so the VM can access GitHub with user's SSH keys\n const { stdout, stderr } = await execAsync(`ssh -A ${sshHost} \"${escapedCmd}\"`, {\n timeout: 300000, // 5 minutes\n maxBuffer: 50 * 1024 * 1024, // 50MB\n });\n\n return { stdout, stderr, exitCode: 0 };\n } catch (error: any) {\n return {\n stdout: error.stdout || '',\n stderr: error.stderr || error.message,\n exitCode: error.code || 1,\n };\n }\n }\n\n /**\n * Execute a command and stream output\n */\n async *sshStream(vm: string, command: string): AsyncIterable<string> {\n const sshHost = `${vm}.exe.xyz`;\n // Use -A for agent forwarding\n const child = spawn('ssh', ['-A', sshHost, command], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n for await (const chunk of child.stdout) {\n yield chunk.toString();\n }\n\n for await (const chunk of child.stderr) {\n yield chunk.toString();\n }\n }\n\n /**\n * Copy file to VM\n */\n async copyToVm(vm: string, localPath: string, remotePath: string): Promise<void> {\n try {\n const sshHost = `${vm}.exe.xyz`;\n await execAsync(`scp \"${localPath}\" ${sshHost}:${remotePath}`, { timeout: 300000 });\n } catch (error: any) {\n throw new Error(`Failed to copy ${localPath} to ${vm}:${remotePath}: ${error.message}`);\n }\n }\n\n /**\n * Copy file from VM\n */\n async copyFromVm(vm: string, remotePath: string, localPath: string): Promise<void> {\n try {\n const sshHost = `${vm}.exe.xyz`;\n await execAsync(`scp ${sshHost}:${remotePath} \"${localPath}\"`, { timeout: 300000 });\n } catch (error: any) {\n throw new Error(`Failed to copy ${vm}:${remotePath} to ${localPath}: ${error.message}`);\n }\n }\n\n /**\n * Expose a port on VM (returns public URL)\n *\n * exe.dev provides automatic HTTPS URLs for services:\n * https://vmname.exe.xyz:PORT\n */\n async exposePort(vm: string, port: number): Promise<string> {\n // exe.dev automatically exposes all ports via HTTPS\n // The URL format is: https://vmname.exe.xyz:PORT\n // Or for default HTTP (80/443): https://vmname.exe.xyz\n if (port === 80 || port === 443) {\n return `https://${vm}.exe.xyz`;\n }\n return `https://${vm}.exe.xyz:${port}`;\n }\n\n /**\n * Create SSH tunnel to VM\n */\n async tunnel(vm: string, remotePort: number, localPort: number): Promise<{ close: () => void }> {\n const sshHost = `${vm}.exe.xyz`;\n const child = spawn('ssh', ['-N', '-L', `${localPort}:localhost:${remotePort}`, sshHost], {\n stdio: 'ignore',\n detached: true,\n });\n\n child.unref();\n\n return {\n close: () => {\n child.kill();\n },\n };\n }\n\n /**\n * Get the configured infrastructure VM name\n */\n getInfraVm(): string | undefined {\n return this.config.infraVm;\n }\n\n /**\n * Initialize the shared infrastructure VM\n *\n * Sets up postgres, redis, and traefik on a dedicated VM.\n */\n async initInfrastructure(vmName: string): Promise<void> {\n // Check if VM exists\n let status = await this.getStatus(vmName);\n\n if (status === 'unknown') {\n // Create the VM\n await this.createVm(vmName);\n // Wait for it to be ready\n await new Promise(resolve => setTimeout(resolve, 10000));\n }\n\n // Install Docker if not present\n const dockerCheck = await this.ssh(vmName, 'which docker');\n if (dockerCheck.exitCode !== 0) {\n await this.ssh(vmName, 'curl -fsSL https://get.docker.com | sh');\n await this.ssh(vmName, 'sudo usermod -aG docker $USER');\n }\n\n // Create docker-compose.yml for shared services\n const composeContent = `\nversion: '3.8'\nservices:\n postgres:\n image: postgres:16\n restart: unless-stopped\n environment:\n POSTGRES_PASSWORD: \\${PAN_POSTGRES_PASSWORD:-panopticon}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n ports:\n - \"5432:5432\"\n\n redis:\n image: redis:7\n restart: unless-stopped\n volumes:\n - redis_data:/data\n ports:\n - \"6379:6379\"\n\n traefik:\n image: traefik:v3.0\n restart: unless-stopped\n ports:\n - \"80:80\"\n - \"443:443\"\n - \"8080:8080\"\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n command:\n - --api.dashboard=true\n - --providers.docker=true\n - --providers.docker.exposedbydefault=false\n - --entrypoints.web.address=:80\n - --entrypoints.websecure.address=:443\n\nvolumes:\n postgres_data:\n redis_data:\n`;\n\n // Write compose file and start services\n await this.ssh(vmName, `mkdir -p /opt/panopticon && cat > /opt/panopticon/docker-compose.yml << 'COMPOSE_EOF'\n${composeContent}\nCOMPOSE_EOF`);\n\n await this.ssh(vmName, 'cd /opt/panopticon && docker compose up -d');\n\n // Store as infra VM\n this.config.infraVm = vmName;\n }\n\n /**\n * Sync Claude Code credentials from local macOS Keychain to remote VM\n *\n * This should be called before spawning agents to ensure fresh credentials.\n * Credentials can expire, and re-running this ensures the VM has valid auth.\n *\n * @returns true if credentials were synced, false if not available\n */\n async syncClaudeCredentials(vmName: string): Promise<boolean> {\n try {\n // Get credentials from macOS Keychain\n const { stdout: credentials } = await execAsync(\n 'security find-generic-password -s \"Claude Code-credentials\" -w 2>/dev/null',\n { encoding: 'utf-8' }\n );\n\n if (!credentials || !credentials.trim()) {\n return false;\n }\n\n // Ensure ~/.claude directory exists\n await this.ssh(vmName, 'mkdir -p ~/.claude');\n\n // Send credentials to VM (base64 encode to handle special characters)\n const credsBase64 = Buffer.from(credentials.trim()).toString('base64');\n const result = await this.ssh(vmName, `echo '${credsBase64}' | base64 -d > ~/.claude/.credentials.json`);\n\n return result.exitCode === 0;\n } catch (error: any) {\n // Credentials not found in Keychain or other error\n return false;\n }\n }\n\n /**\n * Sync GitHub CLI authentication from local macOS to remote VM\n *\n * GitHub CLI on macOS stores tokens in Keychain. On Linux VMs, it stores\n * them directly in ~/.config/gh/hosts.yml. This extracts from Keychain\n * and writes to the VM's config file.\n *\n * @returns true if auth was synced, false if not available\n */\n async syncGitHubAuth(vmName: string): Promise<boolean> {\n try {\n // Get GitHub token from macOS Keychain\n const { stdout: tokenRaw } = await execAsync(\n 'security find-generic-password -s \"gh:github.com\" -w 2>/dev/null',\n { encoding: 'utf-8' }\n );\n\n if (!tokenRaw || !tokenRaw.trim()) {\n return false;\n }\n\n // The token may be base64-encoded with go-keyring prefix\n let token = tokenRaw.trim();\n if (token.startsWith('go-keyring-base64:')) {\n token = Buffer.from(token.replace('go-keyring-base64:', ''), 'base64').toString('utf-8');\n }\n\n // Get GitHub username from local config\n let username = 'user';\n try {\n const { stdout: configOutput } = await execAsync('cat ~/.config/gh/hosts.yml 2>/dev/null', { encoding: 'utf-8' });\n const userMatch = configOutput.match(/user:\\s*(\\S+)/);\n if (userMatch) {\n username = userMatch[1];\n }\n } catch {\n // Use default\n }\n\n // Create hosts.yml content for Linux (token stored directly in file)\n const hostsYml = `github.com:\n oauth_token: ${token}\n git_protocol: ssh\n user: ${username}\n`;\n\n // Ensure ~/.config/gh directory exists and write hosts.yml\n await this.ssh(vmName, 'mkdir -p ~/.config/gh');\n const hostsBase64 = Buffer.from(hostsYml).toString('base64');\n const result = await this.ssh(vmName, `echo '${hostsBase64}' | base64 -d > ~/.config/gh/hosts.yml && chmod 600 ~/.config/gh/hosts.yml`);\n\n return result.exitCode === 0;\n } catch (error: any) {\n // Token not found in Keychain or other error\n return false;\n }\n }\n\n /**\n * Sync GitLab CLI (glab) authentication to a remote VM\n *\n * Copies the glab config from local machine to the remote VM.\n * This allows the remote agent to use glab commands for MRs, etc.\n */\n async syncGitLabAuth(vmName: string): Promise<boolean> {\n try {\n // glab stores config in ~/.config/glab-cli/config.yml\n const glabConfigPath = join(homedir(), '.config', 'glab-cli', 'config.yml');\n\n if (!existsSync(glabConfigPath)) {\n console.log(`[exe-provider] No glab config found at ${glabConfigPath}`);\n return false;\n }\n\n // Read local glab config\n const glabConfig = readFileSync(glabConfigPath, 'utf-8');\n\n // Ensure ~/.config/glab-cli directory exists on remote\n await this.ssh(vmName, 'mkdir -p ~/.config/glab-cli');\n\n // Write config to remote\n const configBase64 = Buffer.from(glabConfig).toString('base64');\n const result = await this.ssh(vmName, `echo '${configBase64}' | base64 -d > ~/.config/glab-cli/config.yml && chmod 600 ~/.config/glab-cli/config.yml`);\n\n if (result.exitCode === 0) {\n console.log(`[exe-provider] GitLab auth synced to ${vmName}`);\n return true;\n }\n\n return false;\n } catch (error: any) {\n console.error(`[exe-provider] Failed to sync GitLab auth: ${error.message}`);\n return false;\n }\n }\n\n /**\n * Sync all credentials needed for remote workspace operation\n *\n * This syncs:\n * - Claude Code OAuth credentials\n * - GitHub CLI authentication\n *\n * Call this before spawning agents to ensure fresh credentials.\n *\n * @returns object with sync status for each credential type\n */\n async syncAllCredentials(vmName: string): Promise<{\n claude: boolean;\n github: boolean;\n }> {\n const [claude, github] = await Promise.all([\n this.syncClaudeCredentials(vmName),\n this.syncGitHubAuth(vmName),\n ]);\n\n return { claude, github };\n }\n\n /**\n * Install beads CLI (bd) on a remote VM\n *\n * Beads is required for planning agents to create task breakdowns.\n * Downloads the binary from GitHub releases since npm/node are often\n * not available on exe.dev VMs.\n *\n * @returns true if bd is available (already installed or just installed)\n */\n async installBeads(vmName: string): Promise<boolean> {\n const FALLBACK_BEADS_VERSION = '0.49.4';\n\n try {\n // Check if bd is already installed\n const checkResult = await this.ssh(vmName, 'which bd');\n if (checkResult.exitCode === 0 && checkResult.stdout.trim()) {\n console.log(`[exe-provider] bd already installed on ${vmName}`);\n return true;\n }\n\n console.log(`[exe-provider] Installing bd (beads CLI) on ${vmName}...`);\n\n // Download from GitHub releases - exe.dev VMs typically don't have npm/node\n // but do have curl and sudo access.\n // Resolve the latest version dynamically with fallback to known-good version.\n const installCmd = `\n cd /tmp && \\\n BEADS_VERSION=$(curl -sI \"https://github.com/steveyegge/beads/releases/latest\" 2>/dev/null | grep -i '^location:' | sed 's|.*/v||' | tr -d '\\\\r\\\\n') && \\\n if [ -z \"$BEADS_VERSION\" ]; then BEADS_VERSION=\"${FALLBACK_BEADS_VERSION}\"; echo \"Using fallback version: $BEADS_VERSION\"; else echo \"Detected version: $BEADS_VERSION\"; fi && \\\n echo \"Downloading beads v$BEADS_VERSION...\" && \\\n curl -sL \"https://github.com/steveyegge/beads/releases/download/v\\${BEADS_VERSION}/beads_\\${BEADS_VERSION}_linux_amd64.tar.gz\" -o beads.tar.gz && \\\n tar -xzf beads.tar.gz && \\\n sudo mv bd /usr/local/bin/ && \\\n rm -f beads.tar.gz CHANGELOG.md LICENSE README.md\n `.trim().replace(/\\n\\s+/g, ' ');\n\n const installResult = await this.ssh(vmName, installCmd);\n if (installResult.stderr) {\n console.log(`[exe-provider] bd install stderr on ${vmName}: ${installResult.stderr}`);\n }\n if (installResult.stdout) {\n console.log(`[exe-provider] bd install output on ${vmName}: ${installResult.stdout.trim()}`);\n }\n\n // Verify installation\n const verifyResult = await this.ssh(vmName, 'which bd && bd --version');\n if (verifyResult.exitCode === 0 && verifyResult.stdout.includes('bd version')) {\n console.log(`[exe-provider] bd installed successfully on ${vmName}: ${verifyResult.stdout.trim()}`);\n return true;\n }\n\n console.warn(`[exe-provider] Failed to install bd on ${vmName} - verify exitCode: ${verifyResult.exitCode}, stdout: ${verifyResult.stdout}, stderr: ${verifyResult.stderr}`);\n return false;\n } catch (error: any) {\n console.error(`[exe-provider] Error installing bd on ${vmName}:`, error.message);\n return false;\n }\n }\n\n /**\n * Initialize beads in a workspace on a remote VM\n *\n * Creates .beads/ directory and initializes the database if needed.\n */\n async initBeads(vmName: string, workspacePath: string = '/workspace'): Promise<boolean> {\n try {\n // Check if .beads already exists\n const checkResult = await this.ssh(vmName, `test -d ${workspacePath}/.beads && echo \"exists\"`);\n if (checkResult.stdout.includes('exists')) {\n console.log(`[exe-provider] .beads already initialized on ${vmName}`);\n return true;\n }\n\n console.log(`[exe-provider] Initializing beads on ${vmName}...`);\n\n // Initialize beads in the workspace\n const initResult = await this.ssh(vmName, `cd ${workspacePath} && bd init 2>/dev/null || true`);\n\n // Verify\n const verifyResult = await this.ssh(vmName, `test -d ${workspacePath}/.beads && echo \"ok\"`);\n if (verifyResult.stdout.includes('ok')) {\n console.log(`[exe-provider] beads initialized on ${vmName}`);\n return true;\n }\n\n // If bd init failed, create the directory manually\n await this.ssh(vmName, `mkdir -p ${workspacePath}/.beads`);\n return true;\n } catch (error: any) {\n console.error(`[exe-provider] Error initializing beads on ${vmName}:`, error.message);\n return false;\n }\n }\n\n /**\n * Sync beads from remote VM to git\n *\n * Exports beads database to JSONL, commits, and pushes.\n * This should be called after planning completes to persist tasks.\n */\n async syncBeadsToGit(vmName: string, workspacePath: string = '/workspace', commitMessage?: string): Promise<boolean> {\n try {\n console.log(`[exe-provider] Syncing beads to git on ${vmName}...`);\n\n // Export beads to JSONL\n const syncResult = await this.ssh(vmName, `cd ${workspacePath} && bd sync 2>/dev/null || true`);\n\n // Check if there are changes to commit\n const statusResult = await this.ssh(vmName, `cd ${workspacePath} && git status --porcelain .beads/ .planning/ 2>/dev/null`);\n\n if (!statusResult.stdout.trim()) {\n console.log(`[exe-provider] No beads changes to commit on ${vmName}`);\n return true;\n }\n\n // Add and commit\n const msg = commitMessage || 'Sync beads from planning session';\n await this.ssh(vmName, `cd ${workspacePath} && git add .beads/ .planning/ 2>/dev/null || true`);\n await this.ssh(vmName, `cd ${workspacePath} && git commit -m \"${msg}\" 2>/dev/null || true`);\n\n // Push - use -u to set upstream if not already set\n const branchResult = await this.ssh(vmName, `cd ${workspacePath} && git branch --show-current`);\n const branch = branchResult.stdout.trim() || 'main';\n const pushResult = await this.ssh(vmName, `cd ${workspacePath} && git push -u origin ${branch} 2>&1`);\n if (pushResult.exitCode !== 0) {\n console.warn(`[exe-provider] Git push failed on ${vmName}: ${pushResult.stderr || pushResult.stdout}`);\n return false;\n }\n\n console.log(`[exe-provider] Beads synced and pushed from ${vmName}`);\n return true;\n } catch (error: any) {\n console.error(`[exe-provider] Error syncing beads on ${vmName}:`, error.message);\n return false;\n }\n }\n\n /**\n * Query beads on a remote VM\n *\n * Runs bd search on the remote and returns results.\n */\n async queryBeads(vmName: string, searchTerm: string, workspacePath: string = '/workspace'): Promise<any[]> {\n try {\n const result = await this.ssh(vmName, `cd ${workspacePath} && bd search \"${searchTerm}\" --json 2>/dev/null || echo \"[]\"`);\n\n if (result.exitCode !== 0 || !result.stdout.trim()) {\n return [];\n }\n\n try {\n return JSON.parse(result.stdout.trim());\n } catch {\n return [];\n }\n } catch (error: any) {\n console.error(`[exe-provider] Error querying beads on ${vmName}:`, error.message);\n return [];\n }\n }\n\n /**\n * Configure Claude Code on a VM for autonomous operation\n *\n * Sets up:\n * - ~/.claude.json with bypass permissions and onboarding settings\n * - ~/.bashrc with proper terminal environment (TERM, LANG, etc.)\n *\n * This is required for remote agents to run without human interaction.\n */\n async configureClaudeCode(vmName: string): Promise<void> {\n // Configure Claude settings\n const setupScript = `\nimport json, os\npath = os.path.expanduser(\"~/.claude.json\")\ndata = {}\nif os.path.exists(path):\n with open(path, \"r\") as f:\n data = json.load(f)\ndata[\"bypassPermissionsModeAccepted\"] = True\ndata[\"hasCompletedOnboarding\"] = True\nwith open(path, \"w\") as f:\n json.dump(data, f, indent=2)\n`;\n const scriptBase64 = Buffer.from(setupScript).toString('base64');\n const result = await this.ssh(vmName, `echo '${scriptBase64}' | base64 -d | python3`);\n if (result.exitCode !== 0) {\n throw new Error(`Failed to configure Claude Code on ${vmName}: ${result.stderr}`);\n }\n\n // Configure terminal environment in .bashrc for proper rendering\n const termEnv = `\n# Panopticon terminal settings\nexport TERM=xterm-256color\nexport LANG=C.UTF-8\nexport LC_ALL=C.UTF-8\nexport COLORTERM=truecolor\n`;\n const termEnvBase64 = Buffer.from(termEnv).toString('base64');\n await this.ssh(vmName, `grep -q \"Panopticon terminal settings\" ~/.bashrc 2>/dev/null || echo '${termEnvBase64}' | base64 -d >> ~/.bashrc`);\n }\n\n /**\n * Copy essential skills from local ~/.panopticon/skills/ to remote VM ~/.claude/skills/\n *\n * Skills are read locally and written to the VM via base64-encoded SSH.\n * Only copies SKILL.md and resource files (not symlinks or deep directories).\n */\n async copySkillsToVm(vmName: string): Promise<void> {\n const essentialSkills = [\n 'beads',\n 'beads-completion-check',\n 'beads-panopticon-guide',\n 'work-complete',\n 'session-health',\n ];\n\n const skillsSourceDir = join(homedir(), '.panopticon', 'skills');\n if (!existsSync(skillsSourceDir)) {\n console.log(`[exe-provider] No skills source directory at ${skillsSourceDir}`);\n return;\n }\n\n // Create skills directory on remote\n await this.ssh(vmName, 'mkdir -p ~/.claude/skills');\n\n for (const skillName of essentialSkills) {\n const skillDir = join(skillsSourceDir, skillName);\n if (!existsSync(skillDir)) {\n continue;\n }\n\n try {\n // Create skill directory on remote\n await this.ssh(vmName, `mkdir -p ~/.claude/skills/${skillName}`);\n\n // Copy SKILL.md\n const skillMdPath = join(skillDir, 'SKILL.md');\n if (existsSync(skillMdPath)) {\n const content = readFileSync(skillMdPath, 'utf-8');\n const contentBase64 = Buffer.from(content).toString('base64');\n await this.ssh(vmName, `echo '${contentBase64}' | base64 -d > ~/.claude/skills/${skillName}/SKILL.md`);\n }\n\n // Copy resources/ directory if it exists\n const resourcesDir = join(skillDir, 'resources');\n if (existsSync(resourcesDir) && statSync(resourcesDir).isDirectory()) {\n await this.ssh(vmName, `mkdir -p ~/.claude/skills/${skillName}/resources`);\n const resourceFiles = readdirSync(resourcesDir).filter(f => f.endsWith('.md'));\n for (const resourceFile of resourceFiles) {\n const resourcePath = join(resourcesDir, resourceFile);\n if (statSync(resourcePath).isFile()) {\n const resourceContent = readFileSync(resourcePath, 'utf-8');\n const resourceBase64 = Buffer.from(resourceContent).toString('base64');\n await this.ssh(vmName, `echo '${resourceBase64}' | base64 -d > ~/.claude/skills/${skillName}/resources/${resourceFile}`);\n }\n }\n }\n\n console.log(`[exe-provider] Copied skill ${skillName} to ${vmName}`);\n } catch (error: any) {\n console.warn(`[exe-provider] Failed to copy skill ${skillName} to ${vmName}: ${error.message}`);\n }\n }\n }\n}\n\n/**\n * Factory function to create an ExeProvider with config from Panopticon settings\n */\nexport function createExeProvider(config?: ExeProviderConfig): ExeProvider {\n return new ExeProvider(config);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/lib/remote/exe-provider.ts"],"sourcesContent":["/**\n * exe.dev Remote Provider\n *\n * Implements the RemoteProvider interface for exe.dev cloud VMs.\n * exe.dev provides affordable dev VMs with persistent storage.\n *\n * exe.dev uses an SSH-based API:\n * - `ssh exe.dev` - Access the CLI\n * - `ssh exe.dev new` - Create a new VM\n * - `ssh exe.dev ls` - List VMs\n * - `ssh vmname.exe.xyz` - SSH into a VM\n *\n * Pricing (as of 2025):\n * - Individual: $20/month, 8GB RAM\n * - Team: $25/month/user, 8GB RAM\n * - Enterprise: $30/month/user, 16GB RAM\n *\n * @see https://exe.dev/docs\n */\n\nimport { exec, spawn } from 'child_process';\nimport { promisify } from 'util';\nimport { existsSync, readFileSync, readdirSync, statSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type {\n RemoteProvider,\n VmInfo,\n VmStatus,\n ExecResult,\n} from './interface.js';\n\nconst execAsync = promisify(exec);\n\nexport interface ExeProviderConfig {\n /** Shared infrastructure VM name (for postgres, redis, traefik) */\n infraVm?: string;\n}\n\n/**\n * Parse exe.dev ls output for VM list\n * Format appears to be: vmname (status info)\n */\nfunction parseVmList(output: string): VmInfo[] {\n const vms: VmInfo[] = [];\n const lines = output.trim().split('\\n');\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('Your VMs')) continue;\n\n // exe.dev format: \" • pan-pan-81-ws.exe.xyz - running (boldsoftware/exeuntu)\"\n // Extract VM name (without .exe.xyz suffix) and status\n const match = trimmed.match(/[•\\-]\\s*([a-z0-9-]+)\\.exe\\.xyz\\s*-\\s*(\\w+)/i);\n if (match) {\n const name = match[1];\n const statusText = match[2].toLowerCase();\n const status: VmStatus = statusText === 'running' ? 'running' :\n statusText === 'stopped' ? 'stopped' : 'unknown';\n vms.push({ name, status });\n }\n }\n\n return vms;\n}\n\n/**\n * Check if SSH key is configured for exe.dev\n */\nasync function canSshToExeDev(): Promise<boolean> {\n try {\n // Try a quick command - ssh exe.dev ls should work if authenticated\n const { stdout } = await execAsync('ssh -o BatchMode=yes -o ConnectTimeout=5 exe.dev ls 2>&1', {\n timeout: 10000,\n });\n // If we get output without permission denied, we're good\n return !stdout.includes('Permission denied') && !stdout.includes('Host key verification failed');\n } catch (error: any) {\n // Check if it's just an empty list (which is fine)\n if (error.stdout && !error.stdout.includes('Permission denied')) {\n return true;\n }\n return false;\n }\n}\n\nexport class ExeProvider implements RemoteProvider {\n readonly name = 'exe';\n private config: ExeProviderConfig;\n\n constructor(config: ExeProviderConfig = {}) {\n this.config = config;\n }\n\n /**\n * Check if user is authenticated with exe.dev\n * This checks if SSH key is configured and can connect\n */\n async isAuthenticated(): Promise<boolean> {\n return canSshToExeDev();\n }\n\n /**\n * Execute a command on the exe.dev CLI (via ssh exe.dev)\n */\n private async exeCmd(command: string): Promise<ExecResult> {\n try {\n const { stdout, stderr } = await execAsync(`ssh exe.dev ${command}`, {\n timeout: 60000,\n maxBuffer: 10 * 1024 * 1024,\n });\n return { stdout, stderr, exitCode: 0 };\n } catch (error: any) {\n return {\n stdout: error.stdout || '',\n stderr: error.stderr || error.message,\n exitCode: error.code || 1,\n };\n }\n }\n\n /**\n * Create a new VM on exe.dev\n */\n async createVm(name: string): Promise<VmInfo> {\n try {\n // exe.dev uses --name flag, and names must be valid hostnames\n const result = await this.exeCmd(`new --name=${name}`);\n\n if (result.exitCode !== 0) {\n throw new Error(`Failed to create VM: ${result.stderr}`);\n }\n\n // Wait a moment for VM to be ready\n await new Promise(resolve => setTimeout(resolve, 5000));\n\n return { name, status: 'running' };\n } catch (error: any) {\n throw new Error(`Failed to create VM ${name}: ${error.message}`);\n }\n }\n\n /**\n * Delete a VM on exe.dev\n */\n async deleteVm(name: string): Promise<void> {\n try {\n const result = await this.exeCmd(`rm ${name}`);\n\n if (result.exitCode !== 0 && !result.stderr.includes('not found')) {\n throw new Error(result.stderr);\n }\n } catch (error: any) {\n throw new Error(`Failed to delete VM ${name}: ${error.message}`);\n }\n }\n\n /**\n * List all VMs\n */\n async listVms(): Promise<VmInfo[]> {\n try {\n const result = await this.exeCmd('ls');\n\n if (result.exitCode !== 0) {\n throw new Error(result.stderr);\n }\n\n return parseVmList(result.stdout);\n } catch (error: any) {\n throw new Error(`Failed to list VMs: ${error.message}`);\n }\n }\n\n /**\n * Get VM status\n */\n async getStatus(name: string): Promise<VmStatus> {\n try {\n const vms = await this.listVms();\n const vm = vms.find(v => v.name === name);\n return vm?.status || 'unknown';\n } catch {\n return 'unknown';\n }\n }\n\n /**\n * Get detailed VM info\n */\n async getVmInfo(name: string): Promise<VmInfo | null> {\n try {\n const vms = await this.listVms();\n return vms.find(v => v.name === name) || null;\n } catch {\n return null;\n }\n }\n\n /**\n * Start a stopped VM\n * Note: exe.dev VMs are persistent and always running\n */\n async startVm(name: string): Promise<void> {\n // exe.dev VMs don't have start/stop - they're always running\n // Check if VM exists\n const status = await this.getStatus(name);\n if (status === 'unknown') {\n throw new Error(`VM ${name} not found`);\n }\n }\n\n /**\n * Stop a running VM\n * Note: exe.dev VMs are persistent - use rm to delete\n */\n async stopVm(name: string): Promise<void> {\n // exe.dev doesn't have stop - VMs are always running or deleted\n // We can implement \"stop\" as a no-op or throw\n console.warn(`exe.dev VMs cannot be stopped, only deleted. VM ${name} continues running.`);\n }\n\n /**\n * Execute a command on VM via SSH\n * SSH to vmname.exe.xyz\n */\n async ssh(vm: string, command: string): Promise<ExecResult> {\n try {\n const sshHost = `${vm}.exe.xyz`;\n const escapedCmd = command.replace(/\"/g, '\\\\\"');\n\n // Use -A for agent forwarding so the VM can access GitHub with user's SSH keys\n const { stdout, stderr } = await execAsync(`ssh -A ${sshHost} \"${escapedCmd}\"`, {\n timeout: 300000, // 5 minutes\n maxBuffer: 50 * 1024 * 1024, // 50MB\n });\n\n return { stdout, stderr, exitCode: 0 };\n } catch (error: any) {\n return {\n stdout: error.stdout || '',\n stderr: error.stderr || error.message,\n exitCode: error.code || 1,\n };\n }\n }\n\n /**\n * Execute a command and stream output\n */\n async *sshStream(vm: string, command: string): AsyncIterable<string> {\n const sshHost = `${vm}.exe.xyz`;\n // Use -A for agent forwarding\n const child = spawn('ssh', ['-A', sshHost, command], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n for await (const chunk of child.stdout) {\n yield chunk.toString();\n }\n\n for await (const chunk of child.stderr) {\n yield chunk.toString();\n }\n }\n\n /**\n * Copy file to VM\n */\n async copyToVm(vm: string, localPath: string, remotePath: string): Promise<void> {\n try {\n const sshHost = `${vm}.exe.xyz`;\n await execAsync(`scp \"${localPath}\" ${sshHost}:${remotePath}`, { timeout: 300000 });\n } catch (error: any) {\n throw new Error(`Failed to copy ${localPath} to ${vm}:${remotePath}: ${error.message}`);\n }\n }\n\n /**\n * Copy file from VM\n */\n async copyFromVm(vm: string, remotePath: string, localPath: string): Promise<void> {\n try {\n const sshHost = `${vm}.exe.xyz`;\n await execAsync(`scp ${sshHost}:${remotePath} \"${localPath}\"`, { timeout: 300000 });\n } catch (error: any) {\n throw new Error(`Failed to copy ${vm}:${remotePath} to ${localPath}: ${error.message}`);\n }\n }\n\n /**\n * Expose a port on VM (returns public URL)\n *\n * exe.dev provides automatic HTTPS URLs for services:\n * https://vmname.exe.xyz:PORT\n */\n async exposePort(vm: string, port: number): Promise<string> {\n // exe.dev automatically exposes all ports via HTTPS\n // The URL format is: https://vmname.exe.xyz:PORT\n // Or for default HTTP (80/443): https://vmname.exe.xyz\n if (port === 80 || port === 443) {\n return `https://${vm}.exe.xyz`;\n }\n return `https://${vm}.exe.xyz:${port}`;\n }\n\n /**\n * Create SSH tunnel to VM\n */\n async tunnel(vm: string, remotePort: number, localPort: number): Promise<{ close: () => void }> {\n const sshHost = `${vm}.exe.xyz`;\n const child = spawn('ssh', ['-N', '-L', `${localPort}:localhost:${remotePort}`, sshHost], {\n stdio: 'ignore',\n detached: true,\n });\n\n child.unref();\n\n return {\n close: () => {\n child.kill();\n },\n };\n }\n\n /**\n * Get the configured infrastructure VM name\n */\n getInfraVm(): string | undefined {\n return this.config.infraVm;\n }\n\n /**\n * Initialize the shared infrastructure VM\n *\n * Sets up postgres, redis, and traefik on a dedicated VM.\n */\n async initInfrastructure(vmName: string): Promise<void> {\n // Check if VM exists\n let status = await this.getStatus(vmName);\n\n if (status === 'unknown') {\n // Create the VM\n await this.createVm(vmName);\n // Wait for it to be ready\n await new Promise(resolve => setTimeout(resolve, 10000));\n }\n\n // Install Docker if not present\n const dockerCheck = await this.ssh(vmName, 'which docker');\n if (dockerCheck.exitCode !== 0) {\n await this.ssh(vmName, 'curl -fsSL https://get.docker.com | sh');\n await this.ssh(vmName, 'sudo usermod -aG docker $USER');\n }\n\n // Create docker-compose.yml for shared services\n const composeContent = `\nversion: '3.8'\nservices:\n postgres:\n image: postgres:16\n restart: unless-stopped\n environment:\n POSTGRES_PASSWORD: \\${PAN_POSTGRES_PASSWORD:-panopticon}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n ports:\n - \"5432:5432\"\n\n redis:\n image: redis:7\n restart: unless-stopped\n volumes:\n - redis_data:/data\n ports:\n - \"6379:6379\"\n\n traefik:\n image: traefik:v3.0\n restart: unless-stopped\n ports:\n - \"80:80\"\n - \"443:443\"\n - \"8080:8080\"\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n command:\n - --api.dashboard=true\n - --providers.docker=true\n - --providers.docker.exposedbydefault=false\n - --entrypoints.web.address=:80\n - --entrypoints.websecure.address=:443\n\nvolumes:\n postgres_data:\n redis_data:\n`;\n\n // Write compose file and start services\n await this.ssh(vmName, `mkdir -p /opt/panopticon && cat > /opt/panopticon/docker-compose.yml << 'COMPOSE_EOF'\n${composeContent}\nCOMPOSE_EOF`);\n\n await this.ssh(vmName, 'cd /opt/panopticon && docker compose up -d');\n\n // Store as infra VM\n this.config.infraVm = vmName;\n }\n\n /**\n * Sync Claude Code credentials from local macOS Keychain to remote VM\n *\n * This should be called before spawning agents to ensure fresh credentials.\n * Credentials can expire, and re-running this ensures the VM has valid auth.\n *\n * @returns true if credentials were synced, false if not available\n */\n async syncClaudeCredentials(vmName: string): Promise<boolean> {\n try {\n // Get credentials from macOS Keychain\n const { stdout: credentials } = await execAsync(\n 'security find-generic-password -s \"Claude Code-credentials\" -w 2>/dev/null',\n { encoding: 'utf-8' }\n );\n\n if (!credentials || !credentials.trim()) {\n return false;\n }\n\n // Ensure ~/.claude directory exists\n await this.ssh(vmName, 'mkdir -p ~/.claude');\n\n // Send credentials to VM (base64 encode to handle special characters)\n const credsBase64 = Buffer.from(credentials.trim()).toString('base64');\n const result = await this.ssh(vmName, `echo '${credsBase64}' | base64 -d > ~/.claude/.credentials.json`);\n\n return result.exitCode === 0;\n } catch (error: any) {\n // Credentials not found in Keychain or other error\n return false;\n }\n }\n\n /**\n * Sync GitHub CLI authentication from local macOS to remote VM\n *\n * GitHub CLI on macOS stores tokens in Keychain. On Linux VMs, it stores\n * them directly in ~/.config/gh/hosts.yml. This extracts from Keychain\n * and writes to the VM's config file.\n *\n * @returns true if auth was synced, false if not available\n */\n async syncGitHubAuth(vmName: string): Promise<boolean> {\n try {\n // Get GitHub token from macOS Keychain\n const { stdout: tokenRaw } = await execAsync(\n 'security find-generic-password -s \"gh:github.com\" -w 2>/dev/null',\n { encoding: 'utf-8' }\n );\n\n if (!tokenRaw || !tokenRaw.trim()) {\n return false;\n }\n\n // The token may be base64-encoded with go-keyring prefix\n let token = tokenRaw.trim();\n if (token.startsWith('go-keyring-base64:')) {\n token = Buffer.from(token.replace('go-keyring-base64:', ''), 'base64').toString('utf-8');\n }\n\n // Get GitHub username from local config\n let username = 'user';\n try {\n const { stdout: configOutput } = await execAsync('cat ~/.config/gh/hosts.yml 2>/dev/null', { encoding: 'utf-8' });\n const userMatch = configOutput.match(/user:\\s*(\\S+)/);\n if (userMatch) {\n username = userMatch[1];\n }\n } catch {\n // Use default\n }\n\n // Create hosts.yml content for Linux (token stored directly in file)\n const hostsYml = `github.com:\n oauth_token: ${token}\n git_protocol: ssh\n user: ${username}\n`;\n\n // Ensure ~/.config/gh directory exists and write hosts.yml\n await this.ssh(vmName, 'mkdir -p ~/.config/gh');\n const hostsBase64 = Buffer.from(hostsYml).toString('base64');\n const result = await this.ssh(vmName, `echo '${hostsBase64}' | base64 -d > ~/.config/gh/hosts.yml && chmod 600 ~/.config/gh/hosts.yml`);\n\n return result.exitCode === 0;\n } catch (error: any) {\n // Token not found in Keychain or other error\n return false;\n }\n }\n\n /**\n * Sync GitLab CLI (glab) authentication to a remote VM\n *\n * Copies the glab config from local machine to the remote VM.\n * This allows the remote agent to use glab commands for MRs, etc.\n */\n async syncGitLabAuth(vmName: string): Promise<boolean> {\n try {\n // glab stores config in ~/.config/glab-cli/config.yml\n const glabConfigPath = join(homedir(), '.config', 'glab-cli', 'config.yml');\n\n if (!existsSync(glabConfigPath)) {\n console.log(`[exe-provider] No glab config found at ${glabConfigPath}`);\n return false;\n }\n\n // Read local glab config\n const glabConfig = readFileSync(glabConfigPath, 'utf-8');\n\n // Ensure ~/.config/glab-cli directory exists on remote\n await this.ssh(vmName, 'mkdir -p ~/.config/glab-cli');\n\n // Write config to remote\n const configBase64 = Buffer.from(glabConfig).toString('base64');\n const result = await this.ssh(vmName, `echo '${configBase64}' | base64 -d > ~/.config/glab-cli/config.yml && chmod 600 ~/.config/glab-cli/config.yml`);\n\n if (result.exitCode === 0) {\n console.log(`[exe-provider] GitLab auth synced to ${vmName}`);\n return true;\n }\n\n return false;\n } catch (error: any) {\n console.error(`[exe-provider] Failed to sync GitLab auth: ${error.message}`);\n return false;\n }\n }\n\n /**\n * Sync all credentials needed for remote workspace operation\n *\n * This syncs:\n * - Claude Code OAuth credentials\n * - GitHub CLI authentication\n *\n * Call this before spawning agents to ensure fresh credentials.\n *\n * @returns object with sync status for each credential type\n */\n async syncAllCredentials(vmName: string): Promise<{\n claude: boolean;\n github: boolean;\n }> {\n const [claude, github] = await Promise.all([\n this.syncClaudeCredentials(vmName),\n this.syncGitHubAuth(vmName),\n ]);\n\n return { claude, github };\n }\n\n /**\n * Install beads CLI (bd) on a remote VM\n *\n * Beads is required for planning agents to create task breakdowns.\n * Downloads the binary from GitHub releases since npm/node are often\n * not available on exe.dev VMs.\n *\n * @returns true if bd is available (already installed or just installed)\n */\n async installBeads(vmName: string): Promise<boolean> {\n const FALLBACK_BEADS_VERSION = '0.49.4';\n\n try {\n // Check if bd is already installed\n const checkResult = await this.ssh(vmName, 'which bd');\n if (checkResult.exitCode === 0 && checkResult.stdout.trim()) {\n console.log(`[exe-provider] bd already installed on ${vmName}`);\n return true;\n }\n\n console.log(`[exe-provider] Installing bd (beads CLI) on ${vmName}...`);\n\n // Download from GitHub releases - exe.dev VMs typically don't have npm/node\n // but do have curl and sudo access.\n // Resolve the latest version dynamically with fallback to known-good version.\n const installCmd = `\n cd /tmp && \\\n BEADS_VERSION=$(curl -sI \"https://github.com/steveyegge/beads/releases/latest\" 2>/dev/null | grep -i '^location:' | sed 's|.*/v||' | tr -d '\\\\r\\\\n') && \\\n if [ -z \"$BEADS_VERSION\" ]; then BEADS_VERSION=\"${FALLBACK_BEADS_VERSION}\"; echo \"Using fallback version: $BEADS_VERSION\"; else echo \"Detected version: $BEADS_VERSION\"; fi && \\\n echo \"Downloading beads v$BEADS_VERSION...\" && \\\n curl -sL \"https://github.com/steveyegge/beads/releases/download/v\\${BEADS_VERSION}/beads_\\${BEADS_VERSION}_linux_amd64.tar.gz\" -o beads.tar.gz && \\\n tar -xzf beads.tar.gz && \\\n sudo mv bd /usr/local/bin/ && \\\n rm -f beads.tar.gz CHANGELOG.md LICENSE README.md\n `.trim().replace(/\\n\\s+/g, ' ');\n\n const installResult = await this.ssh(vmName, installCmd);\n if (installResult.stderr) {\n console.log(`[exe-provider] bd install stderr on ${vmName}: ${installResult.stderr}`);\n }\n if (installResult.stdout) {\n console.log(`[exe-provider] bd install output on ${vmName}: ${installResult.stdout.trim()}`);\n }\n\n // Verify installation\n const verifyResult = await this.ssh(vmName, 'which bd && bd --version');\n if (verifyResult.exitCode === 0 && verifyResult.stdout.includes('bd version')) {\n console.log(`[exe-provider] bd installed successfully on ${vmName}: ${verifyResult.stdout.trim()}`);\n return true;\n }\n\n console.warn(`[exe-provider] Failed to install bd on ${vmName} - verify exitCode: ${verifyResult.exitCode}, stdout: ${verifyResult.stdout}, stderr: ${verifyResult.stderr}`);\n return false;\n } catch (error: any) {\n console.error(`[exe-provider] Error installing bd on ${vmName}:`, error.message);\n return false;\n }\n }\n\n /**\n * Initialize beads in a workspace on a remote VM\n *\n * Creates .beads/ directory and initializes the database if needed.\n */\n async initBeads(vmName: string, workspacePath: string = '/workspace'): Promise<boolean> {\n try {\n // Check if .beads already exists\n const checkResult = await this.ssh(vmName, `test -d ${workspacePath}/.beads && echo \"exists\"`);\n if (checkResult.stdout.includes('exists')) {\n console.log(`[exe-provider] .beads already initialized on ${vmName}`);\n return true;\n }\n\n console.log(`[exe-provider] Initializing beads on ${vmName}...`);\n\n // Initialize beads in the workspace\n const initResult = await this.ssh(vmName, `cd ${workspacePath} && bd init 2>/dev/null || true`);\n\n // Verify\n const verifyResult = await this.ssh(vmName, `test -d ${workspacePath}/.beads && echo \"ok\"`);\n if (verifyResult.stdout.includes('ok')) {\n console.log(`[exe-provider] beads initialized on ${vmName}`);\n return true;\n }\n\n // If bd init failed, create the directory manually\n await this.ssh(vmName, `mkdir -p ${workspacePath}/.beads`);\n return true;\n } catch (error: any) {\n console.error(`[exe-provider] Error initializing beads on ${vmName}:`, error.message);\n return false;\n }\n }\n\n /**\n * Sync beads from remote VM to git\n *\n * Exports beads database to JSONL, commits, and pushes.\n * This should be called after planning completes to persist tasks.\n */\n async syncBeadsToGit(vmName: string, workspacePath: string = '/workspace', commitMessage?: string): Promise<boolean> {\n try {\n console.log(`[exe-provider] Syncing beads to git on ${vmName}...`);\n\n // Export beads to JSONL\n const syncResult = await this.ssh(vmName, `cd ${workspacePath} && bd sync 2>/dev/null || true`);\n\n // Check if there are changes to commit\n const statusResult = await this.ssh(vmName, `cd ${workspacePath} && git status --porcelain .beads/ .planning/ 2>/dev/null`);\n\n if (!statusResult.stdout.trim()) {\n console.log(`[exe-provider] No beads changes to commit on ${vmName}`);\n return true;\n }\n\n // Add and commit\n const msg = commitMessage || 'Sync beads from planning session';\n await this.ssh(vmName, `cd ${workspacePath} && git add .beads/ && git add -f .planning/ 2>/dev/null || true`);\n await this.ssh(vmName, `cd ${workspacePath} && git commit -m \"${msg}\" 2>/dev/null || true`);\n\n // Push - use -u to set upstream if not already set\n const branchResult = await this.ssh(vmName, `cd ${workspacePath} && git branch --show-current`);\n const branch = branchResult.stdout.trim() || 'main';\n const pushResult = await this.ssh(vmName, `cd ${workspacePath} && git push -u origin ${branch} 2>&1`);\n if (pushResult.exitCode !== 0) {\n console.warn(`[exe-provider] Git push failed on ${vmName}: ${pushResult.stderr || pushResult.stdout}`);\n return false;\n }\n\n console.log(`[exe-provider] Beads synced and pushed from ${vmName}`);\n return true;\n } catch (error: any) {\n console.error(`[exe-provider] Error syncing beads on ${vmName}:`, error.message);\n return false;\n }\n }\n\n /**\n * Query beads on a remote VM\n *\n * Runs bd search on the remote and returns results.\n */\n async queryBeads(vmName: string, searchTerm: string, workspacePath: string = '/workspace'): Promise<any[]> {\n try {\n const result = await this.ssh(vmName, `cd ${workspacePath} && bd search \"${searchTerm}\" --json 2>/dev/null || echo \"[]\"`);\n\n if (result.exitCode !== 0 || !result.stdout.trim()) {\n return [];\n }\n\n try {\n return JSON.parse(result.stdout.trim());\n } catch {\n return [];\n }\n } catch (error: any) {\n console.error(`[exe-provider] Error querying beads on ${vmName}:`, error.message);\n return [];\n }\n }\n\n /**\n * Configure Claude Code on a VM for autonomous operation\n *\n * Sets up:\n * - ~/.claude.json with bypass permissions and onboarding settings\n * - ~/.bashrc with proper terminal environment (TERM, LANG, etc.)\n *\n * This is required for remote agents to run without human interaction.\n */\n async configureClaudeCode(vmName: string): Promise<void> {\n // Configure Claude settings\n const setupScript = `\nimport json, os\npath = os.path.expanduser(\"~/.claude.json\")\ndata = {}\nif os.path.exists(path):\n with open(path, \"r\") as f:\n data = json.load(f)\ndata[\"bypassPermissionsModeAccepted\"] = True\ndata[\"hasCompletedOnboarding\"] = True\nwith open(path, \"w\") as f:\n json.dump(data, f, indent=2)\n`;\n const scriptBase64 = Buffer.from(setupScript).toString('base64');\n const result = await this.ssh(vmName, `echo '${scriptBase64}' | base64 -d | python3`);\n if (result.exitCode !== 0) {\n throw new Error(`Failed to configure Claude Code on ${vmName}: ${result.stderr}`);\n }\n\n // Configure terminal environment in .bashrc for proper rendering\n const termEnv = `\n# Panopticon terminal settings\nexport TERM=xterm-256color\nexport LANG=C.UTF-8\nexport LC_ALL=C.UTF-8\nexport COLORTERM=truecolor\n`;\n const termEnvBase64 = Buffer.from(termEnv).toString('base64');\n await this.ssh(vmName, `grep -q \"Panopticon terminal settings\" ~/.bashrc 2>/dev/null || echo '${termEnvBase64}' | base64 -d >> ~/.bashrc`);\n }\n\n /**\n * Copy essential skills from local ~/.panopticon/skills/ to remote VM ~/.claude/skills/\n *\n * Skills are read locally and written to the VM via base64-encoded SSH.\n * Only copies SKILL.md and resource files (not symlinks or deep directories).\n */\n async copySkillsToVm(vmName: string): Promise<void> {\n const essentialSkills = [\n 'beads',\n 'beads-completion-check',\n 'beads-panopticon-guide',\n 'work-complete',\n 'session-health',\n ];\n\n const skillsSourceDir = join(homedir(), '.panopticon', 'skills');\n if (!existsSync(skillsSourceDir)) {\n console.log(`[exe-provider] No skills source directory at ${skillsSourceDir}`);\n return;\n }\n\n // Create skills directory on remote\n await this.ssh(vmName, 'mkdir -p ~/.claude/skills');\n\n for (const skillName of essentialSkills) {\n const skillDir = join(skillsSourceDir, skillName);\n if (!existsSync(skillDir)) {\n continue;\n }\n\n try {\n // Create skill directory on remote\n await this.ssh(vmName, `mkdir -p ~/.claude/skills/${skillName}`);\n\n // Copy SKILL.md\n const skillMdPath = join(skillDir, 'SKILL.md');\n if (existsSync(skillMdPath)) {\n const content = readFileSync(skillMdPath, 'utf-8');\n const contentBase64 = Buffer.from(content).toString('base64');\n await this.ssh(vmName, `echo '${contentBase64}' | base64 -d > ~/.claude/skills/${skillName}/SKILL.md`);\n }\n\n // Copy resources/ directory if it exists\n const resourcesDir = join(skillDir, 'resources');\n if (existsSync(resourcesDir) && statSync(resourcesDir).isDirectory()) {\n await this.ssh(vmName, `mkdir -p ~/.claude/skills/${skillName}/resources`);\n const resourceFiles = readdirSync(resourcesDir).filter(f => f.endsWith('.md'));\n for (const resourceFile of resourceFiles) {\n const resourcePath = join(resourcesDir, resourceFile);\n if (statSync(resourcePath).isFile()) {\n const resourceContent = readFileSync(resourcePath, 'utf-8');\n const resourceBase64 = Buffer.from(resourceContent).toString('base64');\n await this.ssh(vmName, `echo '${resourceBase64}' | base64 -d > ~/.claude/skills/${skillName}/resources/${resourceFile}`);\n }\n }\n }\n\n console.log(`[exe-provider] Copied skill ${skillName} to ${vmName}`);\n } catch (error: any) {\n console.warn(`[exe-provider] Failed to copy skill ${skillName} to ${vmName}: ${error.message}`);\n }\n }\n }\n}\n\n/**\n * Factory function to create an ExeProvider with config from Panopticon settings\n */\nexport function createExeProvider(config?: ExeProviderConfig): ExeProvider {\n return new ExeProvider(config);\n}\n"],"mappings":"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","names":[]}
|