panopticon-cli 0.5.4 → 0.5.6

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 (119) hide show
  1. package/dist/{agents-HNMF52RM.js → agents-5HWTDR4S.js} +12 -9
  2. package/dist/archive-planning-U3AZAKWI.js +16 -0
  3. package/dist/{chunk-KBHRXV5T.js → chunk-43F4LDZ4.js} +3 -3
  4. package/dist/chunk-6OYUJ4AJ.js +146 -0
  5. package/dist/chunk-6OYUJ4AJ.js.map +1 -0
  6. package/dist/{chunk-MOPGR3CL.js → chunk-AAP4G6U7.js} +1 -1
  7. package/dist/chunk-AAP4G6U7.js.map +1 -0
  8. package/dist/{chunk-4HST45MO.js → chunk-BYWVPPAZ.js} +19 -12
  9. package/dist/chunk-BYWVPPAZ.js.map +1 -0
  10. package/dist/{chunk-CFCUOV3Q.js → chunk-DMRTN432.js} +4 -1
  11. package/dist/chunk-DMRTN432.js.map +1 -0
  12. package/dist/{chunk-HOGYHJ2G.js → chunk-DW3PKGIS.js} +2 -2
  13. package/dist/{chunk-KY2E2Q3T.js → chunk-FUUP55PE.js} +104 -46
  14. package/dist/chunk-FUUP55PE.js.map +1 -0
  15. package/dist/chunk-GUV2EPBG.js +692 -0
  16. package/dist/chunk-GUV2EPBG.js.map +1 -0
  17. package/dist/{chunk-44EOY2ZL.js → chunk-HHL3AWXA.js} +46 -2
  18. package/dist/chunk-HHL3AWXA.js.map +1 -0
  19. package/dist/{chunk-6N2KBSJA.js → chunk-IZIXJYXZ.js} +40 -6
  20. package/dist/chunk-IZIXJYXZ.js.map +1 -0
  21. package/dist/chunk-MJXYTGK5.js +64 -0
  22. package/dist/chunk-MJXYTGK5.js.map +1 -0
  23. package/dist/chunk-OJF4QS3S.js +269 -0
  24. package/dist/chunk-OJF4QS3S.js.map +1 -0
  25. package/dist/{chunk-FQ66DECN.js → chunk-QAJAJBFW.js} +1 -1
  26. package/dist/chunk-QAJAJBFW.js.map +1 -0
  27. package/dist/chunk-R4KPLLRB.js +36 -0
  28. package/dist/chunk-R4KPLLRB.js.map +1 -0
  29. package/dist/{chunk-DFNVHK3N.js → chunk-SUM2WVPF.js} +4 -4
  30. package/dist/{chunk-T7BBPDEJ.js → chunk-UKSGE6RH.js} +45 -15
  31. package/dist/chunk-UKSGE6RH.js.map +1 -0
  32. package/dist/chunk-W2OTF6OS.js +201 -0
  33. package/dist/chunk-W2OTF6OS.js.map +1 -0
  34. package/dist/chunk-WEQW3EAT.js +78 -0
  35. package/dist/chunk-WEQW3EAT.js.map +1 -0
  36. package/dist/{chunk-ID4OYXVH.js → chunk-WJJ3ZIQ6.js} +112 -45
  37. package/dist/chunk-WJJ3ZIQ6.js.map +1 -0
  38. package/dist/chunk-YAAT66RT.js +70 -0
  39. package/dist/chunk-YAAT66RT.js.map +1 -0
  40. package/dist/{chunk-RLZQB7HS.js → chunk-ZMJFEHGF.js} +13 -1
  41. package/dist/chunk-ZMJFEHGF.js.map +1 -0
  42. package/dist/{chunk-HRU7S4TA.js → chunk-ZN5RHWGR.js} +18 -208
  43. package/dist/{chunk-HRU7S4TA.js.map → chunk-ZN5RHWGR.js.map} +1 -1
  44. package/dist/{chunk-ZTYHZMEC.js → chunk-ZWZNEA26.js} +2 -2
  45. package/dist/clean-planning-7Z5YY64X.js +9 -0
  46. package/dist/cli/index.js +1301 -2142
  47. package/dist/cli/index.js.map +1 -1
  48. package/dist/close-issue-CTZK777I.js +9 -0
  49. package/dist/compact-beads-72SHALOL.js +9 -0
  50. package/dist/{config-4CJNUE3O.js → config-FFTMBVHM.js} +2 -2
  51. package/dist/dashboard/public/assets/{index-DSvt5pPn.css → index-Bx4NCn9A.css} +1 -1
  52. package/dist/dashboard/public/assets/index-Db9NOz4z.js +756 -0
  53. package/dist/dashboard/public/index.html +3 -2
  54. package/dist/dashboard/server.js +34714 -34296
  55. package/dist/{feedback-writer-T43PI5S2.js → feedback-writer-T2WCT6EZ.js} +2 -2
  56. package/dist/{hume-CKJJ3OUU.js → hume-GVTB5BKW.js} +3 -3
  57. package/dist/index.d.ts +24 -16
  58. package/dist/index.js +4 -4
  59. package/dist/label-cleanup-4HJVX6NP.js +103 -0
  60. package/dist/label-cleanup-4HJVX6NP.js.map +1 -0
  61. package/dist/merge-agent-WM7ZKUET.js +1725 -0
  62. package/dist/merge-agent-WM7ZKUET.js.map +1 -0
  63. package/dist/{projects-KVM3MN3Y.js → projects-3CRF57ZU.js} +2 -2
  64. package/dist/{rally-RKFSWC7E.js → rally-LBY24P4C.js} +2 -2
  65. package/dist/{remote-agents-ULPD6C5U.js → remote-agents-3NZPSHYG.js} +2 -3
  66. package/dist/{remote-workspace-XX6ARE6I.js → remote-workspace-M4IULGFZ.js} +24 -49
  67. package/dist/remote-workspace-M4IULGFZ.js.map +1 -0
  68. package/dist/{review-status-XKUKZF6J.js → review-status-J2YJGL3E.js} +2 -2
  69. package/dist/{specialist-context-C66TEMXS.js → specialist-context-74RQF5SR.js} +7 -5
  70. package/dist/{specialist-context-C66TEMXS.js.map → specialist-context-74RQF5SR.js.map} +1 -1
  71. package/dist/{specialist-logs-CJKXM3SR.js → specialist-logs-T5GW7CSU.js} +6 -4
  72. package/dist/{specialists-NXYD4Z62.js → specialists-HTYYFXHQ.js} +6 -4
  73. package/dist/specialists-HTYYFXHQ.js.map +1 -0
  74. package/dist/tmux-X2I5SAIJ.js +31 -0
  75. package/dist/tmux-X2I5SAIJ.js.map +1 -0
  76. package/dist/{traefik-5GL3Q7DJ.js → traefik-QXLZ4PO2.js} +4 -4
  77. package/dist/traefik-QXLZ4PO2.js.map +1 -0
  78. package/dist/{tunnel-BKC7KLBX.js → tunnel-7IOSRZVH.js} +3 -3
  79. package/dist/tunnel-7IOSRZVH.js.map +1 -0
  80. package/dist/{workspace-manager-ALBR62AS.js → workspace-manager-G6TTBPC3.js} +6 -6
  81. package/dist/workspace-manager-G6TTBPC3.js.map +1 -0
  82. package/package.json +2 -2
  83. package/scripts/build-cost-script.mjs +17 -0
  84. package/scripts/heartbeat-hook +28 -8
  85. package/scripts/record-cost-event.js +46 -7
  86. package/scripts/record-cost-event.ts +2 -1
  87. package/dist/chunk-44EOY2ZL.js.map +0 -1
  88. package/dist/chunk-4HST45MO.js.map +0 -1
  89. package/dist/chunk-565HZ6VV.js +0 -159
  90. package/dist/chunk-565HZ6VV.js.map +0 -1
  91. package/dist/chunk-6N2KBSJA.js.map +0 -1
  92. package/dist/chunk-CFCUOV3Q.js.map +0 -1
  93. package/dist/chunk-FQ66DECN.js.map +0 -1
  94. package/dist/chunk-ID4OYXVH.js.map +0 -1
  95. package/dist/chunk-KY2E2Q3T.js.map +0 -1
  96. package/dist/chunk-MOPGR3CL.js.map +0 -1
  97. package/dist/chunk-RLZQB7HS.js.map +0 -1
  98. package/dist/chunk-T7BBPDEJ.js.map +0 -1
  99. package/dist/chunk-ZDNQFWR5.js +0 -650
  100. package/dist/chunk-ZDNQFWR5.js.map +0 -1
  101. package/dist/dashboard/public/assets/index-DA6pnizT.js +0 -767
  102. package/dist/remote-workspace-XX6ARE6I.js.map +0 -1
  103. /package/dist/{agents-HNMF52RM.js.map → agents-5HWTDR4S.js.map} +0 -0
  104. /package/dist/{config-4CJNUE3O.js.map → archive-planning-U3AZAKWI.js.map} +0 -0
  105. /package/dist/{chunk-KBHRXV5T.js.map → chunk-43F4LDZ4.js.map} +0 -0
  106. /package/dist/{chunk-HOGYHJ2G.js.map → chunk-DW3PKGIS.js.map} +0 -0
  107. /package/dist/{chunk-DFNVHK3N.js.map → chunk-SUM2WVPF.js.map} +0 -0
  108. /package/dist/{chunk-ZTYHZMEC.js.map → chunk-ZWZNEA26.js.map} +0 -0
  109. /package/dist/{hume-CKJJ3OUU.js.map → clean-planning-7Z5YY64X.js.map} +0 -0
  110. /package/dist/{projects-KVM3MN3Y.js.map → close-issue-CTZK777I.js.map} +0 -0
  111. /package/dist/{rally-RKFSWC7E.js.map → compact-beads-72SHALOL.js.map} +0 -0
  112. /package/dist/{remote-agents-ULPD6C5U.js.map → config-FFTMBVHM.js.map} +0 -0
  113. /package/dist/{feedback-writer-T43PI5S2.js.map → feedback-writer-T2WCT6EZ.js.map} +0 -0
  114. /package/dist/{review-status-XKUKZF6J.js.map → hume-GVTB5BKW.js.map} +0 -0
  115. /package/dist/{specialist-logs-CJKXM3SR.js.map → projects-3CRF57ZU.js.map} +0 -0
  116. /package/dist/{specialists-NXYD4Z62.js.map → rally-LBY24P4C.js.map} +0 -0
  117. /package/dist/{traefik-5GL3Q7DJ.js.map → remote-agents-3NZPSHYG.js.map} +0 -0
  118. /package/dist/{tunnel-BKC7KLBX.js.map → review-status-J2YJGL3E.js.map} +0 -0
  119. /package/dist/{workspace-manager-ALBR62AS.js.map → specialist-logs-T5GW7CSU.js.map} +0 -0
