panopticon-cli 0.3.2 → 0.3.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/cli/index.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  restoreBackup,
17
17
  saveConfig,
18
18
  syncHooks
19
- } from "../chunk-B2JBBOJN.js";
19
+ } from "../chunk-ZT55DPAC.js";
20
20
  import {
21
21
  PROJECTS_CONFIG_FILE,
22
22
  getProject,
@@ -26,7 +26,7 @@ import {
26
26
  registerProject,
27
27
  resolveProjectFromIssue,
28
28
  unregisterProject
29
- } from "../chunk-PSJRCUOA.js";
29
+ } from "../chunk-IVAFJ6DS.js";
30
30
  import {
31
31
  AGENTS_DIR,
32
32
  CERTS_DIR,
@@ -41,1149 +41,999 @@ import {
41
41
  SOURCE_TRAEFIK_TEMPLATES,
42
42
  SYNC_TARGETS,
43
43
  TRAEFIK_CERTS_DIR,
44
- TRAEFIK_DIR,
45
- __commonJS,
46
- __esm,
47
- __export,
48
- __require,
49
- __toCommonJS,
50
- init_esm_shims,
51
- init_paths
52
- } from "../chunk-SG7O6I7R.js";
44
+ TRAEFIK_DIR
45
+ } from "../chunk-3SI436SZ.js";
53
46
 