@@ -1,650 +0,0 @@
1
- import {
2
- __esm,
3
- init_esm_shims
4
- } from "./chunk-ZHC57RCV.js";
5
-
6
- // src/lib/remote/exe-provider.ts
7
- import { exec, spawn } from "child_process";
8
- import { promisify } from "util";
9
- import { existsSync, readFileSync, readdirSync, statSync } from "fs";
10
- import { join } from "path";
11
- import { homedir } from "os";
12
- function parseVmList(output) {
13
- const vms = [];
14
- const lines = output.trim().split("\n");
15
- for (const line of lines) {
16
- const trimmed = line.trim();
17
- if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("Your VMs")) continue;
18
- const match = trimmed.match(/[•\-]\s*([a-z0-9-]+)\.exe\.xyz\s*-\s*(\w+)/i);
19
- if (match) {
20
- const name = match[1];
21
- const statusText = match[2].toLowerCase();
22
- const status = statusText === "running" ? "running" : statusText === "stopped" ? "stopped" : "unknown";
23
- vms.push({ name, status });
24
- }
25
- }
26
- return vms;
27
- }
28
- async function canSshToExeDev() {
29
- try {
30
- const { stdout } = await execAsync("ssh -o BatchMode=yes -o ConnectTimeout=5 exe.dev ls 2>&1", {
31
- timeout: 1e4
32
- });
33
- return !stdout.includes("Permission denied") && !stdout.includes("Host key verification failed");
34
- } catch (error) {
35
- if (error.stdout && !error.stdout.includes("Permission denied")) {
36
- return true;
37
- }
38
- return false;
39
- }
40
- }
41
- function createExeProvider(config) {
42
- return new ExeProvider(config);
43
- }
44
- var execAsync, ExeProvider;
45
- var init_exe_provider = __esm({
46
- "src/lib/remote/exe-provider.ts"() {
47
- "use strict";
48
- init_esm_shims();
49
- execAsync = promisify(exec);
50
- ExeProvider = class {
51
- name = "exe";
52
- config;
53
- constructor(config = {}) {
54
- this.config = config;
55
- }
56
- /**
57
- * Check if user is authenticated with exe.dev
58
- * This checks if SSH key is configured and can connect
59
- */
60
- async isAuthenticated() {
61
- return canSshToExeDev();
62
- }
63
- /**
64
- * Execute a command on the exe.dev CLI (via ssh exe.dev)
65
- */
66
- async exeCmd(command) {
67
- try {
68
- const { stdout, stderr } = await execAsync(`ssh exe.dev ${command}`, {
69
- timeout: 6e4,
70
- maxBuffer: 10 * 1024 * 1024
71
- });
72
- return { stdout, stderr, exitCode: 0 };
73
- } catch (error) {
74
- return {
75
- stdout: error.stdout || "",
76
- stderr: error.stderr || error.message,
77
- exitCode: error.code || 1
78
- };
79
- }
80
- }
81
- /**
82
- * Create a new VM on exe.dev
83
- */
84
- async createVm(name) {
85
- try {
86
- const result = await this.exeCmd(`new --name=${name}`);
87
- if (result.exitCode !== 0) {
88
- throw new Error(`Failed to create VM: ${result.stderr}`);
89
- }
90
- await new Promise((resolve) => setTimeout(resolve, 5e3));
91
- return { name, status: "running" };
92
- } catch (error) {
93
- throw new Error(`Failed to create VM ${name}: ${error.message}`);
94
- }
95
- }
96
- /**
97
- * Delete a VM on exe.dev
98
- */
99
- async deleteVm(name) {
100
- try {
101
- const result = await this.exeCmd(`rm ${name}`);
102
- if (result.exitCode !== 0 && !result.stderr.includes("not found")) {
103
- throw new Error(result.stderr);
104
- }
105
- } catch (error) {
106
- throw new Error(`Failed to delete VM ${name}: ${error.message}`);
107
- }
108
- }
109
- /**
110
- * List all VMs
111
- */
112
- async listVms() {
113
- try {
114
- const result = await this.exeCmd("ls");
115
- if (result.exitCode !== 0) {
116
- throw new Error(result.stderr);
117
- }
118
- return parseVmList(result.stdout);
119
- } catch (error) {
120
- throw new Error(`Failed to list VMs: ${error.message}`);
121
- }
122
- }
123
- /**
124
- * Get VM status
125
- */
126
- async getStatus(name) {
127
- try {
128
- const vms = await this.listVms();
129
- const vm = vms.find((v) => v.name === name);
130
- return vm?.status || "unknown";
131
- } catch {
132
- return "unknown";
133
- }
134
- }
135
- /**
136
- * Get detailed VM info
137
- */
138
- async getVmInfo(name) {
139
- try {
140
- const vms = await this.listVms();
141
- return vms.find((v) => v.name === name) || null;
142
- } catch {
143
- return null;
144
- }
145
- }
146
- /**
147
- * Start a stopped VM
148
- * Note: exe.dev VMs are persistent and always running
149
- */
150
- async startVm(name) {
151
- const status = await this.getStatus(name);
152
- if (status === "unknown") {
153
- throw new Error(`VM ${name} not found`);
154
- }
155
- }
156
- /**
157
- * Stop a running VM
158
- * Note: exe.dev VMs are persistent - use rm to delete
159
- */
160
- async stopVm(name) {
161
- console.warn(`exe.dev VMs cannot be stopped, only deleted. VM ${name} continues running.`);
162
- }
163
- /**
164
- * Execute a command on VM via SSH
165
- * SSH to vmname.exe.xyz
166
- */
167
- async ssh(vm, command) {
168
- try {
169
- const sshHost = `${vm}.exe.xyz`;
170
- const escapedCmd = command.replace(/"/g, '\\"');
171
- const { stdout, stderr } = await execAsync(`ssh -A ${sshHost} "${escapedCmd}"`, {
172
- timeout: 3e5,
173
- // 5 minutes
174
- maxBuffer: 50 * 1024 * 1024
175
- // 50MB
176
- });
177
- return { stdout, stderr, exitCode: 0 };
178
- } catch (error) {
179
- return {
180
- stdout: error.stdout || "",
181
- stderr: error.stderr || error.message,
182
- exitCode: error.code || 1
183
- };
184
- }
185
- }
186
- /**
187
- * Execute a command and stream output
188
- */
189
- async *sshStream(vm, command) {
190
- const sshHost = `${vm}.exe.xyz`;
191
- const child = spawn("ssh", ["-A", sshHost, command], {
192
- stdio: ["ignore", "pipe", "pipe"]
193
- });
194
- for await (const chunk of child.stdout) {
195
- yield chunk.toString();
196
- }
197
- for await (const chunk of child.stderr) {
198
- yield chunk.toString();
199
- }
200
- }
201
- /**
202
- * Copy file to VM
203
- */
204
- async copyToVm(vm, localPath, remotePath) {
205
- try {
206
- const sshHost = `${vm}.exe.xyz`;
207
- await execAsync(`scp "${localPath}" ${sshHost}:${remotePath}`, { timeout: 3e5 });
208
- } catch (error) {
209
- throw new Error(`Failed to copy ${localPath} to ${vm}:${remotePath}: ${error.message}`);
210
- }
211
- }
212
- /**
213
- * Copy file from VM
214
- */
215
- async copyFromVm(vm, remotePath, localPath) {
216
- try {
217
- const sshHost = `${vm}.exe.xyz`;
218
- await execAsync(`scp ${sshHost}:${remotePath} "${localPath}"`, { timeout: 3e5 });
219
- } catch (error) {
220
- throw new Error(`Failed to copy ${vm}:${remotePath} to ${localPath}: ${error.message}`);
221
- }
222
- }
223
- /**
224
- * Expose a port on VM (returns public URL)
225
- *
226
- * exe.dev provides automatic HTTPS URLs for services:
227
- * https://vmname.exe.xyz:PORT
228
- */
229
- async exposePort(vm, port) {
230
- if (port === 80 || port === 443) {
231
- return `https://${vm}.exe.xyz`;
232
- }
233
- return `https://${vm}.exe.xyz:${port}`;
234
- }
235
- /**
236
- * Create SSH tunnel to VM
237
- */
238
- async tunnel(vm, remotePort, localPort) {
239
- const sshHost = `${vm}.exe.xyz`;
240
- const child = spawn("ssh", ["-N", "-L", `${localPort}:localhost:${remotePort}`, sshHost], {
241
- stdio: "ignore",
242
- detached: true
243
- });
244
- child.unref();
245
- return {
246
- close: () => {
247
- child.kill();
248
- }
249
- };
250
- }
251
- /**
252
- * Get the configured infrastructure VM name
253
- */
254
- getInfraVm() {
255
- return this.config.infraVm;
256
- }
257
- /**
258
- * Initialize the shared infrastructure VM
259
- *
260
- * Sets up postgres, redis, and traefik on a dedicated VM.
261
- */
262
- async initInfrastructure(vmName) {
263
- let status = await this.getStatus(vmName);
264
- if (status === "unknown") {
265
- await this.createVm(vmName);
266
- await new Promise((resolve) => setTimeout(resolve, 1e4));
267
- }
268
- const dockerCheck = await this.ssh(vmName, "which docker");
269
- if (dockerCheck.exitCode !== 0) {
270
- await this.ssh(vmName, "curl -fsSL https://get.docker.com | sh");
271
- await this.ssh(vmName, "sudo usermod -aG docker $USER");
272
- }
273
- const composeContent = `
274
- version: '3.8'
275
- services:
276
- postgres:
277
- image: postgres:16
278
- restart: unless-stopped
279
- environment:
280
- POSTGRES_PASSWORD: \${PAN_POSTGRES_PASSWORD:-panopticon}
281
- volumes:
282
- - postgres_data:/var/lib/postgresql/data
283
- ports:
284
- - "5432:5432"
285
-
286
- redis:
287
- image: redis:7
288
- restart: unless-stopped
289
- volumes:
290
- - redis_data:/data
291
- ports:
292
- - "6379:6379"
293
-
294
- traefik:
295
- image: traefik:v3.0
296
- restart: unless-stopped
297
- ports:
298
- - "80:80"
299
- - "443:443"
300
- - "8080:8080"
301
- volumes:
302
- - /var/run/docker.sock:/var/run/docker.sock:ro
303
- command:
304
- - --api.dashboard=true
305
- - --providers.docker=true
306
- - --providers.docker.exposedbydefault=false
307
- - --entrypoints.web.address=:80
308
- - --entrypoints.websecure.address=:443
309
-
310
- volumes:
311
- postgres_data:
312
- redis_data:
313
- `;
314
- await this.ssh(vmName, `mkdir -p /opt/panopticon && cat > /opt/panopticon/docker-compose.yml << 'COMPOSE_EOF'
315
- ${composeContent}
316
- COMPOSE_EOF`);
317
- await this.ssh(vmName, "cd /opt/panopticon && docker compose up -d");
318
- this.config.infraVm = vmName;
319
- }
320
- /**
321
- * Sync Claude Code credentials from local macOS Keychain to remote VM
322
- *
323
- * This should be called before spawning agents to ensure fresh credentials.
324
- * Credentials can expire, and re-running this ensures the VM has valid auth.
325
- *
326
- * @returns true if credentials were synced, false if not available
327
- */
328
- async syncClaudeCredentials(vmName) {
329
- try {
330
- const { stdout: credentials } = await execAsync(
331
- 'security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null',
332
- { encoding: "utf-8" }
333
- );
334
- if (!credentials || !credentials.trim()) {
335
- return false;
336
- }
337
- await this.ssh(vmName, "mkdir -p ~/.claude");
338
- const credsBase64 = Buffer.from(credentials.trim()).toString("base64");
339
- const result = await this.ssh(vmName, `echo '${credsBase64}' | base64 -d > ~/.claude/.credentials.json`);
340
- return result.exitCode === 0;
341
- } catch (error) {
342
- return false;
343
- }
344
- }
345
- /**
346
- * Sync GitHub CLI authentication from local macOS to remote VM
347
- *
348
- * GitHub CLI on macOS stores tokens in Keychain. On Linux VMs, it stores
349
- * them directly in ~/.config/gh/hosts.yml. This extracts from Keychain
350
- * and writes to the VM's config file.
351
- *
352
- * @returns true if auth was synced, false if not available
353
- */
354
- async syncGitHubAuth(vmName) {
355
- try {
356
- const { stdout: tokenRaw } = await execAsync(
357
- 'security find-generic-password -s "gh:github.com" -w 2>/dev/null',
358
- { encoding: "utf-8" }
359
- );
360
- if (!tokenRaw || !tokenRaw.trim()) {
361
- return false;
362
- }
363
- let token = tokenRaw.trim();
364
- if (token.startsWith("go-keyring-base64:")) {
365
- token = Buffer.from(token.replace("go-keyring-base64:", ""), "base64").toString("utf-8");
366
- }
367
- let username = "user";
368
- try {
369
- const { stdout: configOutput } = await execAsync("cat ~/.config/gh/hosts.yml 2>/dev/null", { encoding: "utf-8" });
370
- const userMatch = configOutput.match(/user:\s*(\S+)/);
371
- if (userMatch) {
372
- username = userMatch[1];
373
- }
374
- } catch {
375
- }
376
- const hostsYml = `github.com:
377
- oauth_token: ${token}
378
- git_protocol: ssh
379
- user: ${username}
380
- `;
381
- await this.ssh(vmName, "mkdir -p ~/.config/gh");
382
- const hostsBase64 = Buffer.from(hostsYml).toString("base64");
383
- const result = await this.ssh(vmName, `echo '${hostsBase64}' | base64 -d > ~/.config/gh/hosts.yml && chmod 600 ~/.config/gh/hosts.yml`);
384
- return result.exitCode === 0;
385
- } catch (error) {
386
- return false;
387
- }
388
- }
389
- /**
390
- * Sync GitLab CLI (glab) authentication to a remote VM
391
- *
392
- * Copies the glab config from local machine to the remote VM.
393
- * This allows the remote agent to use glab commands for MRs, etc.
394
- */
395
- async syncGitLabAuth(vmName) {
396
- try {
397
- const glabConfigPath = join(homedir(), ".config", "glab-cli", "config.yml");
398
- if (!existsSync(glabConfigPath)) {
399
- console.log(`[exe-provider] No glab config found at ${glabConfigPath}`);
400
- return false;
401
- }
402
- const glabConfig = readFileSync(glabConfigPath, "utf-8");
403
- await this.ssh(vmName, "mkdir -p ~/.config/glab-cli");
404
- const configBase64 = Buffer.from(glabConfig).toString("base64");
405
- const result = await this.ssh(vmName, `echo '${configBase64}' | base64 -d > ~/.config/glab-cli/config.yml && chmod 600 ~/.config/glab-cli/config.yml`);
406
- if (result.exitCode === 0) {
407
- console.log(`[exe-provider] GitLab auth synced to ${vmName}`);
408
- return true;
409
- }
410
- return false;
411
- } catch (error) {
412
- console.error(`[exe-provider] Failed to sync GitLab auth: ${error.message}`);
413
- return false;
414
- }
415
- }
416
- /**
417
- * Sync all credentials needed for remote workspace operation
418
- *
419
- * This syncs:
420
- * - Claude Code OAuth credentials
421
- * - GitHub CLI authentication
422
- *
423
- * Call this before spawning agents to ensure fresh credentials.
424
- *
425
- * @returns object with sync status for each credential type
426
- */
427
- async syncAllCredentials(vmName) {
428
- const [claude, github] = await Promise.all([
429
- this.syncClaudeCredentials(vmName),
430
- this.syncGitHubAuth(vmName)
431
- ]);
432
- return { claude, github };
433
- }
434
- /**
435
- * Install beads CLI (bd) on a remote VM
436
- *
437
- * Beads is required for planning agents to create task breakdowns.
438
- * Downloads the binary from GitHub releases since npm/node are often
439
- * not available on exe.dev VMs.
440
- *
441
- * @returns true if bd is available (already installed or just installed)
442
- */
443
- async installBeads(vmName) {
444
- const FALLBACK_BEADS_VERSION = "0.49.4";
445
- try {
446
- const checkResult = await this.ssh(vmName, "which bd");
447
- if (checkResult.exitCode === 0 && checkResult.stdout.trim()) {
448
- console.log(`[exe-provider] bd already installed on ${vmName}`);
449
- return true;
450
- }
451
- console.log(`[exe-provider] Installing bd (beads CLI) on ${vmName}...`);
452
- const installCmd = `
453
- cd /tmp && BEADS_VERSION=$(curl -sI "https://github.com/steveyegge/beads/releases/latest" 2>/dev/null | grep -i '^location:' | sed 's|.*/v||' | tr -d '\\r\\n') && if [ -z "$BEADS_VERSION" ]; then BEADS_VERSION="${FALLBACK_BEADS_VERSION}"; echo "Using fallback version: $BEADS_VERSION"; else echo "Detected version: $BEADS_VERSION"; fi && echo "Downloading beads v$BEADS_VERSION..." && curl -sL "https://github.com/steveyegge/beads/releases/download/v\${BEADS_VERSION}/beads_\${BEADS_VERSION}_linux_amd64.tar.gz" -o beads.tar.gz && tar -xzf beads.tar.gz && sudo mv bd /usr/local/bin/ && rm -f beads.tar.gz CHANGELOG.md LICENSE README.md
454
- `.trim().replace(/\n\s+/g, " ");
455
- const installResult = await this.ssh(vmName, installCmd);
456
- if (installResult.stderr) {
457
- console.log(`[exe-provider] bd install stderr on ${vmName}: ${installResult.stderr}`);
458
- }
459
- if (installResult.stdout) {
460
- console.log(`[exe-provider] bd install output on ${vmName}: ${installResult.stdout.trim()}`);
461
- }
462
- const verifyResult = await this.ssh(vmName, "which bd && bd --version");
463
- if (verifyResult.exitCode === 0 && verifyResult.stdout.includes("bd version")) {
464
- console.log(`[exe-provider] bd installed successfully on ${vmName}: ${verifyResult.stdout.trim()}`);
465
- return true;
466
- }
467
- console.warn(`[exe-provider] Failed to install bd on ${vmName} - verify exitCode: ${verifyResult.exitCode}, stdout: ${verifyResult.stdout}, stderr: ${verifyResult.stderr}`);
468
- return false;
469
- } catch (error) {
470
- console.error(`[exe-provider] Error installing bd on ${vmName}:`, error.message);
471
- return false;
472
- }
473
- }
474
- /**
475
- * Initialize beads in a workspace on a remote VM
476
- *
477
- * Creates .beads/ directory and initializes the database if needed.
478
- */
479
- async initBeads(vmName, workspacePath = "/workspace") {
480
- try {
481
- const checkResult = await this.ssh(vmName, `test -d ${workspacePath}/.beads && echo "exists"`);
482
- if (checkResult.stdout.includes("exists")) {
483
- console.log(`[exe-provider] .beads already initialized on ${vmName}`);
484
- return true;
485
- }
486
- console.log(`[exe-provider] Initializing beads on ${vmName}...`);
487
- const initResult = await this.ssh(vmName, `cd ${workspacePath} && bd init 2>/dev/null || true`);
488
- const verifyResult = await this.ssh(vmName, `test -d ${workspacePath}/.beads && echo "ok"`);
489
- if (verifyResult.stdout.includes("ok")) {
490
- console.log(`[exe-provider] beads initialized on ${vmName}`);
491
- return true;
492
- }
493
- await this.ssh(vmName, `mkdir -p ${workspacePath}/.beads`);
494
- return true;
495
- } catch (error) {
496
- console.error(`[exe-provider] Error initializing beads on ${vmName}:`, error.message);
497
- return false;
498
- }
499
- }
500
- /**
501
- * Sync beads from remote VM to git
502
- *
503
- * Exports beads database to JSONL, commits, and pushes.
504
- * This should be called after planning completes to persist tasks.
505
- */
506
- async syncBeadsToGit(vmName, workspacePath = "/workspace", commitMessage) {
507
- try {
508
- console.log(`[exe-provider] Syncing beads to git on ${vmName}...`);
509
- const syncResult = await this.ssh(vmName, `cd ${workspacePath} && bd sync 2>/dev/null || true`);
510
- const statusResult = await this.ssh(vmName, `cd ${workspacePath} && git status --porcelain .beads/ .planning/ 2>/dev/null`);
511
- if (!statusResult.stdout.trim()) {
512
- console.log(`[exe-provider] No beads changes to commit on ${vmName}`);
513
- return true;
514
- }
515
- const msg = commitMessage || "Sync beads from planning session";
516
- await this.ssh(vmName, `cd ${workspacePath} && git add .beads/ && git add -f .planning/ 2>/dev/null || true`);
517
- await this.ssh(vmName, `cd ${workspacePath} && git commit -m "${msg}" 2>/dev/null || true`);
518
- const branchResult = await this.ssh(vmName, `cd ${workspacePath} && git branch --show-current`);
519
- const branch = branchResult.stdout.trim() || "main";
520
- const pushResult = await this.ssh(vmName, `cd ${workspacePath} && git push -u origin ${branch} 2>&1`);
521
- if (pushResult.exitCode !== 0) {
522
- console.warn(`[exe-provider] Git push failed on ${vmName}: ${pushResult.stderr || pushResult.stdout}`);
523
- return false;
524
- }
525
- console.log(`[exe-provider] Beads synced and pushed from ${vmName}`);
526
- return true;
527
- } catch (error) {
528
- console.error(`[exe-provider] Error syncing beads on ${vmName}:`, error.message);
529
- return false;
530
- }
531
- }
532
- /**
533
- * Query beads on a remote VM
534
- *
535
- * Runs bd search on the remote and returns results.
536
- */
537
- async queryBeads(vmName, searchTerm, workspacePath = "/workspace") {
538
- try {
539
- const result = await this.ssh(vmName, `cd ${workspacePath} && bd search "${searchTerm}" --json 2>/dev/null || echo "[]"`);
540
- if (result.exitCode !== 0 || !result.stdout.trim()) {
541
- return [];
542
- }
543
- try {
544
- return JSON.parse(result.stdout.trim());
545
- } catch {
546
- return [];
547
- }
548
- } catch (error) {
549
- console.error(`[exe-provider] Error querying beads on ${vmName}:`, error.message);
550
- return [];
551
- }
552
- }
553
- /**
554
- * Configure Claude Code on a VM for autonomous operation
555
- *
556
- * Sets up:
557
- * - ~/.claude.json with bypass permissions and onboarding settings
558
- * - ~/.bashrc with proper terminal environment (TERM, LANG, etc.)
559
- *
560
- * This is required for remote agents to run without human interaction.
561
- */
562
- async configureClaudeCode(vmName) {
563
- const setupScript = `
564
- import json, os
565
- path = os.path.expanduser("~/.claude.json")
566
- data = {}
567
- if os.path.exists(path):
568
- with open(path, "r") as f:
569
- data = json.load(f)
570
- data["bypassPermissionsModeAccepted"] = True
571
- data["hasCompletedOnboarding"] = True
572
- with open(path, "w") as f:
573
- json.dump(data, f, indent=2)
574
- `;
575
- const scriptBase64 = Buffer.from(setupScript).toString("base64");
576
- const result = await this.ssh(vmName, `echo '${scriptBase64}' | base64 -d | python3`);
577
- if (result.exitCode !== 0) {
578
- throw new Error(`Failed to configure Claude Code on ${vmName}: ${result.stderr}`);
579
- }
580
- const termEnv = `
581
- # Panopticon terminal settings
582
- export TERM=xterm-256color
583
- export LANG=C.UTF-8
584
- export LC_ALL=C.UTF-8
585
- export COLORTERM=truecolor
586
- `;
587
- const termEnvBase64 = Buffer.from(termEnv).toString("base64");
588
- await this.ssh(vmName, `grep -q "Panopticon terminal settings" ~/.bashrc 2>/dev/null || echo '${termEnvBase64}' | base64 -d >> ~/.bashrc`);
589
- }
590
- /**
591
- * Copy essential skills from local ~/.panopticon/skills/ to remote VM ~/.claude/skills/
592
- *
593
- * Skills are read locally and written to the VM via base64-encoded SSH.
594
- * Only copies SKILL.md and resource files (not symlinks or deep directories).
595
- */
596
- async copySkillsToVm(vmName) {
597
- const essentialSkills = [
598
- "beads",
599
- "beads-completion-check",
600
- "beads-panopticon-guide",
601
- "work-complete",
602
- "session-health"
603
- ];
604
- const skillsSourceDir = join(homedir(), ".panopticon", "skills");
605
- if (!existsSync(skillsSourceDir)) {
606
- console.log(`[exe-provider] No skills source directory at ${skillsSourceDir}`);
607
- return;
608
- }
609
- await this.ssh(vmName, "mkdir -p ~/.claude/skills");
610
- for (const skillName of essentialSkills) {
611
- const skillDir = join(skillsSourceDir, skillName);
612
- if (!existsSync(skillDir)) {
613
- continue;
614
- }
615
- try {
616
- await this.ssh(vmName, `mkdir -p ~/.claude/skills/${skillName}`);
617
- const skillMdPath = join(skillDir, "SKILL.md");
618
- if (existsSync(skillMdPath)) {
619
- const content = readFileSync(skillMdPath, "utf-8");
620
- const contentBase64 = Buffer.from(content).toString("base64");
621
- await this.ssh(vmName, `echo '${contentBase64}' | base64 -d > ~/.claude/skills/${skillName}/SKILL.md`);
622
- }
623
- const resourcesDir = join(skillDir, "resources");
624
- if (existsSync(resourcesDir) && statSync(resourcesDir).isDirectory()) {
625
- await this.ssh(vmName, `mkdir -p ~/.claude/skills/${skillName}/resources`);
626
- const resourceFiles = readdirSync(resourcesDir).filter((f) => f.endsWith(".md"));
627
- for (const resourceFile of resourceFiles) {
628
- const resourcePath = join(resourcesDir, resourceFile);
629
- if (statSync(resourcePath).isFile()) {
630
- const resourceContent = readFileSync(resourcePath, "utf-8");
631
- const resourceBase64 = Buffer.from(resourceContent).toString("base64");
632
- await this.ssh(vmName, `echo '${resourceBase64}' | base64 -d > ~/.claude/skills/${skillName}/resources/${resourceFile}`);
633
- }
634
- }
635
- }
636
- console.log(`[exe-provider] Copied skill ${skillName} to ${vmName}`);
637
- } catch (error) {
638
- console.warn(`[exe-provider] Failed to copy skill ${skillName} to ${vmName}: ${error.message}`);
639
- }
640
- }
641
- }
642
- };
643
- }
644
- });
645
-
646
- export {
647
- createExeProvider,
648
- init_exe_provider
649
- };
650
- //# sourceMappingURL=chunk-ZDNQFWR5.js.map