54
- // src/lib/tmux.ts
55
- import { execSync } from "child_process";
56
- function listSessions() {
57
- try {
58
- const output = execSync('tmux list-sessions -F "#{session_name}|#{session_created}|#{session_attached}|#{session_windows}"', {
59
- encoding: "utf8"
60
- });
61
- return output.trim().split("\n").filter(Boolean).map((line) => {
62
- const [name, created, attached, windows] = line.split("|");
63
- return {
64
- name,
65
- created: new Date(parseInt(created) * 1e3),
66
- attached: attached === "1",
67
- windows: parseInt(windows)
68
- };
69
- });
70
- } catch {
71
- return [];
72
- }
73
- }
74
- function sessionExists(name) {
75
- try {
76
- execSync(`tmux has-session -t ${name} 2>/dev/null`);
77
- return true;
78
- } catch {
79
- return false;
47
+ // src/cli/index.ts
48
+ import { Command } from "commander";
49
+ import chalk29 from "chalk";
50
+
51
+ // src/cli/commands/init.ts
52
+ import { existsSync, mkdirSync, readdirSync, cpSync } from "fs";
53
+ import { join, dirname } from "path";
54
+ import { fileURLToPath } from "url";
55
+ import chalk from "chalk";
56
+ import ora from "ora";
57
+ var __filename = fileURLToPath(import.meta.url);
58
+ var __dirname = dirname(__filename);
59
+ var PACKAGE_ROOT = join(__dirname, "..", "..");
60
+ var BUNDLED_SKILLS_DIR = join(PACKAGE_ROOT, "skills");
61
+ var BUNDLED_AGENTS_DIR = join(PACKAGE_ROOT, "agents");
62
+ function copyBundledSkills() {
63
+ if (!existsSync(BUNDLED_SKILLS_DIR)) {
64
+ return 0;
80
65
  }
81
- }
82
- function createSession(name, cwd, initialCommand, options) {
83
- const escapedCwd = cwd.replace(/"/g, '\\"');
84
- let envFlags = "";
85
- if (options?.env) {
86
- for (const [key, value] of Object.entries(options.env)) {
87
- envFlags += ` -e ${key}="${value.replace(/"/g, '\\"')}"`;
88
- }
66
+ if (!existsSync(SKILLS_DIR)) {
67
+ mkdirSync(SKILLS_DIR, { recursive: true });
89
68
  }
90
- if (initialCommand && (initialCommand.includes("`") || initialCommand.includes("\n") || initialCommand.length > 500)) {
91
- execSync(`tmux new-session -d -s ${name} -c "${escapedCwd}"${envFlags}`);
92
- execSync("sleep 0.5");
93
- const tmpFile = `/tmp/pan-cmd-${name}.sh`;
94
- const fs = __require("fs");
95
- fs.writeFileSync(tmpFile, initialCommand);
96
- fs.chmodSync(tmpFile, "755");
97
- execSync(`tmux send-keys -t ${name} "bash ${tmpFile}" Enter`);
98
- } else if (initialCommand) {
99
- const cmd = `tmux new-session -d -s ${name} -c "${escapedCwd}"${envFlags} "${initialCommand.replace(/"/g, '\\"')}"`;
100
- execSync(cmd);
101
- } else {
102
- execSync(`tmux new-session -d -s ${name} -c "${escapedCwd}"${envFlags}`);
69
+ const skills = readdirSync(BUNDLED_SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
70
+ let copied = 0;
71
+ for (const skill of skills) {
72
+ const sourcePath = join(BUNDLED_SKILLS_DIR, skill.name);
73
+ const targetPath = join(SKILLS_DIR, skill.name);
74
+ cpSync(sourcePath, targetPath, { recursive: true });
75
+ copied++;
103
76
  }
77
+ return copied;
104
78
  }
105
- function killSession(name) {
106
- execSync(`tmux kill-session -t ${name}`);
107
- }
108
- function sendKeys(sessionName, keys) {
109
- const escapedKeys = keys.replace(/"/g, '\\"');
110
- execSync(`tmux send-keys -t ${sessionName} "${escapedKeys}"`);
111
- execSync(`tmux send-keys -t ${sessionName} Enter`);
112
- }
113
- function getAgentSessions() {
114
- return listSessions().filter((s) => s.name.startsWith("agent-"));
115
- }
116
- var init_tmux = __esm({
117
- "src/lib/tmux.ts"() {
118
- "use strict";
119
- init_esm_shims();
79
+ function copyBundledAgents() {
80
+ if (!existsSync(BUNDLED_AGENTS_DIR)) {
81
+ return 0;
120
82
  }
121
- });
122
-
123
- // src/lib/hooks.ts
124
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync, readdirSync as readdirSync3, unlinkSync } from "fs";
125
- import { join as join3 } from "path";
126
- function getHookDir(agentId) {
127
- return join3(AGENTS_DIR, agentId);
128
- }
129
- function getHookFile(agentId) {
130
- return join3(getHookDir(agentId), "hook.json");
131
- }
132
- function getMailDir(agentId) {
133
- return join3(getHookDir(agentId), "mail");
134
- }
135
- function initHook(agentId) {
136
- const hookDir = getHookDir(agentId);
137
- const mailDir = getMailDir(agentId);
138
- mkdirSync2(hookDir, { recursive: true });
139
- mkdirSync2(mailDir, { recursive: true });
140
- const hookFile = getHookFile(agentId);
141
- if (!existsSync3(hookFile)) {
142
- const hook = {
143
- agentId,
144
- items: []
145
- };
146
- writeFileSync(hookFile, JSON.stringify(hook, null, 2));
83
+ if (!existsSync(AGENTS_DIR)) {
84
+ mkdirSync(AGENTS_DIR, { recursive: true });
147
85
  }
86
+ const agents = readdirSync(BUNDLED_AGENTS_DIR, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md"));
87
+ let copied = 0;
88
+ for (const agent of agents) {
89
+ const sourcePath = join(BUNDLED_AGENTS_DIR, agent.name);
90
+ const targetPath = join(AGENTS_DIR, agent.name);
91
+ cpSync(sourcePath, targetPath);
92
+ copied++;
93
+ }
94
+ return copied;
148
95
  }
149
- function getHook(agentId) {
150
- const hookFile = getHookFile(agentId);
151
- if (!existsSync3(hookFile)) {
152
- return null;
96
+ async function initCommand() {
97
+ const spinner = ora("Initializing Panopticon...").start();
98
+ if (existsSync(CONFIG_FILE)) {
99
+ spinner.info("Panopticon already initialized");
100
+ console.log(chalk.dim(` Config: ${CONFIG_FILE}`));
101
+ console.log(chalk.dim(` Home: ${PANOPTICON_HOME}`));
102
+ console.log(chalk.dim(" Run `pan sync` to update skills"));
103
+ return;
153
104
  }
154
105
  try {
155
- const content = readFileSync2(hookFile, "utf-8");
156
- return JSON.parse(content);
157
- } catch {
158
- return null;
159
- }
160
- }
161
- function pushToHook(agentId, item) {
162
- initHook(agentId);
163
- const hook = getHook(agentId) || { agentId, items: [] };
164
- const newItem = {
165
- ...item,
166
- id: `hook-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
167
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
168
- };
169
- hook.items.push(newItem);
170
- writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));
171
- return newItem;
172
- }
173
- function checkHook(agentId) {
174
- const hook = getHook(agentId);
175
- if (!hook || hook.items.length === 0) {
176
- const mailDir = getMailDir(agentId);
177
- if (existsSync3(mailDir)) {
178
- const mails = readdirSync3(mailDir).filter((f) => f.endsWith(".json"));
179
- if (mails.length > 0) {
180
- const mailItems = mails.map((file) => {
181
- try {
182
- const content = readFileSync2(join3(mailDir, file), "utf-8");
183
- return JSON.parse(content);
184
- } catch {
185
- return null;
186
- }
187
- }).filter(Boolean);
188
- return {
189
- hasWork: mailItems.length > 0,
190
- urgentCount: mailItems.filter((i) => i.priority === "urgent").length,
191
- items: mailItems
192
- };
106
+ for (const dir of INIT_DIRS) {
107
+ if (!existsSync(dir)) {
108
+ mkdirSync(dir, { recursive: true });
193
109
  }
194
110
  }
195
- return { hasWork: false, urgentCount: 0, items: [] };
196
- }
197
- const now = /* @__PURE__ */ new Date();
198
- const activeItems = hook.items.filter((item) => {
199
- if (item.expiresAt) {
200
- return new Date(item.expiresAt) > now;
111
+ spinner.text = "Created directories...";
112
+ const config = getDefaultConfig();
113
+ saveConfig(config);
114
+ spinner.text = "Created config...";
115
+ spinner.text = "Installing bundled skills...";
116
+ const skillsCopied = copyBundledSkills();
117
+ spinner.text = "Installing bundled agents...";
118
+ const agentsCopied = copyBundledAgents();
119
+ const shell = detectShell();
120
+ const rcFile = getShellRcFile(shell);
121
+ if (rcFile && existsSync(rcFile)) {
122
+ addAlias(rcFile);
123
+ spinner.succeed("Panopticon initialized!");
124
+ console.log("");
125
+ console.log(chalk.green("\u2713") + " Created " + chalk.cyan(PANOPTICON_HOME));
126
+ console.log(chalk.green("\u2713") + " Created " + chalk.cyan(CONFIG_FILE));
127
+ if (skillsCopied > 0) {
128
+ console.log(chalk.green("\u2713") + ` Installed ${skillsCopied} bundled skills`);
129
+ }
130
+ if (agentsCopied > 0) {
131
+ console.log(chalk.green("\u2713") + ` Installed ${agentsCopied} bundled agents`);
132
+ }
133
+ console.log(chalk.green("\u2713") + " " + getAliasInstructions(shell));
134
+ } else {
135
+ spinner.succeed("Panopticon initialized!");
136
+ console.log("");
137
+ console.log(chalk.green("\u2713") + " Created " + chalk.cyan(PANOPTICON_HOME));
138
+ console.log(chalk.green("\u2713") + " Created " + chalk.cyan(CONFIG_FILE));
139
+ if (skillsCopied > 0) {
140
+ console.log(chalk.green("\u2713") + ` Installed ${skillsCopied} bundled skills`);
141
+ }
142
+ if (agentsCopied > 0) {
143
+ console.log(chalk.green("\u2713") + ` Installed ${agentsCopied} bundled agents`);
144
+ }
145
+ console.log(chalk.yellow("!") + " Could not detect shell. Add alias manually:");
146
+ console.log(chalk.dim(' alias pan="panopticon"'));
201
147
  }
202
- return true;
203
- });
204
- const priorityOrder = { urgent: 0, high: 1, normal: 2, low: 3 };
205
- activeItems.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
206
- return {
207
- hasWork: activeItems.length > 0,
208
- urgentCount: activeItems.filter((i) => i.priority === "urgent").length,
209
- items: activeItems
210
- };
211
- }
212
- function popFromHook(agentId, itemId) {
213
- const hook = getHook(agentId);
214
- if (!hook) return false;
215
- const index = hook.items.findIndex((i) => i.id === itemId);
216
- if (index === -1) return false;
217
- hook.items.splice(index, 1);
218
- hook.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
219
- writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));
220
- return true;
221
- }
222
- function clearHook(agentId) {
223
- const hook = getHook(agentId);
224
- if (!hook) return;
225
- hook.items = [];
226
- hook.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
227
- writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));
148
+ console.log("");
149
+ console.log("Next steps:");
150
+ console.log(chalk.dim(" 1. Run: pan sync"));
151
+ console.log(chalk.dim(" 2. Start dashboard: pan up"));
152
+ } catch (error) {
153
+ spinner.fail("Failed to initialize");
154
+ console.error(chalk.red(error.message));
155
+ process.exit(1);
156
+ }
228
157
  }
229
- function sendMail(toAgentId, from, message, priority = "normal") {
230
- initHook(toAgentId);
231
- const mailDir = getMailDir(toAgentId);
232
- const mailItem = {
233
- id: `mail-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
234
- type: "message",
235
- priority,
236
- source: from,
237
- payload: { message },
238
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
239
- };
240
- writeFileSync(
241
- join3(mailDir, `${mailItem.id}.json`),
242
- JSON.stringify(mailItem, null, 2)
243
- );
244
- }
245
- function generateGUPPPrompt(agentId) {
246
- const { hasWork, urgentCount, items } = checkHook(agentId);
247
- if (!hasWork) return null;
248
- const lines = [
249
- "# GUPP: Work Found on Your Hook",
250
- "",
251
- '> "If there is work on your Hook, YOU MUST RUN IT."',
252
- ""
253
- ];
254
- if (urgentCount > 0) {
255
- lines.push(`\u26A0\uFE0F **${urgentCount} URGENT item(s) require immediate attention**`);
256
- lines.push("");
158
+
159
+ // src/cli/commands/sync.ts
160
+ import chalk2 from "chalk";
161
+ import ora2 from "ora";
162
+ async function syncCommand(options) {
163
+ const config = loadConfig();
164
+ const targets = config.sync?.targets;
165
+ if (!targets || !Array.isArray(targets) || targets.length === 0) {
166
+ console.log(chalk2.yellow("No sync targets configured."));
167
+ console.log(chalk2.dim("Edit ~/.panopticon/config.toml to add targets to the [sync] section."));
168
+ console.log(chalk2.dim('Example: targets = ["claude-code", "cursor"]'));
169
+ return;
257
170
  }
258
- lines.push(`## Pending Work Items (${items.length})`);
259
- lines.push("");
260
- for (const item of items) {
261
- const priorityEmoji = {
262
- urgent: "\u{1F534}",
263
- high: "\u{1F7E0}",
264
- normal: "\u{1F7E2}",
265
- low: "\u26AA"
266
- }[item.priority];
267
- lines.push(`### ${priorityEmoji} ${item.type.toUpperCase()}: ${item.id}`);
268
- lines.push(`- Source: ${item.source}`);
269
- lines.push(`- Created: ${item.createdAt}`);
270
- if (item.payload.issueId) {
271
- lines.push(`- Issue: ${item.payload.issueId}`);
272
- }
273
- if (item.payload.message) {
274
- lines.push(`- Message: ${item.payload.message}`);
171
+ const validTargets = targets;
172
+ if (options.dryRun) {
173
+ console.log(chalk2.bold("Sync Plan (dry run):\n"));
174
+ const hooksPlan = planHooksSync();
175
+ if (hooksPlan.length > 0) {
176
+ console.log(chalk2.cyan("hooks (bin scripts):"));
177
+ for (const hook of hooksPlan) {
178
+ const icon = hook.status === "new" ? chalk2.green("+") : chalk2.blue("\u21BB");
179
+ const status = hook.status === "new" ? "" : chalk2.dim("[update]");
180
+ console.log(` ${icon} ${hook.name} ${status}`);
181
+ }
182
+ console.log("");
275
183
  }
276
- if (item.payload.action) {
277
- lines.push(`- Action: ${item.payload.action}`);
184
+ for (const runtime of validTargets) {
185
+ const plan = planSync(runtime);
186
+ console.log(chalk2.cyan(`${runtime}:`));
187
+ if (plan.skills.length === 0 && plan.commands.length === 0 && plan.agents.length === 0) {
188
+ console.log(chalk2.dim(" (nothing to sync)"));
189
+ continue;
190
+ }
191
+ for (const item of plan.skills) {
192
+ const icon = item.status === "conflict" ? chalk2.yellow("!") : chalk2.green("+");
193
+ const status = item.status === "conflict" ? chalk2.yellow("[conflict]") : "";
194
+ console.log(` ${icon} skill/${item.name} ${status}`);
195
+ }
196
+ for (const item of plan.commands) {
197
+ const icon = item.status === "conflict" ? chalk2.yellow("!") : chalk2.green("+");
198
+ const status = item.status === "conflict" ? chalk2.yellow("[conflict]") : "";
199
+ console.log(` ${icon} command/${item.name} ${status}`);
200
+ }
201
+ for (const item of plan.agents) {
202
+ const icon = item.status === "conflict" ? chalk2.yellow("!") : chalk2.green("+");
203
+ const status = item.status === "conflict" ? chalk2.yellow("[conflict]") : "";
204
+ console.log(` ${icon} agent/${item.name} ${status}`);
205
+ }
206
+ console.log("");
278
207
  }
279
- lines.push("");
280
- }
281
- lines.push("---");
282
- lines.push("");
283
- lines.push("Execute these items in priority order. Use `bd hook pop <id>` after completing each item.");
284
- return lines.join("\n");
285
- }
286
- var init_hooks = __esm({
287
- "src/lib/hooks.ts"() {
288
- "use strict";
289
- init_esm_shims();
290
- init_paths();
208
+ console.log(chalk2.dim("Run without --dry-run to apply changes."));
209
+ return;
291
210
  }
292
- });
293
-
294
- // src/lib/cv.ts
295
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync as readdirSync4 } from "fs";
296
- import { join as join4 } from "path";
297
- function getCVFile(agentId) {
298
- return join4(AGENTS_DIR, agentId, "cv.json");
299
- }
300
- function getAgentCV(agentId) {
301
- const cvFile = getCVFile(agentId);
302
- if (existsSync4(cvFile)) {
303
- try {
304
- return JSON.parse(readFileSync3(cvFile, "utf-8"));
305
- } catch {
211
+ if (config.sync.backup_before_sync) {
212
+ const spinner2 = ora2("Creating backup...").start();
213
+ const backupDirs = validTargets.flatMap((r) => [
214
+ SYNC_TARGETS[r].skills,
215
+ SYNC_TARGETS[r].commands,
216
+ SYNC_TARGETS[r].agents
217
+ ]);
218
+ const backup2 = createBackup(backupDirs);
219
+ if (backup2.targets.length > 0) {
220
+ spinner2.succeed(`Backup created: ${backup2.timestamp}`);
221
+ } else {
222
+ spinner2.info("No existing content to backup");
223
+ }
224
+ if (options.backupOnly) {
225
+ return;
306
226
  }
307
227
  }
308
- const cv = {
309
- agentId,
310
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
311
- lastActive: (/* @__PURE__ */ new Date()).toISOString(),
312
- runtime: "claude",
313
- model: "sonnet",
314
- stats: {
315
- totalIssues: 0,
316
- successCount: 0,
317
- failureCount: 0,
318
- abandonedCount: 0,
319
- avgDuration: 0,
320
- successRate: 0
321
- },
322
- skillsUsed: [],
323
- recentWork: []
324
- };
325
- saveAgentCV(cv);
326
- return cv;
327
- }
328
- function saveAgentCV(cv) {
329
- const dir = join4(AGENTS_DIR, cv.agentId);
330
- mkdirSync3(dir, { recursive: true });
331
- writeFileSync2(getCVFile(cv.agentId), JSON.stringify(cv, null, 2));
332
- }
333
- function startWork(agentId, issueId, skills) {
334
- const cv = getAgentCV(agentId);
335
- const entry = {
336
- issueId,
337
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
338
- outcome: "in_progress",
339
- skills
340
- };
341
- cv.recentWork.unshift(entry);
342
- cv.stats.totalIssues++;
343
- cv.lastActive = (/* @__PURE__ */ new Date()).toISOString();
344
- if (skills) {
345
- for (const skill of skills) {
346
- if (!cv.skillsUsed.includes(skill)) {
347
- cv.skillsUsed.push(skill);
228
+ const spinner = ora2("Syncing...").start();
229
+ let totalCreated = 0;
230
+ let totalConflicts = 0;
231
+ for (const runtime of validTargets) {
232
+ spinner.text = `Syncing to ${runtime}...`;
233
+ const result = executeSync(runtime, { force: options.force });
234
+ totalCreated += result.created.length;
235
+ totalConflicts += result.conflicts.length;
236
+ if (result.conflicts.length > 0 && !options.force) {
237
+ console.log("");
238
+ console.log(chalk2.yellow(`Conflicts in ${runtime}:`));
239
+ for (const name of result.conflicts) {
240
+ console.log(chalk2.dim(` - ${name} (use --force to overwrite)`));
348
241
  }
349
242
  }
350
243
  }
351
- if (cv.recentWork.length > 50) {
352
- cv.recentWork = cv.recentWork.slice(0, 50);
244
+ if (totalConflicts > 0 && !options.force) {
245
+ spinner.warn(`Synced ${totalCreated} items, ${totalConflicts} conflicts`);
246
+ console.log("");
247
+ console.log(chalk2.dim("Use --force to overwrite conflicting items."));
248
+ } else {
249
+ spinner.succeed(`Synced ${totalCreated} items to ${validTargets.join(", ")}`);
353
250
  }
354
- saveAgentCV(cv);
355
- }
356
- function getAgentRankings() {
357
- const rankings = [];
358
- if (!existsSync4(AGENTS_DIR)) return rankings;
359
- const dirs = readdirSync4(AGENTS_DIR, { withFileTypes: true }).filter(
360
- (d) => d.isDirectory()
361
- );
362
- for (const dir of dirs) {
363
- const cv = getAgentCV(dir.name);
364
- if (cv.stats.totalIssues > 0) {
365
- rankings.push({
366
- agentId: dir.name,
367
- successRate: cv.stats.successRate,
368
- totalIssues: cv.stats.totalIssues,
369
- avgDuration: cv.stats.avgDuration
370
- });
251
+ const hooksSpinner = ora2("Syncing hooks...").start();
252
+ const hooksResult = syncHooks();
253
+ if (hooksResult.errors.length > 0) {
254
+ hooksSpinner.warn(`Synced ${hooksResult.synced.length} hooks, ${hooksResult.errors.length} errors`);
255
+ for (const error of hooksResult.errors) {
256
+ console.log(chalk2.red(` \u2717 ${error}`));
371
257
  }
258
+ } else if (hooksResult.synced.length > 0) {
259
+ hooksSpinner.succeed(`Synced ${hooksResult.synced.length} hooks to ~/.panopticon/bin/`);
260
+ } else {
261
+ hooksSpinner.info("No hooks to sync");
372
262
  }
373
- rankings.sort((a, b) => {
374
- if (b.successRate !== a.successRate) {
375
- return b.successRate - a.successRate;
376
- }
377
- return b.totalIssues - a.totalIssues;
378
- });
379
- return rankings;
380
263
  }
381
- function formatCV(cv) {
382
- const lines = [
383
- `# Agent CV: ${cv.agentId}`,
384
- "",
385
- `Runtime: ${cv.runtime} (${cv.model})`,
386
- `Created: ${cv.createdAt}`,
387
- `Last Active: ${cv.lastActive}`,
388
- "",
389
- "## Statistics",
390
- "",
391
- `- Total Issues: ${cv.stats.totalIssues}`,
392
- `- Success Rate: ${(cv.stats.successRate * 100).toFixed(1)}%`,
393
- `- Successes: ${cv.stats.successCount}`,
394
- `- Failures: ${cv.stats.failureCount}`,
395
- `- Abandoned: ${cv.stats.abandonedCount}`,
396
- `- Avg Duration: ${cv.stats.avgDuration} minutes`,
397
- ""
398
- ];
399
- if (cv.skillsUsed.length > 0) {
400
- lines.push("## Skills Used");
401
- lines.push("");
402
- lines.push(cv.skillsUsed.join(", "));
403
- lines.push("");
264
+
265
+ // src/cli/commands/restore.ts
266
+ import chalk3 from "chalk";
267
+ import ora3 from "ora";
268
+ import inquirer from "inquirer";
269
+ async function restoreCommand(timestamp) {
270
+ const backups = listBackups();
271
+ if (backups.length === 0) {
272
+ console.log(chalk3.yellow("No backups found."));
273
+ return;
404
274
  }
405
- if (cv.recentWork.length > 0) {
406
- lines.push("## Recent Work");
407
- lines.push("");
408
- for (const work of cv.recentWork.slice(0, 10)) {
409
- const statusIcon = {
410
- success: "\u2713",
411
- failed: "\u2717",
412
- abandoned: "\u2298",
413
- in_progress: "\u25CF"
414
- }[work.outcome];
415
- const duration = work.duration ? ` (${work.duration}m)` : "";
416
- lines.push(`${statusIcon} ${work.issueId}${duration}`);
417
- if (work.failureReason) {
418
- lines.push(` Reason: ${work.failureReason}`);
419
- }
420
- }
421
- lines.push("");
422
- }
423
- return lines.join("\n");
424
- }
425
- var init_cv = __esm({
426
- "src/lib/cv.ts"() {
427
- "use strict";
428
- init_esm_shims();
429
- init_paths();
430
- }
431
- });
432
-
433
- // src/lib/agents.ts
434
- var agents_exports = {};
435
- __export(agents_exports, {
436
- autoRecoverAgents: () => autoRecoverAgents,
437
- detectCrashedAgents: () => detectCrashedAgents,
438
- getAgentDir: () => getAgentDir,
439
- getAgentState: () => getAgentState,
440
- listRunningAgents: () => listRunningAgents,
441
- messageAgent: () => messageAgent,
442
- recoverAgent: () => recoverAgent,
443
- saveAgentState: () => saveAgentState,
444
- spawnAgent: () => spawnAgent,
445
- stopAgent: () => stopAgent
446
- });
447
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, readFileSync as readFileSync4, readdirSync as readdirSync5 } from "fs";
448
- import { join as join5 } from "path";
449
- import { execSync as execSync2 } from "child_process";
450
- function getAgentDir(agentId) {
451
- return join5(AGENTS_DIR, agentId);
452
- }
453
- function getAgentState(agentId) {
454
- const stateFile = join5(getAgentDir(agentId), "state.json");
455
- if (!existsSync5(stateFile)) return null;
456
- const content = readFileSync4(stateFile, "utf8");
457
- return JSON.parse(content);
458
- }
459
- function saveAgentState(state) {
460
- const dir = getAgentDir(state.id);
461
- mkdirSync4(dir, { recursive: true });
462
- writeFileSync3(
463
- join5(dir, "state.json"),
464
- JSON.stringify(state, null, 2)
465
- );
466
- }
467
- function spawnAgent(options) {
468
- const agentId = `agent-${options.issueId.toLowerCase()}`;
469
- if (sessionExists(agentId)) {
470
- throw new Error(`Agent ${agentId} already running. Use 'pan work tell' to message it.`);
471
- }
472
- initHook(agentId);
473
- const state = {
474
- id: agentId,
475
- issueId: options.issueId,
476
- workspace: options.workspace,
477
- runtime: options.runtime || "claude",
478
- model: options.model || "sonnet",
479
- status: "starting",
480
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
481
- // Initialize Phase 4 fields
482
- handoffCount: 0,
483
- costSoFar: 0
484
- };
485
- saveAgentState(state);
486
- let prompt = options.prompt || "";
487
- const { hasWork, items } = checkHook(agentId);
488
- if (hasWork) {
489
- const guppPrompt = generateGUPPPrompt(agentId);
490
- if (guppPrompt) {
491
- prompt = guppPrompt + "\n\n---\n\n" + prompt;
275
+ if (!timestamp) {
276
+ console.log(chalk3.bold("Available backups:\n"));
277
+ for (const backup2 of backups.slice(0, 10)) {
278
+ console.log(` ${chalk3.cyan(backup2.timestamp)} - ${backup2.targets.join(", ")}`);
492
279
  }
493
- }
494
- const promptFile = join5(getAgentDir(agentId), "initial-prompt.md");
495
- if (prompt) {
496
- writeFileSync3(promptFile, prompt);
497
- }
498
- checkAndSetupHooks();
499
- writeTaskCache(agentId, options.issueId);
500
- const claudeCmd = `claude --dangerously-skip-permissions --model ${state.model}`;
501
- createSession(agentId, options.workspace, claudeCmd, {
502
- env: {
503
- PANOPTICON_AGENT_ID: agentId
280
+ if (backups.length > 10) {
281
+ console.log(chalk3.dim(` ... and ${backups.length - 10} more`));
504
282
  }
505
- });
506
- if (prompt) {
507
- let ready = false;
508
- for (let i = 0; i < 15; i++) {
509
- execSync2("sleep 1");
510
- try {
511
- const pane = execSync2(`tmux capture-pane -t ${agentId} -p`, { encoding: "utf-8" });
512
- if (pane.includes("\u276F") || pane.includes(">")) {
513
- ready = true;
514
- break;
515
- }
516
- } catch {
283
+ console.log("");
284
+ const { selected } = await inquirer.prompt([
285
+ {
286
+ type: "list",
287
+ name: "selected",
288
+ message: "Select backup to restore:",
289
+ choices: backups.slice(0, 10).map((b) => ({
290
+ name: `${b.timestamp} (${b.targets.join(", ")})`,
291
+ value: b.timestamp
292
+ }))
517
293
  }
294
+ ]);
295
+ timestamp = selected;
296
+ }
297
+ const { confirm } = await inquirer.prompt([
298
+ {
299
+ type: "confirm",
300
+ name: "confirm",
301
+ message: `Restore backup ${timestamp}? This will overwrite current files.`,
302
+ default: false
518
303
  }
519
- if (ready) {
520
- execSync2(`tmux load-buffer "${promptFile}"`);
521
- execSync2(`tmux paste-buffer -t ${agentId}`);
522
- execSync2("sleep 0.5");
523
- execSync2(`tmux send-keys -t ${agentId} Enter`);
524
- } else {
525
- console.error("Claude did not become ready in time, prompt not sent");
526
- }
304
+ ]);
305
+ if (!confirm) {
306
+ console.log(chalk3.dim("Restore cancelled."));
307
+ return;
527
308
  }
528
- state.status = "running";
529
- saveAgentState(state);
530
- startWork(agentId, options.issueId);
531
- return state;
532
- }
533
- function listRunningAgents() {
534
- const tmuxSessions = getAgentSessions();
535
- const tmuxNames = new Set(tmuxSessions.map((s) => s.name));
536
- const agents = [];
537
- if (!existsSync5(AGENTS_DIR)) return agents;
538
- const dirs = readdirSync5(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
539
- for (const dir of dirs) {
540
- const state = getAgentState(dir.name);
541
- if (state) {
542
- agents.push({
543
- ...state,
544
- tmuxActive: tmuxNames.has(state.id)
545
- });
309
+ const spinner = ora3("Restoring backup...").start();
310
+ try {
311
+ const config = loadConfig();
312
+ const targets = config.sync.targets;
313
+ const targetDirs = {};
314
+ for (const runtime of targets) {
315
+ targetDirs[`${runtime}-skills`] = SYNC_TARGETS[runtime].skills;
316
+ targetDirs[`${runtime}-commands`] = SYNC_TARGETS[runtime].commands;
317
+ targetDirs["skills"] = SYNC_TARGETS[runtime].skills;
318
+ targetDirs["commands"] = SYNC_TARGETS[runtime].commands;
546
319
  }
320
+ restoreBackup(timestamp, targetDirs);
321
+ spinner.succeed(`Restored backup: ${timestamp}`);
322
+ } catch (error) {
323
+ spinner.fail("Failed to restore");
324
+ console.error(chalk3.red(error.message));
325
+ process.exit(1);
547
326
  }
548
- return agents;
549
327
  }
550
- function stopAgent(agentId) {
551
- const normalizedId = agentId.startsWith("agent-") ? agentId : `agent-${agentId.toLowerCase()}`;
552
- if (sessionExists(normalizedId)) {
553
- killSession(normalizedId);
328
+
329
+ // src/cli/commands/backup.ts
330
+ import chalk4 from "chalk";
331
+ async function backupListCommand(options) {
332
+ const backups = listBackups();
333
+ if (options.json) {
334
+ console.log(JSON.stringify(backups, null, 2));
335
+ return;
554
336
  }
555
- const state = getAgentState(normalizedId);
556
- if (state) {
557
- state.status = "stopped";
558
- saveAgentState(state);
337
+ if (backups.length === 0) {
338
+ console.log(chalk4.dim("No backups found."));
339
+ console.log(chalk4.dim("Backups are created automatically during sync."));
340
+ return;
341
+ }
342
+ console.log(chalk4.bold("Backups:\n"));
343
+ for (const backup2 of backups) {
344
+ console.log(` ${chalk4.cyan(backup2.timestamp)}`);
345
+ console.log(` ${chalk4.dim("Contains:")} ${backup2.targets.join(", ")}`);
559
346
  }
347
+ console.log();
348
+ console.log(chalk4.dim(`Total: ${backups.length} backups`));
349
+ console.log(chalk4.dim("Use `pan restore <timestamp>` to restore a backup."));
560
350
  }
561
- function messageAgent(agentId, message) {
562
- const normalizedId = agentId.startsWith("agent-") ? agentId : `agent-${agentId.toLowerCase()}`;
563
- if (!sessionExists(normalizedId)) {
564
- throw new Error(`Agent ${normalizedId} not running`);
351
+ async function backupCleanCommand(options) {
352
+ const keepCount = parseInt(options.keep || "10", 10);
353
+ const removed = cleanOldBackups(keepCount);
354
+ if (removed === 0) {
355
+ console.log(chalk4.dim(`No backups removed (keeping ${keepCount}).`));
356
+ } else {
357
+ console.log(chalk4.green(`Removed ${removed} old backup(s), keeping ${keepCount}.`));
565
358
  }
566
- sendKeys(normalizedId, message);
567
- const mailDir = join5(getAgentDir(normalizedId), "mail");
568
- mkdirSync4(mailDir, { recursive: true });
569
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
570
- writeFileSync3(
571
- join5(mailDir, `${timestamp}.md`),
572
- `# Message
359
+ }
573
360
 
574
- ${message}
575
- `
576
- );
361
+ // src/cli/commands/skills.ts
362
+ import { readdirSync as readdirSync2, readFileSync, existsSync as existsSync2 } from "fs";
363
+ import { join as join2 } from "path";
364
+ import chalk5 from "chalk";
365
+ function parseSkillFrontmatter(content) {
366
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
367
+ if (!match) return {};
368
+ const frontmatter = match[1];
369
+ const name = frontmatter.match(/name:\s*(.+)/)?.[1]?.trim();
370
+ const description = frontmatter.match(/description:\s*(.+)/)?.[1]?.trim();
371
+ return { name, description };
577
372
  }
578
- function detectCrashedAgents() {
579
- const agents = listRunningAgents();
580
- return agents.filter(
581
- (agent) => agent.status === "running" && !agent.tmuxActive
582
- );
373
+ function listSkills() {
374
+ if (!existsSync2(SKILLS_DIR)) return [];
375
+ const skills = [];
376
+ const dirs = readdirSync2(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
377
+ for (const dir of dirs) {
378
+ const skillFile = join2(SKILLS_DIR, dir.name, "SKILL.md");
379
+ if (!existsSync2(skillFile)) continue;
380
+ const content = readFileSync(skillFile, "utf8");
381
+ const { name, description } = parseSkillFrontmatter(content);
382
+ skills.push({
383
+ name: name || dir.name,
384
+ description: description || "(no description)",
385
+ path: skillFile
386
+ });
387
+ }
388
+ return skills.sort((a, b) => a.name.localeCompare(b.name));
583
389
  }
584
- function recoverAgent(agentId) {
585
- const normalizedId = agentId.startsWith("agent-") ? agentId : `agent-${agentId.toLowerCase()}`;
586
- const state = getAgentState(normalizedId);
587
- if (!state) {
588
- return null;
390
+ async function skillsCommand(options) {
391
+ const skills = listSkills();
392
+ if (options.json) {
393
+ console.log(JSON.stringify(skills, null, 2));
394
+ return;
589
395
  }
590
- if (sessionExists(normalizedId)) {
591
- return state;
396
+ console.log(chalk5.bold(`
397
+ Panopticon Skills (${skills.length})
398
+ `));
399
+ if (skills.length === 0) {
400
+ console.log(chalk5.yellow("No skills found."));
401
+ console.log(chalk5.dim("Skills should be in ~/.panopticon/skills/<name>/SKILL.md"));
402
+ return;
592
403
  }
593
- const healthFile = join5(getAgentDir(normalizedId), "health.json");
594
- let health = { consecutiveFailures: 0, killCount: 0, recoveryCount: 0 };
595
- if (existsSync5(healthFile)) {
596
- try {
597
- health = { ...health, ...JSON.parse(readFileSync4(healthFile, "utf-8")) };
598
- } catch {
599
- }
404
+ for (const skill of skills) {
405
+ console.log(chalk5.cyan(skill.name));
406
+ console.log(chalk5.dim(` ${skill.description}`));
600
407
  }
601
- health.recoveryCount = (health.recoveryCount || 0) + 1;
602
- writeFileSync3(healthFile, JSON.stringify(health, null, 2));
603
- const recoveryPrompt = generateRecoveryPrompt(state);
604
- const claudeCmd = `claude --dangerously-skip-permissions --model ${state.model} "${recoveryPrompt.replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
605
- createSession(normalizedId, state.workspace, claudeCmd);
606
- state.status = "running";
607
- state.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
608
- saveAgentState(state);
609
- return state;
408
+ console.log(`
409
+ ${chalk5.dim('Run "pan sync" to sync skills to Claude Code')}`);
610
410
  }
611
- function generateRecoveryPrompt(state) {
612
- const lines = [
613
- "# Agent Recovery",
614
- "",
615
- "\u26A0\uFE0F This agent session was recovered after a crash.",
616
- "",
617
- "## Previous Context",
618
- `- Issue: ${state.issueId}`,
619
- `- Workspace: ${state.workspace}`,
620
- `- Started: ${state.startedAt}`,
621
- "",
622
- "## Recovery Steps",
623
- "1. Check beads for context: `bd show " + state.issueId + "`",
624
- "2. Review recent git commits: `git log --oneline -10`",
625
- "3. Check hook for pending work: `pan work hook check`",
626
- "4. Resume from last known state",
627
- "",
628
- "## GUPP Reminder",
629
- '> "If there is work on your Hook, YOU MUST RUN IT."',
630
- ""
631
- ];
632
- const { hasWork } = checkHook(state.id);
633
- if (hasWork) {
634
- const guppPrompt = generateGUPPPrompt(state.id);
635
- if (guppPrompt) {
636
- lines.push("---");
637
- lines.push("");
638
- lines.push(guppPrompt);
639
- }
411
+
412
+ // src/cli/commands/work/issue.ts
413
+ import chalk6 from "chalk";
414
+ import ora4 from "ora";
415
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
416
+ import { join as join6, dirname as dirname2 } from "path";
417
+
418
+ // src/lib/agents.ts
419
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, readFileSync as readFileSync4, readdirSync as readdirSync5 } from "fs";
420
+ import { join as join5 } from "path";
421
+ import { execSync as execSync2 } from "child_process";
422
+ import { homedir } from "os";
423
+
424
+ // src/lib/tmux.ts
425
+ import { execSync } from "child_process";
426
+ import { writeFileSync, chmodSync } from "fs";
427
+ function listSessions() {
428
+ try {
429
+ const output = execSync('tmux list-sessions -F "#{session_name}|#{session_created}|#{session_attached}|#{session_windows}"', {
430
+ encoding: "utf8"
431
+ });
432
+ return output.trim().split("\n").filter(Boolean).map((line) => {
433
+ const [name, created, attached, windows] = line.split("|");
434
+ return {
435
+ name,
436
+ created: new Date(parseInt(created) * 1e3),
437
+ attached: attached === "1",
438
+ windows: parseInt(windows)
439
+ };
440
+ });
441
+ } catch {
442
+ return [];
640
443
  }
641
- return lines.join("\n");
642
444
  }
643
- function autoRecoverAgents() {
644
- const crashed = detectCrashedAgents();
645
- const recovered = [];
646
- const failed = [];
647
- for (const agent of crashed) {
648
- try {
649
- const result = recoverAgent(agent.id);
650
- if (result) {
651
- recovered.push(agent.id);
652
- } else {
653
- failed.push(agent.id);
654
- }
655
- } catch (error) {
656
- failed.push(agent.id);
657
- }
445
+ function sessionExists(name) {
446
+ try {
447
+ execSync(`tmux has-session -t ${name} 2>/dev/null`);
448
+ return true;
449
+ } catch {
450
+ return false;
658
451
  }
659
- return { recovered, failed };
660
452
  }
661
- function checkAndSetupHooks() {
662
- const { homedir: homedir9 } = __require("os");
663
- const settingsPath = join5(homedir9(), ".claude", "settings.json");
664
- const hookPath = join5(homedir9(), ".panopticon", "bin", "heartbeat-hook");
665
- if (existsSync5(settingsPath)) {
666
- try {
667
- const settingsContent = readFileSync4(settingsPath, "utf-8");
668
- const settings = JSON.parse(settingsContent);
669
- const postToolUse = settings?.hooks?.PostToolUse || [];
670
- const hookConfigured = postToolUse.some(
671
- (hookConfig) => hookConfig.hooks?.some(
672
- (hook) => hook.command === hookPath || hook.command?.includes("panopticon") || hook.command?.includes("heartbeat-hook")
673
- )
674
- );
675
- if (hookConfigured) {
676
- return;
677
- }
678
- } catch {
453
+ function createSession(name, cwd, initialCommand, options) {
454
+ const escapedCwd = cwd.replace(/"/g, '\\"');
455
+ let envFlags = "";
456
+ if (options?.env) {
457
+ for (const [key, value] of Object.entries(options.env)) {
458
+ envFlags += ` -e ${key}="${value.replace(/"/g, '\\"')}"`;
679
459
  }
680
460
  }
681
- try {
682
- console.log("Configuring Panopticon heartbeat hooks...");
683
- execSync2("pan setup hooks", { stdio: "pipe" });
684
- console.log("\u2713 Heartbeat hooks configured");
685
- } catch (error) {
686
- console.warn("\u26A0 Failed to auto-configure hooks. Run `pan setup hooks` manually.");
461
+ if (initialCommand && (initialCommand.includes("`") || initialCommand.includes("\n") || initialCommand.length > 500)) {
462
+ execSync(`tmux new-session -d -s ${name} -c "${escapedCwd}"${envFlags}`);
463
+ execSync("sleep 0.5");
464
+ const tmpFile = `/tmp/pan-cmd-${name}.sh`;
465
+ writeFileSync(tmpFile, initialCommand);
466
+ chmodSync(tmpFile, "755");
467
+ execSync(`tmux send-keys -t ${name} "bash ${tmpFile}" Enter`);
468
+ } else if (initialCommand) {
469
+ const cmd = `tmux new-session -d -s ${name} -c "${escapedCwd}"${envFlags} "${initialCommand.replace(/"/g, '\\"')}"`;
470
+ execSync(cmd);
471
+ } else {
472
+ execSync(`tmux new-session -d -s ${name} -c "${escapedCwd}"${envFlags}`);
687
473
  }
688
474
  }
689
- function writeTaskCache(agentId, issueId) {
690
- const cacheDir = join5(getAgentDir(agentId));
691
- mkdirSync4(cacheDir, { recursive: true });
692
- const cacheFile = join5(cacheDir, "current-task.json");
693
- writeFileSync3(
694
- cacheFile,
695
- JSON.stringify({
696
- id: issueId,
697
- title: `Working on ${issueId}`,
698
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
699
- }, null, 2)
700
- );
475
+ function killSession(name) {
476
+ execSync(`tmux kill-session -t ${name}`);
477
+ }
478
+ function sendKeys(sessionName, keys) {
479
+ const escapedKeys = keys.replace(/"/g, '\\"');
480
+ execSync(`tmux send-keys -t ${sessionName} "${escapedKeys}"`);
481
+ execSync(`tmux send-keys -t ${sessionName} Enter`);
482
+ }
483
+ function getAgentSessions() {
484
+ return listSessions().filter((s) => s.name.startsWith("agent-"));
701
485
  }
702
- var init_agents = __esm({
703
- "src/lib/agents.ts"() {
704
- "use strict";
705
- init_esm_shims();
706
- init_paths();
707
- init_tmux();
708
- init_hooks();
709
- init_cv();
710
- }
711
- });
712
486
 
713
- // package.json
714
- var require_package = __commonJS({
715
- "package.json"(exports, module) {
716
- module.exports = {
717
- name: "panopticon-cli",
718
- version: "0.3.2",
719
- description: "Multi-agent orchestration for AI coding assistants (Claude Code, Codex, Cursor, Gemini CLI)",
720
- keywords: [
721
- "ai-agents",
722
- "orchestration",
723
- "claude-code",
724
- "codex",
725
- "cursor",
726
- "gemini",
727
- "multi-agent",
728
- "devtools",
729
- "linear"
730
- ],
731
- author: "Edward Becker <edward.becker@mindyournow.com>",
732
- license: "MIT",
733
- repository: {
734
- type: "git",
735
- url: "https://github.com/eltmon/panopticon-cli.git"
736
- },
737
- homepage: "https://github.com/eltmon/panopticon-cli#readme",
738
- bugs: {
739
- url: "https://github.com/eltmon/panopticon-cli/issues"
740
- },
741
- type: "module",
742
- bin: {
743
- pan: "./dist/cli/index.js",
744
- panopticon: "./dist/cli/index.js"
745
- },
746
- main: "./dist/index.js",
747
- types: "./dist/index.d.ts",
748
- files: [
749
- "dist",
750
- "templates",
751
- "README.md",
752
- "LICENSE"
753
- ],
754
- engines: {
755
- node: ">=18.0.0"
756
- },
757
- scripts: {
758
- dev: "tsx watch src/cli/index.ts",
759
- build: "tsup",
760
- typecheck: "tsc --noEmit",
761
- lint: "eslint src/",
762
- test: "vitest --no-file-parallelism",
763
- "test:unit": "vitest run tests/unit",
764
- "test:integration": "vitest run tests/integration",
765
- "test:e2e": "vitest run tests/e2e",
766
- "test:coverage": "vitest run --coverage",
767
- docs: "typedoc",
768
- "docs:watch": "typedoc --watch",
769
- prepublishOnly: "npm run build",
770
- postinstall: "node scripts/postinstall.mjs || true"
771
- },
772
- dependencies: {
773
- "@iarna/toml": "^2.2.5",
774
- "@linear/sdk": "^70.0.0",
775
- "@octokit/rest": "^22.0.1",
776
- "better-sqlite3": "^12.6.2",
777
- chalk: "^5.6.2",
778
- commander: "^12.1.0",
779
- conf: "^12.0.0",
780
- execa: "^8.0.1",
781
- inquirer: "^9.3.8",
782
- ora: "^8.2.0",
783
- rally: "^2.1.3",
784
- yaml: "^2.8.2"
785
- },
786
- devDependencies: {
787
- "@types/better-sqlite3": "^7.6.13",
788
- "@types/inquirer": "^9.0.9",
789
- "@types/node": "^20.10.0",
790
- "@vitest/coverage-v8": "^1.0.4",
791
- eslint: "^8.55.0",
792
- tsup: "^8.0.1",
793
- tsx: "^4.6.2",
794
- typedoc: "^0.25.0",
795
- typescript: "^5.3.2",
796
- vitest: "^1.0.4"
797
- }
487
+ // src/lib/hooks.ts
488
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, readdirSync as readdirSync3, unlinkSync } from "fs";
489
+ import { join as join3 } from "path";
490
+ function getHookDir(agentId) {
491
+ return join3(AGENTS_DIR, agentId);
492
+ }
493
+ function getHookFile(agentId) {
494
+ return join3(getHookDir(agentId), "hook.json");
495
+ }
496
+ function getMailDir(agentId) {
497
+ return join3(getHookDir(agentId), "mail");
498
+ }
499
+ function initHook(agentId) {
500
+ const hookDir = getHookDir(agentId);
501
+ const mailDir = getMailDir(agentId);
502
+ mkdirSync2(hookDir, { recursive: true });
503
+ mkdirSync2(mailDir, { recursive: true });
504
+ const hookFile = getHookFile(agentId);
505
+ if (!existsSync3(hookFile)) {
506
+ const hook = {
507
+ agentId,
508
+ items: []
798
509
  };
510
+ writeFileSync2(hookFile, JSON.stringify(hook, null, 2));
799
511
  }
800
- });
801
-
802
- // src/cli/index.ts
803
- init_esm_shims();
804
- import { Command } from "commander";
805
- import chalk29 from "chalk";
806
-
807
- // src/cli/commands/init.ts
808
- init_esm_shims();
809
- init_paths();
810
- import { existsSync, mkdirSync, readdirSync, cpSync } from "fs";
811
- import { join, dirname } from "path";
812
- import { fileURLToPath } from "url";
813
- import chalk from "chalk";
814
- import ora from "ora";
815
- var __filename = fileURLToPath(import.meta.url);
816
- var __dirname = dirname(__filename);
817
- var PACKAGE_ROOT = join(__dirname, "..", "..");
818
- var BUNDLED_SKILLS_DIR = join(PACKAGE_ROOT, "skills");
819
- var BUNDLED_AGENTS_DIR = join(PACKAGE_ROOT, "agents");
820
- function copyBundledSkills() {
821
- if (!existsSync(BUNDLED_SKILLS_DIR)) {
822
- return 0;
823
- }
824
- if (!existsSync(SKILLS_DIR)) {
825
- mkdirSync(SKILLS_DIR, { recursive: true });
826
- }
827
- const skills = readdirSync(BUNDLED_SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
828
- let copied = 0;
829
- for (const skill of skills) {
830
- const sourcePath = join(BUNDLED_SKILLS_DIR, skill.name);
831
- const targetPath = join(SKILLS_DIR, skill.name);
832
- cpSync(sourcePath, targetPath, { recursive: true });
833
- copied++;
834
- }
835
- return copied;
836
512
  }
837
- function copyBundledAgents() {
838
- if (!existsSync(BUNDLED_AGENTS_DIR)) {
839
- return 0;
513
+ function getHook(agentId) {
514
+ const hookFile = getHookFile(agentId);
515
+ if (!existsSync3(hookFile)) {
516
+ return null;
840
517
  }
841
- if (!existsSync(AGENTS_DIR)) {
842
- mkdirSync(AGENTS_DIR, { recursive: true });
518
+ try {
519
+ const content = readFileSync2(hookFile, "utf-8");
520
+ return JSON.parse(content);
521
+ } catch {
522
+ return null;
843
523
  }
844
- const agents = readdirSync(BUNDLED_AGENTS_DIR, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md"));
845
- let copied = 0;
846
- for (const agent of agents) {
847
- const sourcePath = join(BUNDLED_AGENTS_DIR, agent.name);
848
- const targetPath = join(AGENTS_DIR, agent.name);
849
- cpSync(sourcePath, targetPath);
850
- copied++;
524
+ }
525
+ function pushToHook(agentId, item) {
526
+ initHook(agentId);
527
+ const hook = getHook(agentId) || { agentId, items: [] };
528
+ const newItem = {
529
+ ...item,
530
+ id: `hook-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
531
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
532
+ };
533
+ hook.items.push(newItem);
534
+ writeFileSync2(getHookFile(agentId), JSON.stringify(hook, null, 2));
535
+ return newItem;
536
+ }
537
+ function checkHook(agentId) {
538
+ const hook = getHook(agentId);
539
+ if (!hook || hook.items.length === 0) {
540
+ const mailDir = getMailDir(agentId);
541
+ if (existsSync3(mailDir)) {
542
+ const mails = readdirSync3(mailDir).filter((f) => f.endsWith(".json"));
543
+ if (mails.length > 0) {
544
+ const mailItems = mails.map((file) => {
545
+ try {
546
+ const content = readFileSync2(join3(mailDir, file), "utf-8");
547
+ return JSON.parse(content);
548
+ } catch {
549
+ return null;
550
+ }
551
+ }).filter(Boolean);
552
+ return {
553
+ hasWork: mailItems.length > 0,
554
+ urgentCount: mailItems.filter((i) => i.priority === "urgent").length,
555
+ items: mailItems
556
+ };
557
+ }
558
+ }
559
+ return { hasWork: false, urgentCount: 0, items: [] };
851
560
  }
852
- return copied;
561
+ const now = /* @__PURE__ */ new Date();
562
+ const activeItems = hook.items.filter((item) => {
563
+ if (item.expiresAt) {
564
+ return new Date(item.expiresAt) > now;
565
+ }
566
+ return true;
567
+ });
568
+ const priorityOrder = { urgent: 0, high: 1, normal: 2, low: 3 };
569
+ activeItems.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
570
+ return {
571
+ hasWork: activeItems.length > 0,
572
+ urgentCount: activeItems.filter((i) => i.priority === "urgent").length,
573
+ items: activeItems
574
+ };
853
575
  }
854
- async function initCommand() {
855
- const spinner = ora("Initializing Panopticon...").start();
856
- if (existsSync(CONFIG_FILE)) {
857
- spinner.info("Panopticon already initialized");
858
- console.log(chalk.dim(` Config: ${CONFIG_FILE}`));
859
- console.log(chalk.dim(` Home: ${PANOPTICON_HOME}`));
860
- console.log(chalk.dim(" Run `pan sync` to update skills"));
861
- return;
576
+ function popFromHook(agentId, itemId) {
577
+ const hook = getHook(agentId);
578
+ if (!hook) return false;
579
+ const index = hook.items.findIndex((i) => i.id === itemId);
580
+ if (index === -1) return false;
581
+ hook.items.splice(index, 1);
582
+ hook.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
583
+ writeFileSync2(getHookFile(agentId), JSON.stringify(hook, null, 2));
584
+ return true;
585
+ }
586
+ function clearHook(agentId) {
587
+ const hook = getHook(agentId);
588
+ if (!hook) return;
589
+ hook.items = [];
590
+ hook.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
591
+ writeFileSync2(getHookFile(agentId), JSON.stringify(hook, null, 2));
592
+ }
593
+ function sendMail(toAgentId, from, message, priority = "normal") {
594
+ initHook(toAgentId);
595
+ const mailDir = getMailDir(toAgentId);
596
+ const mailItem = {
597
+ id: `mail-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
598
+ type: "message",
599
+ priority,
600
+ source: from,
601
+ payload: { message },
602
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
603
+ };
604
+ writeFileSync2(
605
+ join3(mailDir, `${mailItem.id}.json`),
606
+ JSON.stringify(mailItem, null, 2)
607
+ );
608
+ }
609
+ function generateGUPPPrompt(agentId) {
610
+ const { hasWork, urgentCount, items } = checkHook(agentId);
611
+ if (!hasWork) return null;
612
+ const lines = [
613
+ "# GUPP: Work Found on Your Hook",
614
+ "",
615
+ '> "If there is work on your Hook, YOU MUST RUN IT."',
616
+ ""
617
+ ];
618
+ if (urgentCount > 0) {
619
+ lines.push(`\u26A0\uFE0F **${urgentCount} URGENT item(s) require immediate attention**`);
620
+ lines.push("");
862
621
  }
863
- try {
864
- for (const dir of INIT_DIRS) {
865
- if (!existsSync(dir)) {
866
- mkdirSync(dir, { recursive: true });
867
- }
622
+ lines.push(`## Pending Work Items (${items.length})`);
623
+ lines.push("");
624
+ for (const item of items) {
625
+ const priorityEmoji = {
626
+ urgent: "\u{1F534}",
627
+ high: "\u{1F7E0}",
628
+ normal: "\u{1F7E2}",
629
+ low: "\u26AA"
630
+ }[item.priority];
631
+ lines.push(`### ${priorityEmoji} ${item.type.toUpperCase()}: ${item.id}`);
632
+ lines.push(`- Source: ${item.source}`);
633
+ lines.push(`- Created: ${item.createdAt}`);
634
+ if (item.payload.issueId) {
635
+ lines.push(`- Issue: ${item.payload.issueId}`);
868
636
  }
869
- spinner.text = "Created directories...";
870
- const config = getDefaultConfig();
871
- saveConfig(config);
872
- spinner.text = "Created config...";
873
- spinner.text = "Installing bundled skills...";
874
- const skillsCopied = copyBundledSkills();
875
- spinner.text = "Installing bundled agents...";
876
- const agentsCopied = copyBundledAgents();
877
- const shell = detectShell();
878
- const rcFile = getShellRcFile(shell);
879
- if (rcFile && existsSync(rcFile)) {
880
- addAlias(rcFile);
881
- spinner.succeed("Panopticon initialized!");
882
- console.log("");
883
- console.log(chalk.green("\u2713") + " Created " + chalk.cyan(PANOPTICON_HOME));
884
- console.log(chalk.green("\u2713") + " Created " + chalk.cyan(CONFIG_FILE));
885
- if (skillsCopied > 0) {
886
- console.log(chalk.green("\u2713") + ` Installed ${skillsCopied} bundled skills`);
887
- }
888
- if (agentsCopied > 0) {
889
- console.log(chalk.green("\u2713") + ` Installed ${agentsCopied} bundled agents`);
890
- }
891
- console.log(chalk.green("\u2713") + " " + getAliasInstructions(shell));
892
- } else {
893
- spinner.succeed("Panopticon initialized!");
894
- console.log("");
895
- console.log(chalk.green("\u2713") + " Created " + chalk.cyan(PANOPTICON_HOME));
896
- console.log(chalk.green("\u2713") + " Created " + chalk.cyan(CONFIG_FILE));
897
- if (skillsCopied > 0) {
898
- console.log(chalk.green("\u2713") + ` Installed ${skillsCopied} bundled skills`);
899
- }
900
- if (agentsCopied > 0) {
901
- console.log(chalk.green("\u2713") + ` Installed ${agentsCopied} bundled agents`);
902
- }
903
- console.log(chalk.yellow("!") + " Could not detect shell. Add alias manually:");
904
- console.log(chalk.dim(' alias pan="panopticon"'));
637
+ if (item.payload.message) {
638
+ lines.push(`- Message: ${item.payload.message}`);
905
639
  }
906
- console.log("");
907
- console.log("Next steps:");
908
- console.log(chalk.dim(" 1. Run: pan sync"));
909
- console.log(chalk.dim(" 2. Start dashboard: pan up"));
910
- } catch (error) {
911
- spinner.fail("Failed to initialize");
912
- console.error(chalk.red(error.message));
913
- process.exit(1);
640
+ if (item.payload.action) {
641
+ lines.push(`- Action: ${item.payload.action}`);
642
+ }
643
+ lines.push("");
914
644
  }
645
+ lines.push("---");
646
+ lines.push("");
647
+ lines.push("Execute these items in priority order. Use `bd hook pop <id>` after completing each item.");
648
+ return lines.join("\n");
915
649
  }
916
650
 
917
- // src/cli/commands/sync.ts
918
- init_esm_shims();
919
- import chalk2 from "chalk";
920
- import ora2 from "ora";
921
- init_paths();
922
- async function syncCommand(options) {
923
- const config = loadConfig();
924
- const targets = config.sync?.targets;
925
- if (!targets || !Array.isArray(targets) || targets.length === 0) {
926
- console.log(chalk2.yellow("No sync targets configured."));
927
- console.log(chalk2.dim("Edit ~/.panopticon/config.toml to add targets to the [sync] section."));
928
- console.log(chalk2.dim('Example: targets = ["claude-code", "cursor"]'));
929
- return;
930
- }
931
- const validTargets = targets;
932
- if (options.dryRun) {
933
- console.log(chalk2.bold("Sync Plan (dry run):\n"));
934
- const hooksPlan = planHooksSync();
935
- if (hooksPlan.length > 0) {
936
- console.log(chalk2.cyan("hooks (bin scripts):"));
937
- for (const hook of hooksPlan) {
938
- const icon = hook.status === "new" ? chalk2.green("+") : chalk2.blue("\u21BB");
939
- const status = hook.status === "new" ? "" : chalk2.dim("[update]");
940
- console.log(` ${icon} ${hook.name} ${status}`);
941
- }
942
- console.log("");
651
+ // src/lib/cv.ts
652
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "fs";
653
+ import { join as join4 } from "path";
654
+ function getCVFile(agentId) {
655
+ return join4(AGENTS_DIR, agentId, "cv.json");
656
+ }
657
+ function getAgentCV(agentId) {
658
+ const cvFile = getCVFile(agentId);
659
+ if (existsSync4(cvFile)) {
660
+ try {
661
+ return JSON.parse(readFileSync3(cvFile, "utf-8"));
662
+ } catch {
943
663
  }
944
- for (const runtime of validTargets) {
945
- const plan = planSync(runtime);
946
- console.log(chalk2.cyan(`${runtime}:`));
947
- if (plan.skills.length === 0 && plan.commands.length === 0 && plan.agents.length === 0) {
948
- console.log(chalk2.dim(" (nothing to sync)"));
949
- continue;
950
- }
951
- for (const item of plan.skills) {
952
- const icon = item.status === "conflict" ? chalk2.yellow("!") : chalk2.green("+");
953
- const status = item.status === "conflict" ? chalk2.yellow("[conflict]") : "";
954
- console.log(` ${icon} skill/${item.name} ${status}`);
955
- }
956
- for (const item of plan.commands) {
957
- const icon = item.status === "conflict" ? chalk2.yellow("!") : chalk2.green("+");
958
- const status = item.status === "conflict" ? chalk2.yellow("[conflict]") : "";
959
- console.log(` ${icon} command/${item.name} ${status}`);
960
- }
961
- for (const item of plan.agents) {
962
- const icon = item.status === "conflict" ? chalk2.yellow("!") : chalk2.green("+");
963
- const status = item.status === "conflict" ? chalk2.yellow("[conflict]") : "";
964
- console.log(` ${icon} agent/${item.name} ${status}`);
664
+ }
665
+ const cv = {
666
+ agentId,
667
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
668
+ lastActive: (/* @__PURE__ */ new Date()).toISOString(),
669
+ runtime: "claude",
670
+ model: "sonnet",
671
+ stats: {
672
+ totalIssues: 0,
673
+ successCount: 0,
674
+ failureCount: 0,
675
+ abandonedCount: 0,
676
+ avgDuration: 0,
677
+ successRate: 0
678
+ },
679
+ skillsUsed: [],
680
+ recentWork: []
681
+ };
682
+ saveAgentCV(cv);
683
+ return cv;
684
+ }
685
+ function saveAgentCV(cv) {
686
+ const dir = join4(AGENTS_DIR, cv.agentId);
687
+ mkdirSync3(dir, { recursive: true });
688
+ writeFileSync3(getCVFile(cv.agentId), JSON.stringify(cv, null, 2));
689
+ }
690
+ function startWork(agentId, issueId, skills) {
691
+ const cv = getAgentCV(agentId);
692
+ const entry = {
693
+ issueId,
694
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
695
+ outcome: "in_progress",
696
+ skills
697
+ };
698
+ cv.recentWork.unshift(entry);
699
+ cv.stats.totalIssues++;
700
+ cv.lastActive = (/* @__PURE__ */ new Date()).toISOString();
701
+ if (skills) {
702
+ for (const skill of skills) {
703
+ if (!cv.skillsUsed.includes(skill)) {
704
+ cv.skillsUsed.push(skill);
965
705
  }
966
- console.log("");
967
706
  }
968
- console.log(chalk2.dim("Run without --dry-run to apply changes."));
969
- return;
970
707
  }
971
- if (config.sync.backup_before_sync) {
972
- const spinner2 = ora2("Creating backup...").start();
973
- const backupDirs = validTargets.flatMap((r) => [
974
- SYNC_TARGETS[r].skills,
975
- SYNC_TARGETS[r].commands,
976
- SYNC_TARGETS[r].agents
977
- ]);
978
- const backup2 = createBackup(backupDirs);
979
- if (backup2.targets.length > 0) {
980
- spinner2.succeed(`Backup created: ${backup2.timestamp}`);
981
- } else {
982
- spinner2.info("No existing content to backup");
983
- }
984
- if (options.backupOnly) {
985
- return;
986
- }
708
+ if (cv.recentWork.length > 50) {
709
+ cv.recentWork = cv.recentWork.slice(0, 50);
987
710
  }
988
- const spinner = ora2("Syncing...").start();
989
- let totalCreated = 0;
990
- let totalConflicts = 0;
991
- for (const runtime of validTargets) {
992
- spinner.text = `Syncing to ${runtime}...`;
993
- const result = executeSync(runtime, { force: options.force });
994
- totalCreated += result.created.length;
995
- totalConflicts += result.conflicts.length;
996
- if (result.conflicts.length > 0 && !options.force) {
997
- console.log("");
998
- console.log(chalk2.yellow(`Conflicts in ${runtime}:`));
999
- for (const name of result.conflicts) {
1000
- console.log(chalk2.dim(` - ${name} (use --force to overwrite)`));
1001
- }
711
+ saveAgentCV(cv);
712
+ }
713
+ function getAgentRankings() {
714
+ const rankings = [];
715
+ if (!existsSync4(AGENTS_DIR)) return rankings;
716
+ const dirs = readdirSync4(AGENTS_DIR, { withFileTypes: true }).filter(
717
+ (d) => d.isDirectory()
718
+ );
719
+ for (const dir of dirs) {
720
+ const cv = getAgentCV(dir.name);
721
+ if (cv.stats.totalIssues > 0) {
722
+ rankings.push({
723
+ agentId: dir.name,
724
+ successRate: cv.stats.successRate,
725
+ totalIssues: cv.stats.totalIssues,
726
+ avgDuration: cv.stats.avgDuration
727
+ });
1002
728
  }
1003
729
  }
1004
- if (totalConflicts > 0 && !options.force) {
1005
- spinner.warn(`Synced ${totalCreated} items, ${totalConflicts} conflicts`);
1006
- console.log("");
1007
- console.log(chalk2.dim("Use --force to overwrite conflicting items."));
1008
- } else {
1009
- spinner.succeed(`Synced ${totalCreated} items to ${validTargets.join(", ")}`);
730
+ rankings.sort((a, b) => {
731
+ if (b.successRate !== a.successRate) {
732
+ return b.successRate - a.successRate;
733
+ }
734
+ return b.totalIssues - a.totalIssues;
735
+ });
736
+ return rankings;
737
+ }
738
+ function formatCV(cv) {
739
+ const lines = [
740
+ `# Agent CV: ${cv.agentId}`,
741
+ "",
742
+ `Runtime: ${cv.runtime} (${cv.model})`,
743
+ `Created: ${cv.createdAt}`,
744
+ `Last Active: ${cv.lastActive}`,
745
+ "",
746
+ "## Statistics",
747
+ "",
748
+ `- Total Issues: ${cv.stats.totalIssues}`,
749
+ `- Success Rate: ${(cv.stats.successRate * 100).toFixed(1)}%`,
750
+ `- Successes: ${cv.stats.successCount}`,
751
+ `- Failures: ${cv.stats.failureCount}`,
752
+ `- Abandoned: ${cv.stats.abandonedCount}`,
753
+ `- Avg Duration: ${cv.stats.avgDuration} minutes`,
754
+ ""
755
+ ];
756
+ if (cv.skillsUsed.length > 0) {
757
+ lines.push("## Skills Used");
758
+ lines.push("");
759
+ lines.push(cv.skillsUsed.join(", "));
760
+ lines.push("");
1010
761
  }
1011
- const hooksSpinner = ora2("Syncing hooks...").start();
1012
- const hooksResult = syncHooks();
1013
- if (hooksResult.errors.length > 0) {
1014
- hooksSpinner.warn(`Synced ${hooksResult.synced.length} hooks, ${hooksResult.errors.length} errors`);
1015
- for (const error of hooksResult.errors) {
1016
- console.log(chalk2.red(` \u2717 ${error}`));
762
+ if (cv.recentWork.length > 0) {
763
+ lines.push("## Recent Work");
764
+ lines.push("");
765
+ for (const work of cv.recentWork.slice(0, 10)) {
766
+ const statusIcon = {
767
+ success: "\u2713",
768
+ failed: "\u2717",
769
+ abandoned: "\u2298",
770
+ in_progress: "\u25CF"
771
+ }[work.outcome];
772
+ const duration = work.duration ? ` (${work.duration}m)` : "";
773
+ lines.push(`${statusIcon} ${work.issueId}${duration}`);
774
+ if (work.failureReason) {
775
+ lines.push(` Reason: ${work.failureReason}`);
776
+ }
1017
777
  }
1018
- } else if (hooksResult.synced.length > 0) {
1019
- hooksSpinner.succeed(`Synced ${hooksResult.synced.length} hooks to ~/.panopticon/bin/`);
1020
- } else {
1021
- hooksSpinner.info("No hooks to sync");
778
+ lines.push("");
1022
779
  }
780
+ return lines.join("\n");
1023
781
  }
1024
782
 
1025
- // src/cli/commands/restore.ts
1026
- init_esm_shims();
1027
- import chalk3 from "chalk";
1028
- import ora3 from "ora";
1029
- import inquirer from "inquirer";
1030
- init_paths();
1031
- async function restoreCommand(timestamp) {
1032
- const backups = listBackups();
1033
- if (backups.length === 0) {
1034
- console.log(chalk3.yellow("No backups found."));
1035
- return;
783
+ // src/lib/agents.ts
784
+ function getAgentDir(agentId) {
785
+ return join5(AGENTS_DIR, agentId);
786
+ }
787
+ function getAgentState(agentId) {
788
+ const stateFile = join5(getAgentDir(agentId), "state.json");
789
+ if (!existsSync5(stateFile)) return null;
790
+ const content = readFileSync4(stateFile, "utf8");
791
+ return JSON.parse(content);
792
+ }
793
+ function saveAgentState(state) {
794
+ const dir = getAgentDir(state.id);
795
+ mkdirSync4(dir, { recursive: true });
796
+ writeFileSync4(
797
+ join5(dir, "state.json"),
798
+ JSON.stringify(state, null, 2)
799
+ );
800
+ }
801
+ function spawnAgent(options) {
802
+ const agentId = `agent-${options.issueId.toLowerCase()}`;
803
+ if (sessionExists(agentId)) {
804
+ throw new Error(`Agent ${agentId} already running. Use 'pan work tell' to message it.`);
1036
805
  }
1037
- if (!timestamp) {
1038
- console.log(chalk3.bold("Available backups:\n"));
1039
- for (const backup2 of backups.slice(0, 10)) {
1040
- console.log(` ${chalk3.cyan(backup2.timestamp)} - ${backup2.targets.join(", ")}`);
806
+ initHook(agentId);
807
+ const state = {
808
+ id: agentId,
809
+ issueId: options.issueId,
810
+ workspace: options.workspace,
811
+ runtime: options.runtime || "claude",
812
+ model: options.model || "sonnet",
813
+ status: "starting",
814
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
815
+ // Initialize Phase 4 fields
816
+ handoffCount: 0,
817
+ costSoFar: 0
818
+ };
819
+ saveAgentState(state);
820
+ let prompt = options.prompt || "";
821
+ const { hasWork, items } = checkHook(agentId);
822
+ if (hasWork) {
823
+ const guppPrompt = generateGUPPPrompt(agentId);
824
+ if (guppPrompt) {
825
+ prompt = guppPrompt + "\n\n---\n\n" + prompt;
1041
826
  }
1042
- if (backups.length > 10) {
1043
- console.log(chalk3.dim(` ... and ${backups.length - 10} more`));
827
+ }
828
+ const promptFile = join5(getAgentDir(agentId), "initial-prompt.md");
829
+ if (prompt) {
830
+ writeFileSync4(promptFile, prompt);
831
+ }
832
+ checkAndSetupHooks();
833
+ writeTaskCache(agentId, options.issueId);
834
+ const claudeCmd = `claude --dangerously-skip-permissions --model ${state.model}`;
835
+ createSession(agentId, options.workspace, claudeCmd, {
836
+ env: {
837
+ PANOPTICON_AGENT_ID: agentId
1044
838
  }
1045
- console.log("");
1046
- const { selected } = await inquirer.prompt([
1047
- {
1048
- type: "list",
1049
- name: "selected",
1050
- message: "Select backup to restore:",
1051
- choices: backups.slice(0, 10).map((b) => ({
1052
- name: `${b.timestamp} (${b.targets.join(", ")})`,
1053
- value: b.timestamp
1054
- }))
839
+ });
840
+ if (prompt) {
841
+ let ready = false;
842
+ for (let i = 0; i < 15; i++) {
843
+ execSync2("sleep 1");
844
+ try {
845
+ const pane = execSync2(`tmux capture-pane -t ${agentId} -p`, { encoding: "utf-8" });
846
+ if (pane.includes("\u276F") || pane.includes(">")) {
847
+ ready = true;
848
+ break;
849
+ }
850
+ } catch {
1055
851
  }
1056
- ]);
1057
- timestamp = selected;
852
+ }
853
+ if (ready) {
854
+ execSync2(`tmux load-buffer "${promptFile}"`);
855
+ execSync2(`tmux paste-buffer -t ${agentId}`);
856
+ execSync2("sleep 0.5");
857
+ execSync2(`tmux send-keys -t ${agentId} Enter`);
858
+ } else {
859
+ console.error("Claude did not become ready in time, prompt not sent");
860
+ }
1058
861
  }
1059
- const { confirm } = await inquirer.prompt([
1060
- {
1061
- type: "confirm",
1062
- name: "confirm",
1063
- message: `Restore backup ${timestamp}? This will overwrite current files.`,
1064
- default: false
862
+ state.status = "running";
863
+ saveAgentState(state);
864
+ startWork(agentId, options.issueId);
865
+ return state;
866
+ }
867
+ function listRunningAgents() {
868
+ const tmuxSessions = getAgentSessions();
869
+ const tmuxNames = new Set(tmuxSessions.map((s) => s.name));
870
+ const agents = [];
871
+ if (!existsSync5(AGENTS_DIR)) return agents;
872
+ const dirs = readdirSync5(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
873
+ for (const dir of dirs) {
874
+ const state = getAgentState(dir.name);
875
+ if (state) {
876
+ agents.push({
877
+ ...state,
878
+ tmuxActive: tmuxNames.has(state.id)
879
+ });
1065
880
  }
1066
- ]);
1067
- if (!confirm) {
1068
- console.log(chalk3.dim("Restore cancelled."));
1069
- return;
1070
881
  }
1071
- const spinner = ora3("Restoring backup...").start();
1072
- try {
1073
- const config = loadConfig();
1074
- const targets = config.sync.targets;
1075
- const targetDirs = {};
1076
- for (const runtime of targets) {
1077
- targetDirs[`${runtime}-skills`] = SYNC_TARGETS[runtime].skills;
1078
- targetDirs[`${runtime}-commands`] = SYNC_TARGETS[runtime].commands;
1079
- targetDirs["skills"] = SYNC_TARGETS[runtime].skills;
1080
- targetDirs["commands"] = SYNC_TARGETS[runtime].commands;
1081
- }
1082
- restoreBackup(timestamp, targetDirs);
1083
- spinner.succeed(`Restored backup: ${timestamp}`);
1084
- } catch (error) {
1085
- spinner.fail("Failed to restore");
1086
- console.error(chalk3.red(error.message));
1087
- process.exit(1);
882
+ return agents;
883
+ }
884
+ function stopAgent(agentId) {
885
+ const normalizedId = agentId.startsWith("agent-") ? agentId : `agent-${agentId.toLowerCase()}`;
886
+ if (sessionExists(normalizedId)) {
887
+ killSession(normalizedId);
888
+ }
889
+ const state = getAgentState(normalizedId);
890
+ if (state) {
891
+ state.status = "stopped";
892
+ saveAgentState(state);
1088
893
  }
1089
894
  }
895
+ function messageAgent(agentId, message) {
896
+ const normalizedId = agentId.startsWith("agent-") ? agentId : `agent-${agentId.toLowerCase()}`;
897
+ if (!sessionExists(normalizedId)) {
898
+ throw new Error(`Agent ${normalizedId} not running`);
899
+ }
900
+ sendKeys(normalizedId, message);
901
+ const mailDir = join5(getAgentDir(normalizedId), "mail");
902
+ mkdirSync4(mailDir, { recursive: true });
903
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
904
+ writeFileSync4(
905
+ join5(mailDir, `${timestamp}.md`),
906
+ `# Message
1090
907
 
1091
- // src/cli/commands/backup.ts
1092
- init_esm_shims();
1093
- import chalk4 from "chalk";
1094
- async function backupListCommand(options) {
1095
- const backups = listBackups();
1096
- if (options.json) {
1097
- console.log(JSON.stringify(backups, null, 2));
1098
- return;
908
+ ${message}
909
+ `
910
+ );
911
+ }
912
+ function detectCrashedAgents() {
913
+ const agents = listRunningAgents();
914
+ return agents.filter(
915
+ (agent) => agent.status === "running" && !agent.tmuxActive
916
+ );
917
+ }
918
+ function recoverAgent(agentId) {
919
+ const normalizedId = agentId.startsWith("agent-") ? agentId : `agent-${agentId.toLowerCase()}`;
920
+ const state = getAgentState(normalizedId);
921
+ if (!state) {
922
+ return null;
1099
923
  }
1100
- if (backups.length === 0) {
1101
- console.log(chalk4.dim("No backups found."));
1102
- console.log(chalk4.dim("Backups are created automatically during sync."));
1103
- return;
924
+ if (sessionExists(normalizedId)) {
925
+ return state;
1104
926
  }
1105
- console.log(chalk4.bold("Backups:\n"));
1106
- for (const backup2 of backups) {
1107
- console.log(` ${chalk4.cyan(backup2.timestamp)}`);
1108
- console.log(` ${chalk4.dim("Contains:")} ${backup2.targets.join(", ")}`);
927
+ const healthFile = join5(getAgentDir(normalizedId), "health.json");
928
+ let health = { consecutiveFailures: 0, killCount: 0, recoveryCount: 0 };
929
+ if (existsSync5(healthFile)) {
930
+ try {
931
+ health = { ...health, ...JSON.parse(readFileSync4(healthFile, "utf-8")) };
932
+ } catch {
933
+ }
1109
934
  }
1110
- console.log();
1111
- console.log(chalk4.dim(`Total: ${backups.length} backups`));
1112
- console.log(chalk4.dim("Use `pan restore <timestamp>` to restore a backup."));
935
+ health.recoveryCount = (health.recoveryCount || 0) + 1;
936
+ writeFileSync4(healthFile, JSON.stringify(health, null, 2));
937
+ const recoveryPrompt = generateRecoveryPrompt(state);
938
+ const claudeCmd = `claude --dangerously-skip-permissions --model ${state.model} "${recoveryPrompt.replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
939
+ createSession(normalizedId, state.workspace, claudeCmd);
940
+ state.status = "running";
941
+ state.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
942
+ saveAgentState(state);
943
+ return state;
1113
944
  }
1114
- async function backupCleanCommand(options) {
1115
- const keepCount = parseInt(options.keep || "10", 10);
1116
- const removed = cleanOldBackups(keepCount);
1117
- if (removed === 0) {
1118
- console.log(chalk4.dim(`No backups removed (keeping ${keepCount}).`));
1119
- } else {
1120
- console.log(chalk4.green(`Removed ${removed} old backup(s), keeping ${keepCount}.`));
945
+ function generateRecoveryPrompt(state) {
946
+ const lines = [
947
+ "# Agent Recovery",
948
+ "",
949
+ "\u26A0\uFE0F This agent session was recovered after a crash.",
950
+ "",
951
+ "## Previous Context",
952
+ `- Issue: ${state.issueId}`,
953
+ `- Workspace: ${state.workspace}`,
954
+ `- Started: ${state.startedAt}`,
955
+ "",
956
+ "## Recovery Steps",
957
+ "1. Check beads for context: `bd show " + state.issueId + "`",
958
+ "2. Review recent git commits: `git log --oneline -10`",
959
+ "3. Check hook for pending work: `pan work hook check`",
960
+ "4. Resume from last known state",
961
+ "",
962
+ "## GUPP Reminder",
963
+ '> "If there is work on your Hook, YOU MUST RUN IT."',
964
+ ""
965
+ ];
966
+ const { hasWork } = checkHook(state.id);
967
+ if (hasWork) {
968
+ const guppPrompt = generateGUPPPrompt(state.id);
969
+ if (guppPrompt) {
970
+ lines.push("---");
971
+ lines.push("");
972
+ lines.push(guppPrompt);
973
+ }
1121
974
  }
975
+ return lines.join("\n");
1122
976
  }
1123
-
1124
- // src/cli/commands/skills.ts
1125
- init_esm_shims();
1126
- init_paths();
1127
- import { readdirSync as readdirSync2, readFileSync, existsSync as existsSync2 } from "fs";
1128
- import { join as join2 } from "path";
1129
- import chalk5 from "chalk";
1130
- function parseSkillFrontmatter(content) {
1131
- const match = content.match(/^---\n([\s\S]*?)\n---/);
1132
- if (!match) return {};
1133
- const frontmatter = match[1];
1134
- const name = frontmatter.match(/name:\s*(.+)/)?.[1]?.trim();
1135
- const description = frontmatter.match(/description:\s*(.+)/)?.[1]?.trim();
1136
- return { name, description };
1137
- }
1138
- function listSkills() {
1139
- if (!existsSync2(SKILLS_DIR)) return [];
1140
- const skills = [];
1141
- const dirs = readdirSync2(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
1142
- for (const dir of dirs) {
1143
- const skillFile = join2(SKILLS_DIR, dir.name, "SKILL.md");
1144
- if (!existsSync2(skillFile)) continue;
1145
- const content = readFileSync(skillFile, "utf8");
1146
- const { name, description } = parseSkillFrontmatter(content);
1147
- skills.push({
1148
- name: name || dir.name,
1149
- description: description || "(no description)",
1150
- path: skillFile
1151
- });
977
+ function autoRecoverAgents() {
978
+ const crashed = detectCrashedAgents();
979
+ const recovered = [];
980
+ const failed = [];
981
+ for (const agent of crashed) {
982
+ try {
983
+ const result = recoverAgent(agent.id);
984
+ if (result) {
985
+ recovered.push(agent.id);
986
+ } else {
987
+ failed.push(agent.id);
988
+ }
989
+ } catch (error) {
990
+ failed.push(agent.id);
991
+ }
1152
992
  }
1153
- return skills.sort((a, b) => a.name.localeCompare(b.name));
993
+ return { recovered, failed };
1154
994
  }
1155
- async function skillsCommand(options) {
1156
- const skills = listSkills();
1157
- if (options.json) {
1158
- console.log(JSON.stringify(skills, null, 2));
1159
- return;
1160
- }
1161
- console.log(chalk5.bold(`
1162
- Panopticon Skills (${skills.length})
1163
- `));
1164
- if (skills.length === 0) {
1165
- console.log(chalk5.yellow("No skills found."));
1166
- console.log(chalk5.dim("Skills should be in ~/.panopticon/skills/<name>/SKILL.md"));
1167
- return;
995
+ function checkAndSetupHooks() {
996
+ const settingsPath = join5(homedir(), ".claude", "settings.json");
997
+ const hookPath = join5(homedir(), ".panopticon", "bin", "heartbeat-hook");
998
+ if (existsSync5(settingsPath)) {
999
+ try {
1000
+ const settingsContent = readFileSync4(settingsPath, "utf-8");
1001
+ const settings = JSON.parse(settingsContent);
1002
+ const postToolUse = settings?.hooks?.PostToolUse || [];
1003
+ const hookConfigured = postToolUse.some(
1004
+ (hookConfig) => hookConfig.hooks?.some(
1005
+ (hook) => hook.command === hookPath || hook.command?.includes("panopticon") || hook.command?.includes("heartbeat-hook")
1006
+ )
1007
+ );
1008
+ if (hookConfigured) {
1009
+ return;
1010
+ }
1011
+ } catch {
1012
+ }
1168
1013
  }
1169
- for (const skill of skills) {
1170
- console.log(chalk5.cyan(skill.name));
1171
- console.log(chalk5.dim(` ${skill.description}`));
1014
+ try {
1015
+ console.log("Configuring Panopticon heartbeat hooks...");
1016
+ execSync2("pan setup hooks", { stdio: "pipe" });
1017
+ console.log("\u2713 Heartbeat hooks configured");
1018
+ } catch (error) {
1019
+ console.warn("\u26A0 Failed to auto-configure hooks. Run `pan setup hooks` manually.");
1172
1020
  }
1173
- console.log(`
1174
- ${chalk5.dim('Run "pan sync" to sync skills to Claude Code')}`);
1175
1021
  }
1176
-
1177
- // src/cli/commands/work/index.ts
1178
- init_esm_shims();
1022
+ function writeTaskCache(agentId, issueId) {
1023
+ const cacheDir = join5(getAgentDir(agentId));
1024
+ mkdirSync4(cacheDir, { recursive: true });
1025
+ const cacheFile = join5(cacheDir, "current-task.json");
1026
+ writeFileSync4(
1027
+ cacheFile,
1028
+ JSON.stringify({
1029
+ id: issueId,
1030
+ title: `Working on ${issueId}`,
1031
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1032
+ }, null, 2)
1033
+ );
1034
+ }
1179
1035
 
1180
1036
  // src/cli/commands/work/issue.ts
1181
- init_esm_shims();
1182
- init_agents();
1183
- import chalk6 from "chalk";
1184
- import ora4 from "ora";
1185
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
1186
- import { join as join6, dirname as dirname2 } from "path";
1187
1037
  function findWorkspace(issueId, labels = []) {
1188
1038
  const normalizedId = issueId.toLowerCase();
1189
1039
  const resolved = resolveProjectFromIssue(issueId, labels);
@@ -1431,8 +1281,6 @@ async function issueCommand(id, options) {
1431
1281
  }
1432
1282
 
1433
1283
  // src/cli/commands/work/status.ts
1434
- init_esm_shims();
1435
- init_agents();
1436
1284
  import chalk7 from "chalk";
1437
1285
  async function statusCommand(options) {
1438
1286
  const agents = listRunningAgents();
@@ -1462,8 +1310,6 @@ async function statusCommand(options) {
1462
1310
  }
1463
1311
 
1464
1312
  // src/cli/commands/work/tell.ts
1465
- init_esm_shims();
1466
- init_agents();
1467
1313
  import chalk8 from "chalk";
1468
1314
  async function tellCommand(id, message) {
1469
1315
  const agentId = id.startsWith("agent-") ? id : `agent-${id.toLowerCase()}`;
@@ -1478,9 +1324,6 @@ async function tellCommand(id, message) {
1478
1324
  }
1479
1325
 
1480
1326
  // src/cli/commands/work/kill.ts
1481
- init_esm_shims();
1482
- init_agents();
1483
- init_tmux();
1484
1327
  import chalk9 from "chalk";
1485
1328
  async function killCommand(id, options) {
1486
1329
  const agentId = id.startsWith("agent-") ? id : `agent-${id.toLowerCase()}`;
@@ -1502,9 +1345,6 @@ async function killCommand(id, options) {
1502
1345
  }
1503
1346
 
1504
1347
  // src/cli/commands/work/pending.ts
1505
- init_esm_shims();
1506
- init_agents();
1507
- init_paths();
1508
1348
  import chalk10 from "chalk";
1509
1349
  import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
1510
1350
  import { join as join7 } from "path";
@@ -1534,17 +1374,14 @@ async function pendingCommand() {
1534
1374
  }
1535
1375
 
1536
1376
  // src/cli/commands/work/approve.ts
1537
- init_esm_shims();
1538
- init_agents();
1539
- init_paths();
1540
1377
  import chalk11 from "chalk";
1541
1378
  import ora5 from "ora";
1542
- import { existsSync as existsSync8, writeFileSync as writeFileSync4, readFileSync as readFileSync7 } from "fs";
1379
+ import { existsSync as existsSync8, writeFileSync as writeFileSync5, readFileSync as readFileSync7 } from "fs";
1543
1380
  import { join as join8 } from "path";
1544
- import { homedir } from "os";
1381
+ import { homedir as homedir2 } from "os";
1545
1382
  import { execSync as execSync3 } from "child_process";
1546
1383
  function getLinearApiKey() {
1547
- const envFile = join8(homedir(), ".panopticon.env");
1384
+ const envFile = join8(homedir2(), ".panopticon.env");
1548
1385
  if (existsSync8(envFile)) {
1549
1386
  const content = readFileSync7(envFile, "utf-8");
1550
1387
  const match = content.match(/LINEAR_API_KEY=(.+)/);
@@ -1668,7 +1505,7 @@ async function approveCommand(id, options = {}) {
1668
1505
  state.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
1669
1506
  saveAgentState(state);
1670
1507
  const approvedFile = join8(AGENTS_DIR, agentId, "approved");
1671
- writeFileSync4(approvedFile, JSON.stringify({
1508
+ writeFileSync5(approvedFile, JSON.stringify({
1672
1509
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1673
1510
  prMerged,
1674
1511
  linearUpdated
@@ -1689,16 +1526,15 @@ async function approveCommand(id, options = {}) {
1689
1526
  }
1690
1527
 
1691
1528
  // src/cli/commands/work/plan.ts
1692
- init_esm_shims();
1693
1529
  import chalk12 from "chalk";
1694
1530
  import ora6 from "ora";
1695
1531
  import inquirer2 from "inquirer";
1696
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
1532
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
1697
1533
  import { join as join9, dirname as dirname3 } from "path";
1698
- import { homedir as homedir2 } from "os";
1534
+ import { homedir as homedir3 } from "os";
1699
1535
  import { execSync as execSync4 } from "child_process";
1700
1536
  function getLinearApiKey2() {
1701
- const envFile = join9(homedir2(), ".panopticon.env");
1537
+ const envFile = join9(homedir3(), ".panopticon.env");
1702
1538
  if (existsSync9(envFile)) {
1703
1539
  const content = readFileSync8(envFile, "utf-8");
1704
1540
  const match = content.match(/LINEAR_API_KEY=(.+)/);
@@ -2084,7 +1920,7 @@ function copyToPRDDirectory(issue, stateContent) {
2084
1920
  mkdirSync5(prdDir, { recursive: true });
2085
1921
  const filename = `${issue.identifier.toLowerCase()}-plan.md`;
2086
1922
  const prdPath = join9(prdDir, filename);
2087
- writeFileSync5(prdPath, stateContent);
1923
+ writeFileSync6(prdPath, stateContent);
2088
1924
  return prdPath;
2089
1925
  } catch {
2090
1926
  return null;
@@ -2201,9 +2037,9 @@ async function planCommand(id, options = {}) {
2201
2037
  const planningDir = join9(outputDir, ".planning");
2202
2038
  mkdirSync5(planningDir, { recursive: true });
2203
2039
  const statePath = join9(planningDir, "STATE.md");
2204
- writeFileSync5(statePath, stateContent);
2040
+ writeFileSync6(statePath, stateContent);
2205
2041
  const workspacePath = join9(planningDir, "WORKSPACE.md");
2206
- writeFileSync5(workspacePath, workspaceContent);
2042
+ writeFileSync6(workspacePath, workspaceContent);
2207
2043
  spinnerCreate.succeed("Context files created");
2208
2044
  const spinnerBeads = ora6("Creating Beads tasks...").start();
2209
2045
  const beadsResult = createBeadsTasks(issueData, tasks);
@@ -2267,7 +2103,6 @@ async function planCommand(id, options = {}) {
2267
2103
  }
2268
2104
 
2269
2105
  // src/cli/commands/work/list.ts
2270
- init_esm_shims();
2271
2106
  import chalk13 from "chalk";
2272
2107
  import ora7 from "ora";
2273
2108
  var PRIORITY_LABELS = {
@@ -2414,12 +2249,11 @@ ${tracker.toUpperCase()} (${issues.length} issues)
2414
2249
  }
2415
2250
 
2416
2251
  // src/cli/commands/work/triage.ts
2417
- init_esm_shims();
2418
2252
  import chalk14 from "chalk";
2419
2253
  import ora8 from "ora";
2420
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync10, mkdirSync as mkdirSync6 } from "fs";
2254
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync10, mkdirSync as mkdirSync6 } from "fs";
2421
2255
  import { join as join10 } from "path";
2422
- import { homedir as homedir3 } from "os";
2256
+ import { homedir as homedir4 } from "os";
2423
2257
  function getTrackerConfig2(trackerType) {
2424
2258
  const config = loadConfig();
2425
2259
  const trackerConfig = config.trackers[trackerType];
@@ -2437,7 +2271,7 @@ function getTrackerConfig2(trackerType) {
2437
2271
  };
2438
2272
  }
2439
2273
  function getTriageStatePath() {
2440
- return join10(homedir3(), ".panopticon", "triage-state.json");
2274
+ return join10(homedir4(), ".panopticon", "triage-state.json");
2441
2275
  }
2442
2276
  function loadTriageState() {
2443
2277
  const path = getTriageStatePath();
@@ -2451,12 +2285,12 @@ function loadTriageState() {
2451
2285
  return { dismissed: [], created: {} };
2452
2286
  }
2453
2287
  function saveTriageState(state) {
2454
- const dir = join10(homedir3(), ".panopticon");
2288
+ const dir = join10(homedir4(), ".panopticon");
2455
2289
  if (!existsSync10(dir)) {
2456
2290
  mkdirSync6(dir, { recursive: true });
2457
2291
  }
2458
2292
  const path = getTriageStatePath();
2459
- writeFileSync6(path, JSON.stringify(state, null, 2));
2293
+ writeFileSync7(path, JSON.stringify(state, null, 2));
2460
2294
  }
2461
2295
  async function triageCommand(id, options = {}) {
2462
2296
  const spinner = ora8("Loading triage queue...").start();
@@ -2592,8 +2426,6 @@ ${secondaryType.toUpperCase()} Issues Pending Triage (${pending.length})
2592
2426
  }
2593
2427
 
2594
2428
  // src/cli/commands/work/hook.ts
2595
- init_esm_shims();
2596
- init_hooks();
2597
2429
  import chalk15 from "chalk";
2598
2430
  async function hookCommand(action, idOrMessage, options = {}) {
2599
2431
  const agentId = process.env.PANOPTICON_AGENT_ID || "default";
@@ -2709,8 +2541,6 @@ async function hookCommand(action, idOrMessage, options = {}) {
2709
2541
  }
2710
2542
 
2711
2543
  // src/cli/commands/work/recover.ts
2712
- init_esm_shims();
2713
- init_agents();
2714
2544
  import chalk16 from "chalk";
2715
2545
  import ora9 from "ora";
2716
2546
  async function recoverCommand(id, options = {}) {
@@ -2787,8 +2617,6 @@ async function recoverCommand(id, options = {}) {
2787
2617
  }
2788
2618
 
2789
2619
  // src/cli/commands/work/cv.ts
2790
- init_esm_shims();
2791
- init_cv();
2792
2620
  import chalk17 from "chalk";
2793
2621
  async function cvCommand(agentId, options = {}) {
2794
2622
  if (options.rankings || !agentId) {
@@ -2831,13 +2659,10 @@ async function cvCommand(agentId, options = {}) {
2831
2659
  }
2832
2660
 
2833
2661
  // src/cli/commands/work/context.ts
2834
- init_esm_shims();
2835
2662
  import chalk18 from "chalk";
2836
2663
 
2837
2664
  // src/lib/context.ts
2838
- init_esm_shims();
2839
- init_paths();
2840
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7, appendFileSync, readdirSync as readdirSync8 } from "fs";
2665
+ import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync8, appendFileSync, readdirSync as readdirSync8 } from "fs";
2841
2666
  import { join as join11 } from "path";
2842
2667
  function getStateFile(agentId) {
2843
2668
  return join11(AGENTS_DIR, agentId, "STATE.md");
@@ -2856,7 +2681,7 @@ function writeAgentState(agentId, state) {
2856
2681
  const dir = join11(AGENTS_DIR, agentId);
2857
2682
  mkdirSync7(dir, { recursive: true });
2858
2683
  const content = generateStateMd(state);
2859
- writeFileSync7(getStateFile(agentId), content);
2684
+ writeFileSync8(getStateFile(agentId), content);
2860
2685
  }
2861
2686
  function updateCheckpoint(agentId, checkpoint, resumePoint) {
2862
2687
  const state = readAgentState(agentId);
@@ -2940,7 +2765,7 @@ function appendSummary(agentId, summary) {
2940
2765
  if (existsSync11(summaryFile)) {
2941
2766
  appendFileSync(summaryFile, "\n---\n\n" + content);
2942
2767
  } else {
2943
- writeFileSync7(summaryFile, "# Work Summaries\n\n" + content);
2768
+ writeFileSync8(summaryFile, "# Work Summaries\n\n" + content);
2944
2769
  }
2945
2770
  }
2946
2771
  function generateSummaryEntry(summary) {
@@ -3218,14 +3043,10 @@ History matches for "${pattern}":
3218
3043
  }
3219
3044
 
3220
3045
  // src/cli/commands/work/health.ts
3221
- init_esm_shims();
3222
3046
  import chalk19 from "chalk";
3223
3047
 
3224
3048
  // src/lib/health.ts
3225
- init_esm_shims();
3226
- init_paths();
3227
- init_agents();
3228
- import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
3049
+ import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
3229
3050
  import { join as join12 } from "path";
3230
3051
  import { execSync as execSync5 } from "child_process";
3231
3052
  var DEFAULT_PING_TIMEOUT_MS = 30 * 1e3;
@@ -3257,7 +3078,7 @@ function getAgentHealth(agentId) {
3257
3078
  function saveAgentHealth(health) {
3258
3079
  const dir = join12(AGENTS_DIR, health.agentId);
3259
3080
  mkdirSync8(dir, { recursive: true });
3260
- writeFileSync8(getHealthFile(health.agentId), JSON.stringify(health, null, 2));
3081
+ writeFileSync9(getHealthFile(health.agentId), JSON.stringify(health, null, 2));
3261
3082
  }
3262
3083
  function isAgentAlive(agentId) {
3263
3084
  try {
@@ -3469,7 +3290,6 @@ function formatHealthStatus(health) {
3469
3290
  }
3470
3291
 
3471
3292
  // src/cli/commands/work/health.ts
3472
- init_agents();
3473
3293
  async function healthCommand(action, arg, options = {}) {
3474
3294
  const config = {
3475
3295
  pingTimeoutMs: DEFAULT_PING_TIMEOUT_MS,
@@ -3640,14 +3460,12 @@ function registerWorkCommands(program2) {
3640
3460
  }
3641
3461
 
3642
3462
  // src/cli/commands/workspace.ts
3643
- init_esm_shims();
3644
3463
  import chalk20 from "chalk";
3645
3464
  import ora10 from "ora";
3646
- import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9 } from "fs";
3465
+ import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "fs";
3647
3466
  import { join as join15, basename as basename2 } from "path";
3648
3467
 
3649
3468
  // src/lib/worktree.ts
3650
- init_esm_shims();
3651
3469
  import { execSync as execSync6 } from "child_process";
3652
3470
  import { mkdirSync as mkdirSync9 } from "fs";
3653
3471
  import { dirname as dirname4 } from "path";
@@ -3698,8 +3516,6 @@ function removeWorktree(repoPath, worktreePath) {
3698
3516
  }
3699
3517
 
3700
3518
  // src/lib/template.ts
3701
- init_esm_shims();
3702
- init_paths();
3703
3519
  import { readFileSync as readFileSync13, existsSync as existsSync14, readdirSync as readdirSync9 } from "fs";
3704
3520
  import { join as join13 } from "path";
3705
3521
  function loadTemplate(templatePath) {
@@ -3757,8 +3573,6 @@ This workspace was created by Panopticon. Use \`bd\` commands to track your work
3757
3573
  }
3758
3574
 
3759
3575
  // src/lib/skills-merge.ts
3760
- init_esm_shims();
3761
- init_paths();
3762
3576
  import {
3763
3577
  existsSync as existsSync15,
3764
3578
  readdirSync as readdirSync10,
@@ -3914,7 +3728,7 @@ async function createCommand(issueId, options) {
3914
3728
  PROJECT_NAME: projectName
3915
3729
  };
3916
3730
  const claudeMd = generateClaudeMd(projectRoot, variables);
3917
- writeFileSync9(join15(workspacePath, "CLAUDE.md"), claudeMd);
3731
+ writeFileSync10(join15(workspacePath, "CLAUDE.md"), claudeMd);
3918
3732
  let skillsResult = { added: [], skipped: [] };
3919
3733
  if (options.skills !== false) {
3920
3734
  spinner.text = "Merging skills...";
@@ -3945,7 +3759,7 @@ async function createCommand(issueId, options) {
3945
3759
  }
3946
3760
  }
3947
3761
  async function listCommand2(options) {
3948
- const { listProjects: listProjects3 } = await import("../projects-6JVKIYIH.js");
3762
+ const { listProjects: listProjects3 } = await import("../projects-EHEXMVSP.js");
3949
3763
  const projects = listProjects3();
3950
3764
  if (projects.length > 0 && options.all) {
3951
3765
  const allWorkspaces = [];
@@ -4062,14 +3876,12 @@ async function destroyCommand(issueId, options) {
4062
3876
  }
4063
3877
 
4064
3878
  // src/cli/commands/install.ts
4065
- init_esm_shims();
4066
- init_paths();
4067
3879
  import chalk21 from "chalk";
4068
3880
  import ora11 from "ora";
4069
3881
  import { execSync as execSync8 } from "child_process";
4070
3882
  import { existsSync as existsSync17, mkdirSync as mkdirSync12, readFileSync as readFileSync14, copyFileSync, readdirSync as readdirSync11, statSync } from "fs";
4071
3883
  import { join as join16 } from "path";
4072
- import { homedir as homedir4, platform } from "os";
3884
+ import { homedir as homedir5, platform } from "os";
4073
3885
  function registerInstallCommand(program2) {
4074
3886
  program2.command("install").description("Install Panopticon prerequisites").option("--check", "Check prerequisites only").option("--minimal", "Skip Traefik and mkcert (use port-based routing)").option("--skip-mkcert", "Skip mkcert/HTTPS setup").option("--skip-docker", "Skip Docker network setup").action(installCommand);
4075
3887
  }
@@ -4165,7 +3977,7 @@ function checkPrerequisites() {
4165
3977
  message: hasBeads ? "installed" : "not found",
4166
3978
  fix: "cargo install beads-cli"
4167
3979
  });
4168
- const hasTtyd = checkCommand("ttyd") || existsSync17(join16(homedir4(), "bin", "ttyd"));
3980
+ const hasTtyd = checkCommand("ttyd") || existsSync17(join16(homedir5(), "bin", "ttyd"));
4169
3981
  results.push({
4170
3982
  name: "ttyd",
4171
3983
  passed: hasTtyd,
@@ -4245,11 +4057,11 @@ async function installCommand(options) {
4245
4057
  spinner.info("Skipping mkcert (not installed)");
4246
4058
  }
4247
4059
  }
4248
- const hasTtyd = checkCommand("ttyd") || existsSync17(join16(homedir4(), "bin", "ttyd"));
4060
+ const hasTtyd = checkCommand("ttyd") || existsSync17(join16(homedir5(), "bin", "ttyd"));
4249
4061
  if (!hasTtyd) {
4250
4062
  spinner.start("Installing ttyd (web terminal)...");
4251
4063
  try {
4252
- const binDir = join16(homedir4(), "bin");
4064
+ const binDir = join16(homedir5(), "bin");
4253
4065
  mkdirSync12(binDir, { recursive: true });
4254
4066
  const ttydPath = join16(binDir, "ttyd");
4255
4067
  const plat2 = detectPlatform();
@@ -4328,20 +4140,11 @@ async function installCommand(options) {
4328
4140
  console.log("");
4329
4141
  }
4330
4142
 
4331
- // src/cli/commands/cloister/index.ts
4332
- init_esm_shims();
4333
-
4334
4143
  // src/cli/commands/cloister/status.ts
4335
- init_esm_shims();
4336
4144
  import chalk22 from "chalk";
4337
4145
 
4338
- // src/lib/cloister/service.ts
4339
- init_esm_shims();
4340
-
4341
4146
  // src/lib/cloister/config.ts
4342
- init_esm_shims();
4343
- init_paths();
4344
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, existsSync as existsSync18, mkdirSync as mkdirSync13 } from "fs";
4147
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync18, mkdirSync as mkdirSync13 } from "fs";
4345
4148
  import { parse, stringify } from "@iarna/toml";
4346
4149
  import { join as join17 } from "path";
4347
4150
  var CLOISTER_CONFIG_FILE = join17(PANOPTICON_HOME, "cloister.toml");
@@ -4470,7 +4273,7 @@ function saveCloisterConfig(config) {
4470
4273
  }
4471
4274
  try {
4472
4275
  const content = stringify(config);
4473
- writeFileSync11(CLOISTER_CONFIG_FILE, content, "utf-8");
4276
+ writeFileSync12(CLOISTER_CONFIG_FILE, content, "utf-8");
4474
4277
  } catch (error) {
4475
4278
  console.error("Failed to save Cloister config:", error);
4476
4279
  throw error;
@@ -4486,7 +4289,6 @@ function getHealthThresholdsMs() {
4486
4289
  }
4487
4290
 
4488
4291
  // src/lib/cloister/health.ts
4489
- init_esm_shims();
4490
4292
  function evaluateHealthState(timeSinceActivityMs, thresholds) {
4491
4293
  if (timeSinceActivityMs < thresholds.stale) {
4492
4294
  return "active";
@@ -4580,8 +4382,6 @@ function getHealthLabel(state) {
4580
4382
  }
4581
4383
 
4582
4384
  // src/lib/cloister/database.ts
4583
- init_esm_shims();
4584
- init_paths();
4585
4385
  import Database from "better-sqlite3";
4586
4386
  import { join as join18 } from "path";
4587
4387
  import { existsSync as existsSync19, mkdirSync as mkdirSync14 } from "fs";
@@ -4655,21 +4455,16 @@ function cleanupOldEvents(database = getHealthDatabase(), retentionDays = RETENT
4655
4455
  }
4656
4456
 
4657
4457
  // src/lib/cloister/specialists.ts
4658
- init_esm_shims();
4659
- init_paths();
4660
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync12, existsSync as existsSync21, mkdirSync as mkdirSync15, readdirSync as readdirSync13, unlinkSync as unlinkSync2 } from "fs";
4458
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync13, existsSync as existsSync21, mkdirSync as mkdirSync15, readdirSync as readdirSync13, unlinkSync as unlinkSync2 } from "fs";
4661
4459
  import { join as join21, basename as basename4 } from "path";
4662
4460
  import { execSync as execSync9 } from "child_process";
4663
4461
 
4664
4462
  // src/lib/cost-parsers/jsonl-parser.ts
4665
- init_esm_shims();
4666
4463
  import { existsSync as existsSync20, readFileSync as readFileSync16, readdirSync as readdirSync12, statSync as statSync2 } from "fs";
4667
4464
  import { join as join20, basename as basename3 } from "path";
4668
- import { homedir as homedir5 } from "os";
4465
+ import { homedir as homedir6 } from "os";
4669
4466
 
4670
4467
  // src/lib/cost.ts
4671
- init_esm_shims();
4672
- init_paths();
4673
4468
  import { join as join19 } from "path";
4674
4469
  var DEFAULT_PRICING = [
4675
4470
  // Anthropic
@@ -4710,7 +4505,7 @@ function getPricing(provider, model) {
4710
4505
  var BUDGETS_FILE = join19(COSTS_DIR, "budgets.json");
4711
4506
 
4712
4507
  // src/lib/cost-parsers/jsonl-parser.ts
4713
- var CLAUDE_PROJECTS_DIR = join20(homedir5(), ".claude", "projects");
4508
+ var CLAUDE_PROJECTS_DIR = join20(homedir6(), ".claude", "projects");
4714
4509
  function getProjectDirs() {
4715
4510
  if (!existsSync20(CLAUDE_PROJECTS_DIR)) {
4716
4511
  return [];
@@ -4887,7 +4682,7 @@ function saveRegistry(registry) {
4887
4682
  registry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
4888
4683
  try {
4889
4684
  const content = JSON.stringify(registry, null, 2);
4890
- writeFileSync12(REGISTRY_FILE, content, "utf-8");
4685
+ writeFileSync13(REGISTRY_FILE, content, "utf-8");
4891
4686
  } catch (error) {
4892
4687
  console.error("Failed to save specialist registry:", error);
4893
4688
  throw error;
@@ -5023,20 +4818,11 @@ async function initializeEnabledSpecialists() {
5023
4818
  return results;
5024
4819
  }
5025
4820
 
5026
- // src/lib/runtimes/index.ts
5027
- init_esm_shims();
5028
-
5029
- // src/lib/runtimes/types.ts
5030
- init_esm_shims();
5031
-
5032
4821
  // src/lib/runtimes/claude-code.ts
5033
- init_esm_shims();
5034
- init_agents();
5035
- init_tmux();
5036
- import { existsSync as existsSync22, readFileSync as readFileSync18, statSync as statSync3, mkdirSync as mkdirSync16 } from "fs";
4822
+ import { existsSync as existsSync22, readFileSync as readFileSync18, statSync as statSync3, mkdirSync as mkdirSync16, writeFileSync as writeFileSync14 } from "fs";
5037
4823
  import { join as join22 } from "path";
5038
- import { homedir as homedir6 } from "os";
5039
- var CLAUDE_PROJECTS_DIR2 = join22(homedir6(), ".claude", "projects");
4824
+ import { homedir as homedir7 } from "os";
4825
+ var CLAUDE_PROJECTS_DIR2 = join22(homedir7(), ".claude", "projects");
5040
4826
  var ClaudeCodeRuntime = class {
5041
4827
  name = "claude-code";
5042
4828
  /**
@@ -5136,7 +4922,7 @@ var ClaudeCodeRuntime = class {
5136
4922
  * Read active heartbeat file if it exists
5137
4923
  */
5138
4924
  getActiveHeartbeat(agentId) {
5139
- const heartbeatPath = join22(homedir6(), ".panopticon", "heartbeats", `${agentId}.json`);
4925
+ const heartbeatPath = join22(homedir7(), ".panopticon", "heartbeats", `${agentId}.json`);
5140
4926
  if (!existsSync22(heartbeatPath)) {
5141
4927
  return null;
5142
4928
  }
@@ -5248,8 +5034,7 @@ var ClaudeCodeRuntime = class {
5248
5034
  const mailDir = join22(getAgentDir(agentId), "mail");
5249
5035
  mkdirSync16(mailDir, { recursive: true });
5250
5036
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5251
- const fs = __require("fs");
5252
- fs.writeFileSync(
5037
+ writeFileSync14(
5253
5038
  join22(mailDir, `${timestamp}.md`),
5254
5039
  `# Message
5255
5040
 
@@ -5268,8 +5053,7 @@ ${message}
5268
5053
  const state = getAgentState(agentId);
5269
5054
  if (state) {
5270
5055
  state.status = "stopped";
5271
- const { saveAgentState: saveAgentState2 } = (init_agents(), __toCommonJS(agents_exports));
5272
- saveAgentState2(state);
5056
+ saveAgentState(state);
5273
5057
  }
5274
5058
  }
5275
5059
  /**
@@ -5356,7 +5140,6 @@ function createClaudeCodeRuntime() {
5356
5140
  }
5357
5141
 
5358
5142
  // src/lib/runtimes/index.ts
5359
- init_agents();
5360
5143
  var RuntimeRegistry = class {
5361
5144
  runtimes = /* @__PURE__ */ new Map();
5362
5145
  /**
@@ -5417,11 +5200,7 @@ function getRuntimeForAgent(agentId) {
5417
5200
  return getGlobalRegistry().getRuntimeForAgent(agentId);
5418
5201
  }
5419
5202
 
5420
- // src/lib/cloister/service.ts
5421
- init_agents();
5422
-
5423
5203
  // src/lib/cloister/triggers.ts
5424
- init_esm_shims();
5425
5204
  import { existsSync as existsSync23 } from "fs";
5426
5205
  import { join as join23 } from "path";
5427
5206
  import { execSync as execSync10 } from "child_process";
@@ -5656,13 +5435,10 @@ function checkAllTriggers(agentId, workspace, issueId, currentModel, health, con
5656
5435
  }
5657
5436
 
5658
5437
  // src/lib/cloister/handoff.ts
5659
- init_esm_shims();
5660
- init_agents();
5661
- import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync17 } from "fs";
5438
+ import { writeFileSync as writeFileSync15, mkdirSync as mkdirSync17 } from "fs";
5662
5439
  import { join as join25 } from "path";
5663
5440
 
5664
5441
  // src/lib/cloister/handoff-context.ts
5665
- init_esm_shims();
5666
5442
  import { existsSync as existsSync24, readFileSync as readFileSync19 } from "fs";
5667
5443
  import { join as join24 } from "path";
5668
5444
  import { execSync as execSync11 } from "child_process";
@@ -5846,7 +5622,6 @@ function buildHandoffPrompt(context, additionalInstructions) {
5846
5622
  }
5847
5623
 
5848
5624
  // src/lib/cloister/handoff.ts
5849
- init_tmux();
5850
5625
  async function performHandoff(agentId, options) {
5851
5626
  const state = getAgentState(agentId);
5852
5627
  if (!state) {
@@ -5885,7 +5660,7 @@ async function performKillAndSpawn(state, options) {
5885
5660
  const handoffDir = join25(getAgentDir(state.id), "handoffs");
5886
5661
  mkdirSync17(handoffDir, { recursive: true });
5887
5662
  const handoffFile = join25(handoffDir, `handoff-${Date.now()}.md`);
5888
- writeFileSync13(handoffFile, prompt);
5663
+ writeFileSync15(handoffFile, prompt);
5889
5664
  const newState = spawnAgent({
5890
5665
  issueId: state.issueId,
5891
5666
  workspace: state.workspace,
@@ -5957,8 +5732,6 @@ function sleep(ms) {
5957
5732
  }
5958
5733
 
5959
5734
  // src/lib/cloister/handoff-logger.ts
5960
- init_esm_shims();
5961
- init_paths();
5962
5735
  import { existsSync as existsSync26, mkdirSync as mkdirSync18, appendFileSync as appendFileSync3, readFileSync as readFileSync20 } from "fs";
5963
5736
  import { join as join26 } from "path";
5964
5737
  var HANDOFF_LOG_FILE = join26(PANOPTICON_HOME, "logs", "handoffs.jsonl");
@@ -6436,7 +6209,6 @@ async function statusCommand2(options) {
6436
6209
  }
6437
6210
 
6438
6211
  // src/cli/commands/cloister/start.ts
6439
- init_esm_shims();
6440
6212
  import chalk23 from "chalk";
6441
6213
  async function startCommand() {
6442
6214
  const service = getCloisterService();
@@ -6450,7 +6222,6 @@ async function startCommand() {
6450
6222
  }
6451
6223
 
6452
6224
  // src/cli/commands/cloister/stop.ts
6453
- init_esm_shims();
6454
6225
  import chalk24 from "chalk";
6455
6226
  async function stopCommand(options) {
6456
6227
  const service = getCloisterService();
@@ -6483,16 +6254,12 @@ function registerCloisterCommands(program2) {
6483
6254
  cloister.command("emergency-stop").description("Emergency stop - kill ALL agents immediately").action(() => stopCommand({ emergency: true }));
6484
6255
  }
6485
6256
 
6486
- // src/cli/commands/setup/index.ts
6487
- init_esm_shims();
6488
-
6489
6257
  // src/cli/commands/setup/hooks.ts
6490
- init_esm_shims();
6491
6258
  import chalk25 from "chalk";
6492
- import { readFileSync as readFileSync21, writeFileSync as writeFileSync14, existsSync as existsSync27, mkdirSync as mkdirSync19, copyFileSync as copyFileSync2, chmodSync } from "fs";
6259
+ import { readFileSync as readFileSync21, writeFileSync as writeFileSync16, existsSync as existsSync27, mkdirSync as mkdirSync19, copyFileSync as copyFileSync2, chmodSync as chmodSync2 } from "fs";
6493
6260
  import { join as join27 } from "path";
6494
6261
  import { execSync as execSync12 } from "child_process";
6495
- import { homedir as homedir7 } from "os";
6262
+ import { homedir as homedir8 } from "os";
6496
6263
  function checkJqInstalled() {
6497
6264
  try {
6498
6265
  execSync12("which jq", { stdio: "pipe" });
@@ -6561,7 +6328,7 @@ async function setupHooksCommand() {
6561
6328
  } else {
6562
6329
  console.log(chalk25.green("\u2713 jq is installed"));
6563
6330
  }
6564
- const panopticonHome = join27(homedir7(), ".panopticon");
6331
+ const panopticonHome = join27(homedir8(), ".panopticon");
6565
6332
  const binDir = join27(panopticonHome, "bin");
6566
6333
  const heartbeatsDir = join27(panopticonHome, "heartbeats");
6567
6334
  if (!existsSync27(binDir)) {
@@ -6576,9 +6343,9 @@ async function setupHooksCommand() {
6576
6343
  const scriptDest = join27(binDir, "heartbeat-hook");
6577
6344
  let sourcePath = scriptSource;
6578
6345
  if (!existsSync27(sourcePath)) {
6579
- const { fileURLToPath: fileURLToPath2 } = await import("url");
6580
- const { dirname: dirname5 } = await import("path");
6581
- const __dirname3 = dirname5(fileURLToPath2(import.meta.url));
6346
+ const { fileURLToPath: fileURLToPath3 } = await import("url");
6347
+ const { dirname: dirname6 } = await import("path");
6348
+ const __dirname3 = dirname6(fileURLToPath3(import.meta.url));
6582
6349
  const installedSource = join27(__dirname3, "..", "..", "..", "scripts", "heartbeat-hook");
6583
6350
  if (existsSync27(installedSource)) {
6584
6351
  sourcePath = installedSource;
@@ -6590,9 +6357,9 @@ async function setupHooksCommand() {
6590
6357
  }
6591
6358
  }
6592
6359
  copyFileSync2(sourcePath, scriptDest);
6593
- chmodSync(scriptDest, 493);
6360
+ chmodSync2(scriptDest, 493);
6594
6361
  console.log(chalk25.green("\u2713 Installed heartbeat-hook script"));
6595
- const claudeDir = join27(homedir7(), ".claude");
6362
+ const claudeDir = join27(homedir8(), ".claude");
6596
6363
  const settingsPath = join27(claudeDir, "settings.json");
6597
6364
  let settings = {};
6598
6365
  if (existsSync27(settingsPath)) {
@@ -6630,7 +6397,7 @@ async function setupHooksCommand() {
6630
6397
  }
6631
6398
  ]
6632
6399
  });
6633
- writeFileSync14(settingsPath, JSON.stringify(settings, null, 2));
6400
+ writeFileSync16(settingsPath, JSON.stringify(settings, null, 2));
6634
6401
  console.log(chalk25.green("\u2713 Updated Claude Code settings.json"));
6635
6402
  console.log(chalk25.green.bold("\n\u2713 Setup complete!\n"));
6636
6403
  console.log(chalk25.dim("Heartbeat hooks are now active. When you run agents via"));
@@ -6645,7 +6412,6 @@ function registerSetupCommands(program2) {
6645
6412
  }
6646
6413
 
6647
6414
  // src/cli/commands/project.ts
6648
- init_esm_shims();
6649
6415
  import chalk26 from "chalk";
6650
6416
  import { existsSync as existsSync28, readFileSync as readFileSync22 } from "fs";
6651
6417
  import { join as join28, resolve } from "path";
@@ -6799,12 +6565,10 @@ Project: ${foundKey}
6799
6565
  }
6800
6566
 
6801
6567
  // src/cli/commands/doctor.ts
6802
- init_esm_shims();
6803
- init_paths();
6804
6568
  import chalk27 from "chalk";
6805
- import { existsSync as existsSync29, readdirSync as readdirSync15 } from "fs";
6569
+ import { existsSync as existsSync29, readdirSync as readdirSync15, readFileSync as readFileSync23 } from "fs";
6806
6570
  import { execSync as execSync13 } from "child_process";
6807
- import { homedir as homedir8 } from "os";
6571
+ import { homedir as homedir9 } from "os";
6808
6572
  import { join as join29 } from "path";
6809
6573
  function checkCommand2(cmd) {
6810
6574
  try {
@@ -6891,7 +6655,7 @@ async function doctorCommand() {
6891
6655
  fix: "Install Claude Code first"
6892
6656
  });
6893
6657
  }
6894
- const envFile = join29(homedir8(), ".panopticon.env");
6658
+ const envFile = join29(homedir9(), ".panopticon.env");
6895
6659
  if (existsSync29(envFile)) {
6896
6660
  checks.push({ name: "Config File", status: "ok", message: "~/.panopticon.env exists" });
6897
6661
  } else {
@@ -6905,7 +6669,7 @@ async function doctorCommand() {
6905
6669
  if (process.env.LINEAR_API_KEY) {
6906
6670
  checks.push({ name: "LINEAR_API_KEY", status: "ok", message: "Set in environment" });
6907
6671
  } else if (existsSync29(envFile)) {
6908
- const content = __require("fs").readFileSync(envFile, "utf-8");
6672
+ const content = readFileSync23(envFile, "utf-8");
6909
6673
  if (content.includes("LINEAR_API_KEY")) {
6910
6674
  checks.push({ name: "LINEAR_API_KEY", status: "ok", message: "Set in config file" });
6911
6675
  } else {
@@ -6969,12 +6733,17 @@ async function doctorCommand() {
6969
6733
  }
6970
6734
 
6971
6735
  // src/cli/commands/update.ts
6972
- init_esm_shims();
6973
6736
  import { execSync as execSync14 } from "child_process";
6974
6737
  import chalk28 from "chalk";
6738
+ import { readFileSync as readFileSync24 } from "fs";
6739
+ import { fileURLToPath as fileURLToPath2 } from "url";
6740
+ import { dirname as dirname5, join as join30 } from "path";
6975
6741
  function getCurrentVersion() {
6976
6742
  try {
6977
- const pkg = require_package();
6743
+ const __filename3 = fileURLToPath2(import.meta.url);
6744
+ const __dirname3 = dirname5(__filename3);
6745
+ const pkgPath = join30(__dirname3, "..", "..", "..", "package.json");
6746
+ const pkg = JSON.parse(readFileSync24(pkgPath, "utf-8"));
6978
6747
  return pkg.version;
6979
6748
  } catch {
6980
6749
  return "unknown";
@@ -7073,18 +6842,18 @@ registerInstallCommand(program);
7073
6842
  program.command("status").description("Show running agents (shorthand for work status)").option("--json", "Output as JSON").action(statusCommand);
7074
6843
  program.command("up").description("Start dashboard (and Traefik if enabled)").option("--detach", "Run in background").option("--skip-traefik", "Skip Traefik startup").action(async (options) => {
7075
6844
  const { spawn, execSync: execSync15 } = await import("child_process");
7076
- const { join: join30, dirname: dirname5 } = await import("path");
7077
- const { fileURLToPath: fileURLToPath2 } = await import("url");
7078
- const { readFileSync: readFileSync23, existsSync: existsSync30 } = await import("fs");
6845
+ const { join: join31, dirname: dirname6 } = await import("path");
6846
+ const { fileURLToPath: fileURLToPath3 } = await import("url");
6847
+ const { readFileSync: readFileSync25, existsSync: existsSync30 } = await import("fs");
7079
6848
  const { parse: parse2 } = await import("@iarna/toml");
7080
- const __dirname3 = dirname5(fileURLToPath2(import.meta.url));
7081
- const dashboardDir = join30(__dirname3, "..", "dashboard");
7082
- const configFile = join30(process.env.HOME || "", ".panopticon", "config.toml");
6849
+ const __dirname3 = dirname6(fileURLToPath3(import.meta.url));
6850
+ const dashboardDir = join31(__dirname3, "..", "dashboard");
6851
+ const configFile = join31(process.env.HOME || "", ".panopticon", "config.toml");
7083
6852
  let traefikEnabled = false;
7084
6853
  let traefikDomain = "pan.localhost";
7085
6854
  if (existsSync30(configFile)) {
7086
6855
  try {
7087
- const configContent = readFileSync23(configFile, "utf-8");
6856
+ const configContent = readFileSync25(configFile, "utf-8");
7088
6857
  const config = parse2(configContent);
7089
6858
  traefikEnabled = config.traefik?.enabled === true;
7090
6859
  traefikDomain = config.traefik?.domain || "pan.localhost";
@@ -7094,7 +6863,7 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
7094
6863
  }
7095
6864
  console.log(chalk29.bold("Starting Panopticon...\n"));
7096
6865
  if (traefikEnabled && !options.skipTraefik) {
7097
- const traefikDir = join30(process.env.HOME || "", ".panopticon", "traefik");
6866
+ const traefikDir = join31(process.env.HOME || "", ".panopticon", "traefik");
7098
6867
  if (existsSync30(traefikDir)) {
7099
6868
  try {
7100
6869
  console.log(chalk29.dim("Starting Traefik..."));
@@ -7148,8 +6917,8 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
7148
6917
  });
7149
6918
  program.command("down").description("Stop dashboard (and Traefik if enabled)").option("--skip-traefik", "Skip Traefik shutdown").action(async (options) => {
7150
6919
  const { execSync: execSync15 } = await import("child_process");
7151
- const { join: join30 } = await import("path");
7152
- const { readFileSync: readFileSync23, existsSync: existsSync30 } = await import("fs");
6920
+ const { join: join31 } = await import("path");
6921
+ const { readFileSync: readFileSync25, existsSync: existsSync30 } = await import("fs");
7153
6922
  const { parse: parse2 } = await import("@iarna/toml");
7154
6923
  console.log(chalk29.bold("Stopping Panopticon...\n"));
7155
6924
  console.log(chalk29.dim("Stopping dashboard..."));
@@ -7160,18 +6929,18 @@ program.command("down").description("Stop dashboard (and Traefik if enabled)").o
7160
6929
  } catch {
7161
6930
  console.log(chalk29.dim(" No dashboard processes found"));
7162
6931
  }
7163
- const configFile = join30(process.env.HOME || "", ".panopticon", "config.toml");
6932
+ const configFile = join31(process.env.HOME || "", ".panopticon", "config.toml");
7164
6933
  let traefikEnabled = false;
7165
6934
  if (existsSync30(configFile)) {
7166
6935
  try {
7167
- const configContent = readFileSync23(configFile, "utf-8");
6936
+ const configContent = readFileSync25(configFile, "utf-8");
7168
6937
  const config = parse2(configContent);
7169
6938
  traefikEnabled = config.traefik?.enabled === true;
7170
6939
  } catch (error) {
7171
6940
  }
7172
6941
  }
7173
6942
  if (traefikEnabled && !options.skipTraefik) {
7174
- const traefikDir = join30(process.env.HOME || "", ".panopticon", "traefik");
6943
+ const traefikDir = join31(process.env.HOME || "", ".panopticon", "traefik");
7175
6944
  if (existsSync30(traefikDir)) {
7176
6945
  console.log(chalk29.dim("Stopping Traefik..."));
7177
6946
  try {