panopticon-cli 0.1.3 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  AGENTS_DIR,
4
+ CERTS_DIR,
4
5
  CLAUDE_DIR,
5
6
  CLAUDE_MD_TEMPLATES,
6
7
  COMMANDS_DIR,
@@ -8,10 +9,16 @@ import {
8
9
  INIT_DIRS,
9
10
  PANOPTICON_HOME,
10
11
  SKILLS_DIR,
12
+ SOURCE_TRAEFIK_TEMPLATES,
11
13
  SYNC_TARGETS,
14
+ TRAEFIK_CERTS_DIR,
15
+ TRAEFIK_DIR,
16
+ __commonJS,
12
17
  __require,
13
18
  addAlias,
19
+ cleanOldBackups,
14
20
  createBackup,
21
+ createTracker,
15
22
  detectShell,
16
23
  executeSync,
17
24
  getAliasInstructions,
@@ -22,22 +29,122 @@ import {
22
29
  planSync,
23
30
  restoreBackup,
24
31
  saveConfig
25
- } from "../chunk-FR2P66GU.js";
32
+ } from "../chunk-RM3WGTFO.js";
33
+
34
+ // package.json
35
+ var require_package = __commonJS({
36
+ "package.json"(exports, module) {
37
+ module.exports = {
38
+ name: "panopticon-cli",
39
+ version: "0.1.6",
40
+ description: "Multi-agent orchestration for AI coding assistants (Claude Code, Codex, Cursor, Gemini CLI)",
41
+ keywords: [
42
+ "ai-agents",
43
+ "orchestration",
44
+ "claude-code",
45
+ "codex",
46
+ "cursor",
47
+ "gemini",
48
+ "multi-agent",
49
+ "devtools",
50
+ "linear"
51
+ ],
52
+ author: "Edward Becker <edward.becker@mindyournow.com>",
53
+ license: "MIT",
54
+ repository: {
55
+ type: "git",
56
+ url: "https://github.com/eltmon/panopticon-cli.git"
57
+ },
58
+ homepage: "https://github.com/eltmon/panopticon-cli#readme",
59
+ bugs: {
60
+ url: "https://github.com/eltmon/panopticon-cli/issues"
61
+ },
62
+ type: "module",
63
+ bin: {
64
+ pan: "./dist/cli/index.js",
65
+ panopticon: "./dist/cli/index.js"
66
+ },
67
+ main: "./dist/index.js",
68
+ types: "./dist/index.d.ts",
69
+ files: [
70
+ "dist",
71
+ "templates",
72
+ "README.md",
73
+ "LICENSE"
74
+ ],
75
+ engines: {
76
+ node: ">=18.0.0"
77
+ },
78
+ scripts: {
79
+ dev: "tsx watch src/cli/index.ts",
80
+ build: "tsup",
81
+ typecheck: "tsc --noEmit",
82
+ lint: "eslint src/",
83
+ test: "vitest",
84
+ prepublishOnly: "npm run build"
85
+ },
86
+ dependencies: {
87
+ "@iarna/toml": "^2.2.5",
88
+ "@linear/sdk": "^70.0.0",
89
+ "@octokit/rest": "^22.0.1",
90
+ chalk: "^5.6.2",
91
+ commander: "^12.1.0",
92
+ conf: "^12.0.0",
93
+ execa: "^8.0.1",
94
+ inquirer: "^9.3.8",
95
+ ora: "^8.2.0"
96
+ },
97
+ devDependencies: {
98
+ "@types/inquirer": "^9.0.9",
99
+ "@types/node": "^20.10.0",
100
+ eslint: "^8.55.0",
101
+ tsup: "^8.0.1",
102
+ tsx: "^4.6.2",
103
+ typescript: "^5.3.2",
104
+ vitest: "^1.0.4"
105
+ }
106
+ };
107
+ }
108
+ });
26
109
 
27
110
  // src/cli/index.ts
28
111
  import { Command } from "commander";
29
- import chalk23 from "chalk";
112
+ import chalk25 from "chalk";
30
113
 
31
114
  // src/cli/commands/init.ts
32
- import { existsSync, mkdirSync } from "fs";
115
+ import { existsSync, mkdirSync, readdirSync, cpSync } from "fs";
116
+ import { join, dirname } from "path";
117
+ import { fileURLToPath } from "url";
33
118
  import chalk from "chalk";
34
119
  import ora from "ora";
120
+ var __filename = fileURLToPath(import.meta.url);
121
+ var __dirname = dirname(__filename);
122
+ var PACKAGE_ROOT = join(__dirname, "..", "..");
123
+ var BUNDLED_SKILLS_DIR = join(PACKAGE_ROOT, "skills");
124
+ function copyBundledSkills() {
125
+ if (!existsSync(BUNDLED_SKILLS_DIR)) {
126
+ return 0;
127
+ }
128
+ if (!existsSync(SKILLS_DIR)) {
129
+ mkdirSync(SKILLS_DIR, { recursive: true });
130
+ }
131
+ const skills = readdirSync(BUNDLED_SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
132
+ let copied = 0;
133
+ for (const skill of skills) {
134
+ const sourcePath = join(BUNDLED_SKILLS_DIR, skill.name);
135
+ const targetPath = join(SKILLS_DIR, skill.name);
136
+ cpSync(sourcePath, targetPath, { recursive: true });
137
+ copied++;
138
+ }
139
+ return copied;
140
+ }
35
141
  async function initCommand() {
36
142
  const spinner = ora("Initializing Panopticon...").start();
37
143
  if (existsSync(CONFIG_FILE)) {
38
144
  spinner.info("Panopticon already initialized");
39
145
  console.log(chalk.dim(` Config: ${CONFIG_FILE}`));
40
146
  console.log(chalk.dim(` Home: ${PANOPTICON_HOME}`));
147
+ console.log(chalk.dim(" Run `pan sync` to update skills"));
41
148
  return;
42
149
  }
43
150
  try {
@@ -50,6 +157,8 @@ async function initCommand() {
50
157
  const config = getDefaultConfig();
51
158
  saveConfig(config);
52
159
  spinner.text = "Created config...";
160
+ spinner.text = "Installing bundled skills...";
161
+ const skillsCopied = copyBundledSkills();
53
162
  const shell = detectShell();
54
163
  const rcFile = getShellRcFile(shell);
55
164
  if (rcFile && existsSync(rcFile)) {
@@ -58,19 +167,25 @@ async function initCommand() {
58
167
  console.log("");
59
168
  console.log(chalk.green("\u2713") + " Created " + chalk.cyan(PANOPTICON_HOME));
60
169
  console.log(chalk.green("\u2713") + " Created " + chalk.cyan(CONFIG_FILE));
170
+ if (skillsCopied > 0) {
171
+ console.log(chalk.green("\u2713") + ` Installed ${skillsCopied} bundled skills`);
172
+ }
61
173
  console.log(chalk.green("\u2713") + " " + getAliasInstructions(shell));
62
174
  } else {
63
175
  spinner.succeed("Panopticon initialized!");
64
176
  console.log("");
65
177
  console.log(chalk.green("\u2713") + " Created " + chalk.cyan(PANOPTICON_HOME));
66
178
  console.log(chalk.green("\u2713") + " Created " + chalk.cyan(CONFIG_FILE));
179
+ if (skillsCopied > 0) {
180
+ console.log(chalk.green("\u2713") + ` Installed ${skillsCopied} bundled skills`);
181
+ }
67
182
  console.log(chalk.yellow("!") + " Could not detect shell. Add alias manually:");
68
183
  console.log(chalk.dim(' alias pan="panopticon"'));
69
184
  }
70
185
  console.log("");
71
186
  console.log("Next steps:");
72
- console.log(chalk.dim(" 1. Add skills to ~/.panopticon/skills/"));
73
- console.log(chalk.dim(" 2. Run: pan sync"));
187
+ console.log(chalk.dim(" 1. Run: pan sync"));
188
+ console.log(chalk.dim(" 2. Start dashboard: pan up"));
74
189
  } catch (error) {
75
190
  spinner.fail("Failed to initialize");
76
191
  console.error(chalk.red(error.message));
@@ -119,9 +234,9 @@ async function syncCommand(options) {
119
234
  SYNC_TARGETS[r].skills,
120
235
  SYNC_TARGETS[r].commands
121
236
  ]);
122
- const backup = createBackup(backupDirs);
123
- if (backup.targets.length > 0) {
124
- spinner2.succeed(`Backup created: ${backup.timestamp}`);
237
+ const backup2 = createBackup(backupDirs);
238
+ if (backup2.targets.length > 0) {
239
+ spinner2.succeed(`Backup created: ${backup2.timestamp}`);
125
240
  } else {
126
241
  spinner2.info("No existing content to backup");
127
242
  }
@@ -166,8 +281,8 @@ async function restoreCommand(timestamp) {
166
281
  }
167
282
  if (!timestamp) {
168
283
  console.log(chalk3.bold("Available backups:\n"));
169
- for (const backup of backups.slice(0, 10)) {
170
- console.log(` ${chalk3.cyan(backup.timestamp)} - ${backup.targets.join(", ")}`);
284
+ for (const backup2 of backups.slice(0, 10)) {
285
+ console.log(` ${chalk3.cyan(backup2.timestamp)} - ${backup2.targets.join(", ")}`);
171
286
  }
172
287
  if (backups.length > 10) {
173
288
  console.log(chalk3.dim(` ... and ${backups.length - 10} more`));
@@ -218,10 +333,42 @@ async function restoreCommand(timestamp) {
218
333
  }
219
334
  }
220
335
 
221
- // src/cli/commands/skills.ts
222
- import { readdirSync, readFileSync, existsSync as existsSync2 } from "fs";
223
- import { join } from "path";
336
+ // src/cli/commands/backup.ts
224
337
  import chalk4 from "chalk";
338
+ async function backupListCommand(options) {
339
+ const backups = listBackups();
340
+ if (options.json) {
341
+ console.log(JSON.stringify(backups, null, 2));
342
+ return;
343
+ }
344
+ if (backups.length === 0) {
345
+ console.log(chalk4.dim("No backups found."));
346
+ console.log(chalk4.dim("Backups are created automatically during sync."));
347
+ return;
348
+ }
349
+ console.log(chalk4.bold("Backups:\n"));
350
+ for (const backup2 of backups) {
351
+ console.log(` ${chalk4.cyan(backup2.timestamp)}`);
352
+ console.log(` ${chalk4.dim("Contains:")} ${backup2.targets.join(", ")}`);
353
+ }
354
+ console.log();
355
+ console.log(chalk4.dim(`Total: ${backups.length} backups`));
356
+ console.log(chalk4.dim("Use `pan restore <timestamp>` to restore a backup."));
357
+ }
358
+ async function backupCleanCommand(options) {
359
+ const keepCount = parseInt(options.keep || "10", 10);
360
+ const removed = cleanOldBackups(keepCount);
361
+ if (removed === 0) {
362
+ console.log(chalk4.dim(`No backups removed (keeping ${keepCount}).`));
363
+ } else {
364
+ console.log(chalk4.green(`Removed ${removed} old backup(s), keeping ${keepCount}.`));
365
+ }
366
+ }
367
+
368
+ // src/cli/commands/skills.ts
369
+ import { readdirSync as readdirSync2, readFileSync, existsSync as existsSync2 } from "fs";
370
+ import { join as join2 } from "path";
371
+ import chalk5 from "chalk";
225
372
  function parseSkillFrontmatter(content) {
226
373
  const match = content.match(/^---\n([\s\S]*?)\n---/);
227
374
  if (!match) return {};
@@ -233,9 +380,9 @@ function parseSkillFrontmatter(content) {
233
380
  function listSkills() {
234
381
  if (!existsSync2(SKILLS_DIR)) return [];
235
382
  const skills = [];
236
- const dirs = readdirSync(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
383
+ const dirs = readdirSync2(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
237
384
  for (const dir of dirs) {
238
- const skillFile = join(SKILLS_DIR, dir.name, "SKILL.md");
385
+ const skillFile = join2(SKILLS_DIR, dir.name, "SKILL.md");
239
386
  if (!existsSync2(skillFile)) continue;
240
387
  const content = readFileSync(skillFile, "utf8");
241
388
  const { name, description } = parseSkillFrontmatter(content);
@@ -253,29 +400,32 @@ async function skillsCommand(options) {
253
400
  console.log(JSON.stringify(skills, null, 2));
254
401
  return;
255
402
  }
256
- console.log(chalk4.bold(`
403
+ console.log(chalk5.bold(`
257
404
  Panopticon Skills (${skills.length})
258
405
  `));
259
406
  if (skills.length === 0) {
260
- console.log(chalk4.yellow("No skills found."));
261
- console.log(chalk4.dim("Skills should be in ~/.panopticon/skills/<name>/SKILL.md"));
407
+ console.log(chalk5.yellow("No skills found."));
408
+ console.log(chalk5.dim("Skills should be in ~/.panopticon/skills/<name>/SKILL.md"));
262
409
  return;
263
410
  }
264
411
  for (const skill of skills) {
265
- console.log(chalk4.cyan(skill.name));
266
- console.log(chalk4.dim(` ${skill.description}`));
412
+ console.log(chalk5.cyan(skill.name));
413
+ console.log(chalk5.dim(` ${skill.description}`));
267
414
  }
268
415
  console.log(`
269
- ${chalk4.dim('Run "pan sync" to sync skills to Claude Code')}`);
416
+ ${chalk5.dim('Run "pan sync" to sync skills to Claude Code')}`);
270
417
  }
271
418
 
272
419
  // src/cli/commands/work/issue.ts
273
- import chalk5 from "chalk";
420
+ import chalk6 from "chalk";
274
421
  import ora4 from "ora";
422
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
423
+ import { join as join6, dirname as dirname2 } from "path";
275
424
 
276
425
  // src/lib/agents.ts
277
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, readFileSync as readFileSync4, readdirSync as readdirSync4 } from "fs";
278
- import { join as join4 } from "path";
426
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, readFileSync as readFileSync4, readdirSync as readdirSync5 } from "fs";
427
+ import { join as join5 } from "path";
428
+ import { execSync as execSync2 } from "child_process";
279
429
 
280
430
  // src/lib/tmux.ts
281
431
  import { execSync } from "child_process";
@@ -307,8 +457,20 @@ function sessionExists(name) {
307
457
  }
308
458
  function createSession(name, cwd, initialCommand) {
309
459
  const escapedCwd = cwd.replace(/"/g, '\\"');
310
- const cmd = initialCommand ? `tmux new-session -d -s ${name} -c "${escapedCwd}" "${initialCommand.replace(/"/g, '\\"')}"` : `tmux new-session -d -s ${name} -c "${escapedCwd}"`;
311
- execSync(cmd);
460
+ if (initialCommand && (initialCommand.includes("`") || initialCommand.includes("\n") || initialCommand.length > 500)) {
461
+ execSync(`tmux new-session -d -s ${name} -c "${escapedCwd}"`);
462
+ execSync("sleep 0.5");
463
+ const tmpFile = `/tmp/pan-cmd-${name}.sh`;
464
+ const fs = __require("fs");
465
+ fs.writeFileSync(tmpFile, initialCommand);
466
+ fs.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}" "${initialCommand.replace(/"/g, '\\"')}"`;
470
+ execSync(cmd);
471
+ } else {
472
+ execSync(`tmux new-session -d -s ${name} -c "${escapedCwd}"`);
473
+ }
312
474
  }
313
475
  function killSession(name) {
314
476
  execSync(`tmux kill-session -t ${name}`);
@@ -323,16 +485,16 @@ function getAgentSessions() {
323
485
  }
324
486
 
325
487
  // src/lib/hooks.ts
326
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync, readdirSync as readdirSync2, unlinkSync } from "fs";
327
- import { join as join2 } from "path";
488
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync, readdirSync as readdirSync3, unlinkSync } from "fs";
489
+ import { join as join3 } from "path";
328
490
  function getHookDir(agentId) {
329
- return join2(AGENTS_DIR, agentId);
491
+ return join3(AGENTS_DIR, agentId);
330
492
  }
331
493
  function getHookFile(agentId) {
332
- return join2(getHookDir(agentId), "hook.json");
494
+ return join3(getHookDir(agentId), "hook.json");
333
495
  }
334
496
  function getMailDir(agentId) {
335
- return join2(getHookDir(agentId), "mail");
497
+ return join3(getHookDir(agentId), "mail");
336
498
  }
337
499
  function initHook(agentId) {
338
500
  const hookDir = getHookDir(agentId);
@@ -377,11 +539,11 @@ function checkHook(agentId) {
377
539
  if (!hook || hook.items.length === 0) {
378
540
  const mailDir = getMailDir(agentId);
379
541
  if (existsSync3(mailDir)) {
380
- const mails = readdirSync2(mailDir).filter((f) => f.endsWith(".json"));
542
+ const mails = readdirSync3(mailDir).filter((f) => f.endsWith(".json"));
381
543
  if (mails.length > 0) {
382
544
  const mailItems = mails.map((file) => {
383
545
  try {
384
- const content = readFileSync2(join2(mailDir, file), "utf-8");
546
+ const content = readFileSync2(join3(mailDir, file), "utf-8");
385
547
  return JSON.parse(content);
386
548
  } catch {
387
549
  return null;
@@ -440,7 +602,7 @@ function sendMail(toAgentId, from, message, priority = "normal") {
440
602
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
441
603
  };
442
604
  writeFileSync(
443
- join2(mailDir, `${mailItem.id}.json`),
605
+ join3(mailDir, `${mailItem.id}.json`),
444
606
  JSON.stringify(mailItem, null, 2)
445
607
  );
446
608
  }
@@ -487,10 +649,10 @@ function generateGUPPPrompt(agentId) {
487
649
  }
488
650
 
489
651
  // src/lib/cv.ts
490
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync as readdirSync3 } from "fs";
491
- import { join as join3 } from "path";
652
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync as readdirSync4 } from "fs";
653
+ import { join as join4 } from "path";
492
654
  function getCVFile(agentId) {
493
- return join3(AGENTS_DIR, agentId, "cv.json");
655
+ return join4(AGENTS_DIR, agentId, "cv.json");
494
656
  }
495
657
  function getAgentCV(agentId) {
496
658
  const cvFile = getCVFile(agentId);
@@ -521,7 +683,7 @@ function getAgentCV(agentId) {
521
683
  return cv;
522
684
  }
523
685
  function saveAgentCV(cv) {
524
- const dir = join3(AGENTS_DIR, cv.agentId);
686
+ const dir = join4(AGENTS_DIR, cv.agentId);
525
687
  mkdirSync3(dir, { recursive: true });
526
688
  writeFileSync2(getCVFile(cv.agentId), JSON.stringify(cv, null, 2));
527
689
  }
@@ -551,7 +713,7 @@ function startWork(agentId, issueId, skills) {
551
713
  function getAgentRankings() {
552
714
  const rankings = [];
553
715
  if (!existsSync4(AGENTS_DIR)) return rankings;
554
- const dirs = readdirSync3(AGENTS_DIR, { withFileTypes: true }).filter(
716
+ const dirs = readdirSync4(AGENTS_DIR, { withFileTypes: true }).filter(
555
717
  (d) => d.isDirectory()
556
718
  );
557
719
  for (const dir of dirs) {
@@ -620,10 +782,10 @@ function formatCV(cv) {
620
782
 
621
783
  // src/lib/agents.ts
622
784
  function getAgentDir(agentId) {
623
- return join4(AGENTS_DIR, agentId);
785
+ return join5(AGENTS_DIR, agentId);
624
786
  }
625
787
  function getAgentState(agentId) {
626
- const stateFile = join4(getAgentDir(agentId), "state.json");
788
+ const stateFile = join5(getAgentDir(agentId), "state.json");
627
789
  if (!existsSync5(stateFile)) return null;
628
790
  const content = readFileSync4(stateFile, "utf8");
629
791
  return JSON.parse(content);
@@ -632,7 +794,7 @@ function saveAgentState(state) {
632
794
  const dir = getAgentDir(state.id);
633
795
  mkdirSync4(dir, { recursive: true });
634
796
  writeFileSync3(
635
- join4(dir, "state.json"),
797
+ join5(dir, "state.json"),
636
798
  JSON.stringify(state, null, 2)
637
799
  );
638
800
  }
@@ -660,8 +822,34 @@ function spawnAgent(options) {
660
822
  prompt = guppPrompt + "\n\n---\n\n" + prompt;
661
823
  }
662
824
  }
663
- const claudeCmd = prompt ? `claude --model ${state.model} "${prompt.replace(/"/g, '\\"').replace(/\n/g, "\\n")}"` : `claude --model ${state.model}`;
825
+ const promptFile = join5(getAgentDir(agentId), "initial-prompt.md");
826
+ if (prompt) {
827
+ writeFileSync3(promptFile, prompt);
828
+ }
829
+ const claudeCmd = `claude --dangerously-skip-permissions --model ${state.model}`;
664
830
  createSession(agentId, options.workspace, claudeCmd);
831
+ if (prompt) {
832
+ let ready = false;
833
+ for (let i = 0; i < 15; i++) {
834
+ execSync2("sleep 1");
835
+ try {
836
+ const pane = execSync2(`tmux capture-pane -t ${agentId} -p`, { encoding: "utf-8" });
837
+ if (pane.includes("\u276F") || pane.includes(">")) {
838
+ ready = true;
839
+ break;
840
+ }
841
+ } catch {
842
+ }
843
+ }
844
+ if (ready) {
845
+ execSync2(`tmux load-buffer "${promptFile}"`);
846
+ execSync2(`tmux paste-buffer -t ${agentId}`);
847
+ execSync2("sleep 0.5");
848
+ execSync2(`tmux send-keys -t ${agentId} Enter`);
849
+ } else {
850
+ console.error("Claude did not become ready in time, prompt not sent");
851
+ }
852
+ }
665
853
  state.status = "running";
666
854
  saveAgentState(state);
667
855
  startWork(agentId, options.issueId);
@@ -672,7 +860,7 @@ function listRunningAgents() {
672
860
  const tmuxNames = new Set(tmuxSessions.map((s) => s.name));
673
861
  const agents = [];
674
862
  if (!existsSync5(AGENTS_DIR)) return agents;
675
- const dirs = readdirSync4(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
863
+ const dirs = readdirSync5(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
676
864
  for (const dir of dirs) {
677
865
  const state = getAgentState(dir.name);
678
866
  if (state) {
@@ -701,11 +889,11 @@ function messageAgent(agentId, message) {
701
889
  throw new Error(`Agent ${normalizedId} not running`);
702
890
  }
703
891
  sendKeys(normalizedId, message);
704
- const mailDir = join4(getAgentDir(normalizedId), "mail");
892
+ const mailDir = join5(getAgentDir(normalizedId), "mail");
705
893
  mkdirSync4(mailDir, { recursive: true });
706
894
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
707
895
  writeFileSync3(
708
- join4(mailDir, `${timestamp}.md`),
896
+ join5(mailDir, `${timestamp}.md`),
709
897
  `# Message
710
898
 
711
899
  ${message}
@@ -727,7 +915,7 @@ function recoverAgent(agentId) {
727
915
  if (sessionExists(normalizedId)) {
728
916
  return state;
729
917
  }
730
- const healthFile = join4(getAgentDir(normalizedId), "health.json");
918
+ const healthFile = join5(getAgentDir(normalizedId), "health.json");
731
919
  let health = { consecutiveFailures: 0, killCount: 0, recoveryCount: 0 };
732
920
  if (existsSync5(healthFile)) {
733
921
  try {
@@ -738,7 +926,7 @@ function recoverAgent(agentId) {
738
926
  health.recoveryCount = (health.recoveryCount || 0) + 1;
739
927
  writeFileSync3(healthFile, JSON.stringify(health, null, 2));
740
928
  const recoveryPrompt = generateRecoveryPrompt(state);
741
- const claudeCmd = `claude --model ${state.model} "${recoveryPrompt.replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
929
+ const claudeCmd = `claude --dangerously-skip-permissions --model ${state.model} "${recoveryPrompt.replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
742
930
  createSession(normalizedId, state.workspace, claudeCmd);
743
931
  state.status = "running";
744
932
  state.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
@@ -797,37 +985,203 @@ function autoRecoverAgents() {
797
985
  }
798
986
 
799
987
  // src/cli/commands/work/issue.ts
988
+ function findWorkspace(issueId) {
989
+ const normalizedId = issueId.toLowerCase();
990
+ let dir = process.cwd();
991
+ for (let i = 0; i < 10; i++) {
992
+ const workspacesDir = join6(dir, "workspaces");
993
+ if (existsSync6(workspacesDir)) {
994
+ const workspaceName = `feature-${normalizedId}`;
995
+ const workspacePath = join6(workspacesDir, workspaceName);
996
+ if (existsSync6(workspacePath)) {
997
+ return workspacePath;
998
+ }
999
+ const altPath = join6(workspacesDir, normalizedId);
1000
+ if (existsSync6(altPath)) {
1001
+ return altPath;
1002
+ }
1003
+ }
1004
+ const parent = dirname2(dir);
1005
+ if (parent === dir) break;
1006
+ dir = parent;
1007
+ }
1008
+ return null;
1009
+ }
1010
+ function findProjectRoot() {
1011
+ let dir = process.cwd();
1012
+ for (let i = 0; i < 10; i++) {
1013
+ if (existsSync6(join6(dir, "workspaces")) || existsSync6(join6(dir, ".git")) || existsSync6(join6(dir, "CLAUDE.md"))) {
1014
+ return dir;
1015
+ }
1016
+ const parent = dirname2(dir);
1017
+ if (parent === dir) break;
1018
+ dir = parent;
1019
+ }
1020
+ return process.cwd();
1021
+ }
1022
+ function readPlanningContext(workspacePath) {
1023
+ const statePath = join6(workspacePath, ".planning", "STATE.md");
1024
+ if (existsSync6(statePath)) {
1025
+ return readFileSync5(statePath, "utf-8");
1026
+ }
1027
+ return null;
1028
+ }
1029
+ function extractBeadsIdsFromState(stateContent) {
1030
+ const ids = [];
1031
+ const backtickMatches = stateContent.match(/`([a-z]+-[a-z0-9]+)`/g) || [];
1032
+ for (const match of backtickMatches) {
1033
+ const id = match.replace(/`/g, "");
1034
+ if (id.match(/^[a-z]+-[a-z0-9]{2,4}$/)) {
1035
+ ids.push(id);
1036
+ }
1037
+ }
1038
+ return [...new Set(ids)];
1039
+ }
1040
+ function readBeadsTasks(workspacePath, projectRoot, issueId) {
1041
+ const tasks = [];
1042
+ const normalizedId = issueId.toLowerCase();
1043
+ const stateContent = readPlanningContext(workspacePath);
1044
+ const beadsIds = stateContent ? extractBeadsIdsFromState(stateContent) : [];
1045
+ const beadsPaths = [
1046
+ join6(workspacePath, ".beads", "issues.jsonl"),
1047
+ join6(projectRoot, ".beads", "issues.jsonl")
1048
+ ];
1049
+ const seenIds = /* @__PURE__ */ new Set();
1050
+ for (const beadsPath of beadsPaths) {
1051
+ if (!existsSync6(beadsPath)) continue;
1052
+ try {
1053
+ const content = readFileSync5(beadsPath, "utf-8");
1054
+ const lines = content.split("\n").filter((line) => line.trim());
1055
+ for (const line of lines) {
1056
+ try {
1057
+ const task = JSON.parse(line);
1058
+ if (seenIds.has(task.id)) continue;
1059
+ const tags = task.tags || [];
1060
+ const isMatch = beadsIds.includes(task.id) || tags.some((t) => t.toLowerCase().includes(normalizedId)) || task.title?.toLowerCase().includes(normalizedId);
1061
+ if (isMatch) {
1062
+ seenIds.add(task.id);
1063
+ tasks.push(`- [${task.status || "open"}] ${task.title} (${task.id})`);
1064
+ }
1065
+ } catch {
1066
+ }
1067
+ }
1068
+ } catch {
1069
+ }
1070
+ }
1071
+ return tasks;
1072
+ }
1073
+ function buildAgentPrompt(issueId, workspacePath, projectRoot) {
1074
+ const lines = [
1075
+ `# Working on Issue: ${issueId}`,
1076
+ "",
1077
+ `**Workspace:** ${workspacePath}`,
1078
+ ""
1079
+ ];
1080
+ const hasStateFile = existsSync6(join6(workspacePath, ".planning", "STATE.md"));
1081
+ const hasClaudeMd = existsSync6(join6(workspacePath, "CLAUDE.md"));
1082
+ const hasProjectClaudeMd = existsSync6(join6(projectRoot, "CLAUDE.md"));
1083
+ lines.push("## IMPORTANT: Read Context Files First");
1084
+ lines.push("");
1085
+ lines.push("Before starting any work, you MUST read these files to understand the full context:");
1086
+ lines.push("");
1087
+ if (hasStateFile) {
1088
+ lines.push(`1. **Read \`.planning/STATE.md\`** - Contains the full planning context, decisions made, and current status for this issue.`);
1089
+ }
1090
+ if (hasClaudeMd) {
1091
+ lines.push(`2. **Read \`CLAUDE.md\`** (in workspace) - Contains workspace-specific instructions and warnings.`);
1092
+ }
1093
+ if (hasProjectClaudeMd && projectRoot !== workspacePath) {
1094
+ lines.push(`3. **Read \`${projectRoot}/CLAUDE.md\`** - Contains project-wide development guidelines.`);
1095
+ }
1096
+ lines.push("");
1097
+ lines.push("These files contain critical context that may have been updated since the last session.");
1098
+ lines.push("");
1099
+ const beadsTasks = readBeadsTasks(workspacePath, projectRoot, issueId);
1100
+ if (beadsTasks.length > 0) {
1101
+ lines.push("## Beads Tasks");
1102
+ lines.push("");
1103
+ lines.push("Tasks created during planning (check STATE.md for which are complete):");
1104
+ lines.push("");
1105
+ lines.push(beadsTasks.join("\n"));
1106
+ lines.push("");
1107
+ lines.push("Use `bd show <task-id>` to see task details, `bd update <task-id> --status in_progress` to start work.");
1108
+ lines.push("");
1109
+ }
1110
+ lines.push("## Your Task");
1111
+ lines.push("");
1112
+ lines.push("1. Read the context files listed above");
1113
+ lines.push("2. Check STATE.md for current status and what work remains");
1114
+ lines.push("3. Continue implementing the planned work");
1115
+ lines.push("4. Mark beads tasks as complete as you finish them: `bd update <task-id> --status closed`");
1116
+ lines.push("");
1117
+ lines.push("## CRITICAL: Keep STATE.md Updated");
1118
+ lines.push("");
1119
+ lines.push("**You may be interrupted, crash, or be stopped at any time.** To ensure the next agent can continue:");
1120
+ lines.push("");
1121
+ lines.push("1. **Update `.planning/STATE.md` frequently** as you complete work");
1122
+ lines.push('2. After completing each task or significant milestone, update the "Current Status" section');
1123
+ lines.push("3. Document any decisions made or blockers encountered");
1124
+ lines.push('4. Keep the "Remaining Work" section accurate');
1125
+ lines.push("");
1126
+ lines.push("The next agent will read STATE.md to know exactly where to pick up. Beads tasks track individual items,");
1127
+ lines.push("but STATE.md provides the narrative context and current state that beads alone cannot capture.");
1128
+ lines.push("");
1129
+ return lines.join("\n");
1130
+ }
800
1131
  async function issueCommand(id, options) {
801
1132
  const spinner = ora4(`Preparing workspace for ${id}...`).start();
802
1133
  try {
803
1134
  const normalizedId = id.toLowerCase();
804
- const workspace = process.cwd();
1135
+ const projectRoot = findProjectRoot();
1136
+ let workspace = findWorkspace(id);
1137
+ if (!workspace) {
1138
+ spinner.warn(`No workspace found for ${id}, using project root`);
1139
+ workspace = projectRoot;
1140
+ } else {
1141
+ spinner.text = `Found workspace: ${workspace}`;
1142
+ }
805
1143
  if (options.dryRun) {
806
1144
  spinner.info("Dry run mode");
807
1145
  console.log("");
808
- console.log(chalk5.bold("Would create:"));
1146
+ console.log(chalk6.bold("Would create:"));
809
1147
  console.log(` Agent ID: agent-${normalizedId}`);
810
1148
  console.log(` Workspace: ${workspace}`);
811
1149
  console.log(` Runtime: ${options.runtime}`);
812
1150
  console.log(` Model: ${options.model}`);
1151
+ const planningContext2 = readPlanningContext(workspace);
1152
+ const beadsTasks2 = readBeadsTasks(workspace, projectRoot, id);
1153
+ console.log("");
1154
+ console.log(chalk6.bold("Context:"));
1155
+ console.log(` Planning: ${planningContext2 ? "Found (.planning/STATE.md)" : "None"}`);
1156
+ console.log(` Beads: ${beadsTasks2.length} tasks`);
813
1157
  return;
814
1158
  }
1159
+ spinner.text = "Building agent prompt with planning context...";
1160
+ const prompt = buildAgentPrompt(id, workspace, projectRoot);
815
1161
  spinner.text = "Spawning agent...";
816
1162
  const agent = spawnAgent({
817
1163
  issueId: id,
818
1164
  workspace,
819
1165
  runtime: options.runtime,
820
1166
  model: options.model,
821
- prompt: `You are working on issue ${id}. Check beads for context: bd show ${id}`
1167
+ prompt
822
1168
  });
823
1169
  spinner.succeed(`Agent spawned: ${agent.id}`);
824
1170
  console.log("");
825
- console.log(chalk5.bold("Agent Details:"));
826
- console.log(` Session: ${chalk5.cyan(agent.id)}`);
1171
+ console.log(chalk6.bold("Agent Details:"));
1172
+ console.log(` Session: ${chalk6.cyan(agent.id)}`);
827
1173
  console.log(` Workspace: ${workspace}`);
828
1174
  console.log(` Runtime: ${agent.runtime} (${agent.model})`);
1175
+ const planningContext = readPlanningContext(workspace);
1176
+ const beadsTasks = readBeadsTasks(workspace, projectRoot, id);
1177
+ if (planningContext || beadsTasks.length > 0) {
1178
+ console.log("");
1179
+ console.log(chalk6.bold("Context Loaded:"));
1180
+ if (planningContext) console.log(` Planning: ${chalk6.green("\u2713")} STATE.md`);
1181
+ if (beadsTasks.length > 0) console.log(` Beads: ${chalk6.green("\u2713")} ${beadsTasks.length} tasks`);
1182
+ }
829
1183
  console.log("");
830
- console.log(chalk5.dim("Commands:"));
1184
+ console.log(chalk6.dim("Commands:"));
831
1185
  console.log(` Attach: tmux attach -t ${agent.id}`);
832
1186
  console.log(` Message: pan work tell ${id} "your message"`);
833
1187
  console.log(` Kill: pan work kill ${id}`);
@@ -838,7 +1192,7 @@ async function issueCommand(id, options) {
838
1192
  }
839
1193
 
840
1194
  // src/cli/commands/work/status.ts
841
- import chalk6 from "chalk";
1195
+ import chalk7 from "chalk";
842
1196
  async function statusCommand(options) {
843
1197
  const agents = listRunningAgents();
844
1198
  if (options.json) {
@@ -846,101 +1200,101 @@ async function statusCommand(options) {
846
1200
  return;
847
1201
  }
848
1202
  if (agents.length === 0) {
849
- console.log(chalk6.dim("No running agents."));
850
- console.log(chalk6.dim('Use "pan work issue <id>" to spawn one.'));
1203
+ console.log(chalk7.dim("No running agents."));
1204
+ console.log(chalk7.dim('Use "pan work issue <id>" to spawn one.'));
851
1205
  return;
852
1206
  }
853
- console.log(chalk6.bold("\nRunning Agents\n"));
1207
+ console.log(chalk7.bold("\nRunning Agents\n"));
854
1208
  for (const agent of agents) {
855
- const statusColor = agent.tmuxActive ? chalk6.green : chalk6.red;
1209
+ const statusColor = agent.tmuxActive ? chalk7.green : chalk7.red;
856
1210
  const status = agent.tmuxActive ? "running" : "stopped";
857
1211
  const startedAt = new Date(agent.startedAt);
858
1212
  const duration = Math.floor((Date.now() - startedAt.getTime()) / 1e3 / 60);
859
- console.log(`${chalk6.cyan(agent.id)}`);
1213
+ console.log(`${chalk7.cyan(agent.id)}`);
860
1214
  console.log(` Issue: ${agent.issueId}`);
861
1215
  console.log(` Status: ${statusColor(status)}`);
862
1216
  console.log(` Runtime: ${agent.runtime} (${agent.model})`);
863
1217
  console.log(` Duration: ${duration} min`);
864
- console.log(` Workspace: ${chalk6.dim(agent.workspace)}`);
1218
+ console.log(` Workspace: ${chalk7.dim(agent.workspace)}`);
865
1219
  console.log("");
866
1220
  }
867
1221
  }
868
1222
 
869
1223
  // src/cli/commands/work/tell.ts
870
- import chalk7 from "chalk";
1224
+ import chalk8 from "chalk";
871
1225
  async function tellCommand(id, message) {
872
1226
  const agentId = id.startsWith("agent-") ? id : `agent-${id.toLowerCase()}`;
873
1227
  try {
874
1228
  messageAgent(agentId, message);
875
- console.log(chalk7.green("Message sent to " + agentId));
876
- console.log(chalk7.dim(` "${message}"`));
1229
+ console.log(chalk8.green("Message sent to " + agentId));
1230
+ console.log(chalk8.dim(` "${message}"`));
877
1231
  } catch (error) {
878
- console.error(chalk7.red("Error: " + error.message));
1232
+ console.error(chalk8.red("Error: " + error.message));
879
1233
  process.exit(1);
880
1234
  }
881
1235
  }
882
1236
 
883
1237
  // src/cli/commands/work/kill.ts
884
- import chalk8 from "chalk";
1238
+ import chalk9 from "chalk";
885
1239
  async function killCommand(id, options) {
886
1240
  const agentId = id.startsWith("agent-") ? id : `agent-${id.toLowerCase()}`;
887
1241
  const state = getAgentState(agentId);
888
1242
  const isRunning = sessionExists(agentId);
889
1243
  if (!state && !isRunning) {
890
- console.log(chalk8.yellow(`Agent ${agentId} not found.`));
1244
+ console.log(chalk9.yellow(`Agent ${agentId} not found.`));
891
1245
  return;
892
1246
  }
893
1247
  if (!options.force && isRunning) {
894
1248
  }
895
1249
  try {
896
1250
  stopAgent(agentId);
897
- console.log(chalk8.green(`Killed agent: ${agentId}`));
1251
+ console.log(chalk9.green(`Killed agent: ${agentId}`));
898
1252
  } catch (error) {
899
- console.error(chalk8.red("Error: " + error.message));
1253
+ console.error(chalk9.red("Error: " + error.message));
900
1254
  process.exit(1);
901
1255
  }
902
1256
  }
903
1257
 
904
1258
  // src/cli/commands/work/pending.ts
905
- import chalk9 from "chalk";
906
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
907
- import { join as join5 } from "path";
1259
+ import chalk10 from "chalk";
1260
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
1261
+ import { join as join7 } from "path";
908
1262
  async function pendingCommand() {
909
1263
  const agents = listRunningAgents().filter((a) => !a.tmuxActive && a.status !== "error");
910
1264
  if (agents.length === 0) {
911
- console.log(chalk9.dim("No pending reviews."));
912
- console.log(chalk9.dim("Agents will appear here when they complete work."));
1265
+ console.log(chalk10.dim("No pending reviews."));
1266
+ console.log(chalk10.dim("Agents will appear here when they complete work."));
913
1267
  return;
914
1268
  }
915
- console.log(chalk9.bold("\nPending Reviews\n"));
1269
+ console.log(chalk10.bold("\nPending Reviews\n"));
916
1270
  for (const agent of agents) {
917
- console.log(`${chalk9.cyan(agent.issueId)}`);
1271
+ console.log(`${chalk10.cyan(agent.issueId)}`);
918
1272
  console.log(` Agent: ${agent.id}`);
919
- console.log(` Workspace: ${chalk9.dim(agent.workspace)}`);
920
- const completionFile = join5(AGENTS_DIR, agent.id, "completion.md");
921
- if (existsSync6(completionFile)) {
922
- const content = readFileSync5(completionFile, "utf8");
1273
+ console.log(` Workspace: ${chalk10.dim(agent.workspace)}`);
1274
+ const completionFile = join7(AGENTS_DIR, agent.id, "completion.md");
1275
+ if (existsSync7(completionFile)) {
1276
+ const content = readFileSync6(completionFile, "utf8");
923
1277
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"));
924
1278
  if (firstLine) {
925
- console.log(` Summary: ${chalk9.dim(firstLine.trim())}`);
1279
+ console.log(` Summary: ${chalk10.dim(firstLine.trim())}`);
926
1280
  }
927
1281
  }
928
1282
  console.log("");
929
1283
  }
930
- console.log(chalk9.dim('Run "pan work approve <id>" to approve and merge.'));
1284
+ console.log(chalk10.dim('Run "pan work approve <id>" to approve and merge.'));
931
1285
  }
932
1286
 
933
1287
  // src/cli/commands/work/approve.ts
934
- import chalk10 from "chalk";
1288
+ import chalk11 from "chalk";
935
1289
  import ora5 from "ora";
936
- import { existsSync as existsSync7, writeFileSync as writeFileSync4, readFileSync as readFileSync6 } from "fs";
937
- import { join as join6 } from "path";
1290
+ import { existsSync as existsSync8, writeFileSync as writeFileSync4, readFileSync as readFileSync7 } from "fs";
1291
+ import { join as join8 } from "path";
938
1292
  import { homedir } from "os";
939
- import { execSync as execSync2 } from "child_process";
1293
+ import { execSync as execSync3 } from "child_process";
940
1294
  function getLinearApiKey() {
941
- const envFile = join6(homedir(), ".panopticon.env");
942
- if (existsSync7(envFile)) {
943
- const content = readFileSync6(envFile, "utf-8");
1295
+ const envFile = join8(homedir(), ".panopticon.env");
1296
+ if (existsSync8(envFile)) {
1297
+ const content = readFileSync7(envFile, "utf-8");
944
1298
  const match = content.match(/LINEAR_API_KEY=(.+)/);
945
1299
  if (match) return match[1].trim();
946
1300
  }
@@ -948,7 +1302,7 @@ function getLinearApiKey() {
948
1302
  }
949
1303
  function checkGhCli() {
950
1304
  try {
951
- execSync2("which gh", { stdio: "pipe" });
1305
+ execSync3("which gh", { stdio: "pipe" });
952
1306
  return true;
953
1307
  } catch {
954
1308
  return false;
@@ -956,12 +1310,12 @@ function checkGhCli() {
956
1310
  }
957
1311
  function findPRForBranch(workspace) {
958
1312
  try {
959
- const branch = execSync2("git rev-parse --abbrev-ref HEAD", {
1313
+ const branch = execSync3("git rev-parse --abbrev-ref HEAD", {
960
1314
  cwd: workspace,
961
1315
  encoding: "utf-8",
962
1316
  stdio: ["pipe", "pipe", "pipe"]
963
1317
  }).trim();
964
- const prJson = execSync2(`gh pr list --head "${branch}" --json number,url --limit 1`, {
1318
+ const prJson = execSync3(`gh pr list --head "${branch}" --json number,url --limit 1`, {
965
1319
  cwd: workspace,
966
1320
  encoding: "utf-8",
967
1321
  stdio: ["pipe", "pipe", "pipe"]
@@ -977,7 +1331,7 @@ function findPRForBranch(workspace) {
977
1331
  }
978
1332
  function mergePR(workspace, prNumber) {
979
1333
  try {
980
- execSync2(`gh pr merge ${prNumber} --squash --delete-branch`, {
1334
+ execSync3(`gh pr merge ${prNumber} --squash --delete-branch`, {
981
1335
  cwd: workspace,
982
1336
  encoding: "utf-8",
983
1337
  stdio: ["pipe", "pipe", "pipe"]
@@ -1013,8 +1367,8 @@ async function approveCommand(id, options = {}) {
1013
1367
  const agentId = id.startsWith("agent-") ? id : `agent-${id.toLowerCase()}`;
1014
1368
  const state = getAgentState(agentId);
1015
1369
  if (!state) {
1016
- console.log(chalk10.yellow(`Agent ${agentId} not found.`));
1017
- console.log(chalk10.dim('Run "pan work status" to see running agents.'));
1370
+ console.log(chalk11.yellow(`Agent ${agentId} not found.`));
1371
+ console.log(chalk11.dim('Run "pan work status" to see running agents.'));
1018
1372
  return;
1019
1373
  }
1020
1374
  const spinner = ora5("Approving work...").start();
@@ -1025,7 +1379,7 @@ async function approveCommand(id, options = {}) {
1025
1379
  if (options.merge !== false) {
1026
1380
  if (!checkGhCli()) {
1027
1381
  spinner.warn("gh CLI not found - skipping PR merge");
1028
- console.log(chalk10.dim(" Install: https://cli.github.com/"));
1382
+ console.log(chalk11.dim(" Install: https://cli.github.com/"));
1029
1383
  } else {
1030
1384
  spinner.text = "Looking for PR...";
1031
1385
  const pr = findPRForBranch(workspace);
@@ -1034,13 +1388,13 @@ async function approveCommand(id, options = {}) {
1034
1388
  const result = mergePR(workspace, pr.number);
1035
1389
  if (result.success) {
1036
1390
  prMerged = true;
1037
- console.log(chalk10.green(` \u2713 Merged PR #${pr.number}`));
1391
+ console.log(chalk11.green(` \u2713 Merged PR #${pr.number}`));
1038
1392
  } else {
1039
- console.log(chalk10.yellow(` \u26A0 Failed to merge: ${result.error}`));
1040
- console.log(chalk10.dim(` Merge manually: gh pr merge ${pr.number} --squash`));
1393
+ console.log(chalk11.yellow(` \u26A0 Failed to merge: ${result.error}`));
1394
+ console.log(chalk11.dim(` Merge manually: gh pr merge ${pr.number} --squash`));
1041
1395
  }
1042
1396
  } else {
1043
- console.log(chalk10.dim(" No PR found for this branch"));
1397
+ console.log(chalk11.dim(" No PR found for this branch"));
1044
1398
  }
1045
1399
  }
1046
1400
  }
@@ -1050,18 +1404,18 @@ async function approveCommand(id, options = {}) {
1050
1404
  spinner.text = "Updating Linear status...";
1051
1405
  linearUpdated = await updateLinearStatus(apiKey, state.issueId);
1052
1406
  if (linearUpdated) {
1053
- console.log(chalk10.green(` \u2713 Updated ${state.issueId} to Done`));
1407
+ console.log(chalk11.green(` \u2713 Updated ${state.issueId} to Done`));
1054
1408
  } else {
1055
- console.log(chalk10.yellow(` \u26A0 Failed to update Linear status`));
1409
+ console.log(chalk11.yellow(` \u26A0 Failed to update Linear status`));
1056
1410
  }
1057
1411
  } else {
1058
- console.log(chalk10.dim(" LINEAR_API_KEY not set - skipping status update"));
1412
+ console.log(chalk11.dim(" LINEAR_API_KEY not set - skipping status update"));
1059
1413
  }
1060
1414
  }
1061
1415
  state.status = "stopped";
1062
1416
  state.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
1063
1417
  saveAgentState(state);
1064
- const approvedFile = join6(AGENTS_DIR, agentId, "approved");
1418
+ const approvedFile = join8(AGENTS_DIR, agentId, "approved");
1065
1419
  writeFileSync4(approvedFile, JSON.stringify({
1066
1420
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1067
1421
  prMerged,
@@ -1069,13 +1423,13 @@ async function approveCommand(id, options = {}) {
1069
1423
  }));
1070
1424
  spinner.succeed(`Approved: ${state.issueId}`);
1071
1425
  console.log("");
1072
- console.log(chalk10.bold("Summary:"));
1073
- console.log(` Issue: ${chalk10.cyan(state.issueId)}`);
1074
- console.log(` PR: ${prMerged ? chalk10.green("Merged") : chalk10.dim("Not merged")}`);
1075
- console.log(` Linear: ${linearUpdated ? chalk10.green("Updated to Done") : chalk10.dim("Not updated")}`);
1426
+ console.log(chalk11.bold("Summary:"));
1427
+ console.log(` Issue: ${chalk11.cyan(state.issueId)}`);
1428
+ console.log(` PR: ${prMerged ? chalk11.green("Merged") : chalk11.dim("Not merged")}`);
1429
+ console.log(` Linear: ${linearUpdated ? chalk11.green("Updated to Done") : chalk11.dim("Not updated")}`);
1076
1430
  console.log("");
1077
- console.log(chalk10.dim("Workspace can be cleaned up with:"));
1078
- console.log(chalk10.dim(` pan workspace destroy ${state.issueId}`));
1431
+ console.log(chalk11.dim("Workspace can be cleaned up with:"));
1432
+ console.log(chalk11.dim(` pan workspace destroy ${state.issueId}`));
1079
1433
  } catch (error) {
1080
1434
  spinner.fail(error.message);
1081
1435
  process.exit(1);
@@ -1083,16 +1437,17 @@ async function approveCommand(id, options = {}) {
1083
1437
  }
1084
1438
 
1085
1439
  // src/cli/commands/work/plan.ts
1086
- import chalk11 from "chalk";
1440
+ import chalk12 from "chalk";
1087
1441
  import ora6 from "ora";
1088
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync8 } from "fs";
1089
- import { join as join7 } from "path";
1442
+ import inquirer2 from "inquirer";
1443
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
1444
+ import { join as join9, dirname as dirname3 } from "path";
1090
1445
  import { homedir as homedir2 } from "os";
1091
- import { execSync as execSync3 } from "child_process";
1446
+ import { execSync as execSync4 } from "child_process";
1092
1447
  function getLinearApiKey2() {
1093
- const envFile = join7(homedir2(), ".panopticon.env");
1094
- if (existsSync8(envFile)) {
1095
- const content = readFileSync7(envFile, "utf-8");
1448
+ const envFile = join9(homedir2(), ".panopticon.env");
1449
+ if (existsSync9(envFile)) {
1450
+ const content = readFileSync8(envFile, "utf-8");
1096
1451
  const match = content.match(/LINEAR_API_KEY=(.+)/);
1097
1452
  if (match) return match[1].trim();
1098
1453
  }
@@ -1102,6 +1457,8 @@ function findPRDFiles(issueId) {
1102
1457
  const found = [];
1103
1458
  const cwd = process.cwd();
1104
1459
  const searchPaths = [
1460
+ "docs/prds/active",
1461
+ "docs/prds/planned",
1105
1462
  "docs/prds",
1106
1463
  "docs/prd",
1107
1464
  "prds",
@@ -1109,10 +1466,10 @@ function findPRDFiles(issueId) {
1109
1466
  ];
1110
1467
  const issueIdLower = issueId.toLowerCase();
1111
1468
  for (const searchPath of searchPaths) {
1112
- const fullPath = join7(cwd, searchPath);
1113
- if (!existsSync8(fullPath)) continue;
1469
+ const fullPath = join9(cwd, searchPath);
1470
+ if (!existsSync9(fullPath)) continue;
1114
1471
  try {
1115
- const result = execSync3(
1472
+ const result = execSync4(
1116
1473
  `find "${fullPath}" -type f -name "*.md" 2>/dev/null | xargs grep -l -i "${issueIdLower}" 2>/dev/null || true`,
1117
1474
  { encoding: "utf-8" }
1118
1475
  );
@@ -1123,76 +1480,352 @@ function findPRDFiles(issueId) {
1123
1480
  }
1124
1481
  return [...new Set(found)];
1125
1482
  }
1126
- function generatePlan(issue, prdFiles) {
1127
- const sections = [];
1128
- sections.push(`# Execution Plan: ${issue.identifier}`);
1129
- sections.push("");
1130
- sections.push(`**Title:** ${issue.title}`);
1131
- sections.push(`**Status:** ${issue.state.name}`);
1132
- if (issue.project) {
1133
- sections.push(`**Project:** ${issue.project.name}`);
1134
- }
1135
- sections.push(`**Linear:** ${issue.url}`);
1136
- sections.push("");
1137
- if (issue.description) {
1138
- sections.push("## Issue Description");
1139
- sections.push("");
1140
- sections.push(issue.description);
1141
- sections.push("");
1483
+ function analyzeComplexity(issue, prdFiles) {
1484
+ const reasons = [];
1485
+ const subsystems = [];
1486
+ let estimatedTasks = 1;
1487
+ const desc = (issue.description || "").toLowerCase();
1488
+ const title = issue.title.toLowerCase();
1489
+ const combined = `${title} ${desc}`;
1490
+ if (combined.includes("frontend") || combined.includes("ui") || combined.includes("component")) {
1491
+ subsystems.push("frontend");
1492
+ }
1493
+ if (combined.includes("backend") || combined.includes("api") || combined.includes("endpoint")) {
1494
+ subsystems.push("backend");
1495
+ }
1496
+ if (combined.includes("database") || combined.includes("migration") || combined.includes("schema")) {
1497
+ subsystems.push("database");
1498
+ }
1499
+ if (combined.includes("test") || combined.includes("e2e") || combined.includes("playwright")) {
1500
+ subsystems.push("tests");
1501
+ }
1502
+ if (subsystems.length > 1) {
1503
+ reasons.push(`Multiple subsystems involved: ${subsystems.join(", ")}`);
1504
+ estimatedTasks += subsystems.length;
1505
+ }
1506
+ const ambiguousPatterns = [
1507
+ "should we",
1508
+ "maybe",
1509
+ "or",
1510
+ "consider",
1511
+ "option",
1512
+ "approach",
1513
+ "tbd",
1514
+ "to be determined",
1515
+ "needs discussion",
1516
+ "unclear"
1517
+ ];
1518
+ for (const pattern of ambiguousPatterns) {
1519
+ if (combined.includes(pattern)) {
1520
+ reasons.push("Requirements may be ambiguous");
1521
+ break;
1522
+ }
1523
+ }
1524
+ const architecturePatterns = [
1525
+ "refactor",
1526
+ "architecture",
1527
+ "redesign",
1528
+ "restructure",
1529
+ "migrate",
1530
+ "integration",
1531
+ "authentication",
1532
+ "authorization",
1533
+ "security"
1534
+ ];
1535
+ for (const pattern of architecturePatterns) {
1536
+ if (combined.includes(pattern)) {
1537
+ reasons.push(`Architecture decision needed: ${pattern}`);
1538
+ estimatedTasks += 2;
1539
+ break;
1540
+ }
1541
+ }
1542
+ if (desc.length > 500) {
1543
+ reasons.push("Detailed description suggests complexity");
1544
+ estimatedTasks += 1;
1142
1545
  }
1143
1546
  if (prdFiles.length > 0) {
1144
- sections.push("## Related PRDs");
1145
- sections.push("");
1146
- for (const prd of prdFiles) {
1147
- sections.push(`- [${prd.replace(process.cwd() + "/", "")}](${prd})`);
1148
- }
1149
- sections.push("");
1150
- sections.push("> **IMPORTANT:** Review the PRD before starting implementation.");
1151
- sections.push("");
1152
- }
1153
- sections.push("## Implementation Steps");
1154
- sections.push("");
1155
- sections.push("<!-- Edit these steps based on the issue requirements -->");
1156
- sections.push("");
1157
- sections.push("- [ ] Understand requirements and existing code");
1158
- sections.push("- [ ] Design approach (document in comments if complex)");
1159
- sections.push("- [ ] Implement core functionality");
1160
- sections.push("- [ ] Add tests");
1161
- sections.push("- [ ] Verify linting/type checks pass");
1162
- sections.push("- [ ] Manual testing");
1163
- sections.push("- [ ] Update documentation if needed");
1164
- sections.push("");
1165
- sections.push("## Files to Modify");
1166
- sections.push("");
1167
- sections.push("<!-- List files that will need changes -->");
1168
- sections.push("");
1169
- sections.push("- TBD after codebase exploration");
1170
- sections.push("");
1171
- sections.push("## Test Strategy");
1172
- sections.push("");
1173
- sections.push("<!-- Define how this will be tested -->");
1174
- sections.push("");
1175
- sections.push("- Unit tests: TBD");
1176
- sections.push("- Integration tests: TBD");
1177
- sections.push("- E2E tests: TBD");
1178
- sections.push("");
1179
- sections.push("## Acceptance Criteria");
1180
- sections.push("");
1181
- sections.push("<!-- What must be true for this to be complete? -->");
1182
- sections.push("");
1183
- sections.push("- [ ] Feature works as described");
1184
- sections.push("- [ ] Tests pass");
1185
- sections.push("- [ ] No regressions");
1186
- sections.push("");
1187
- sections.push("## Notes for Agent");
1188
- sections.push("");
1189
- sections.push("<!-- Add any special instructions or context -->");
1190
- sections.push("");
1191
- sections.push("- Review this plan before starting");
1192
- sections.push("- Ask clarifying questions if requirements are unclear");
1193
- sections.push("- Commit frequently with descriptive messages");
1194
- sections.push("");
1195
- return sections.join("\n");
1547
+ reasons.push(`PRD exists - complexity already documented`);
1548
+ }
1549
+ const complexLabels = ["complex", "large", "epic", "multi-phase", "architecture"];
1550
+ for (const label of issue.labels || []) {
1551
+ if (complexLabels.some((cl) => label.name.toLowerCase().includes(cl))) {
1552
+ reasons.push(`Label indicates complexity: ${label.name}`);
1553
+ estimatedTasks += 2;
1554
+ }
1555
+ }
1556
+ const isComplex = reasons.length >= 2 || subsystems.length > 1 || estimatedTasks >= 4;
1557
+ return {
1558
+ isComplex,
1559
+ reasons,
1560
+ subsystems,
1561
+ estimatedTasks: Math.max(estimatedTasks, subsystems.length + 1)
1562
+ };
1563
+ }
1564
+ async function runDiscoveryPhase(issue, complexity, prdContent) {
1565
+ const decisions = [];
1566
+ const tasks = [];
1567
+ console.log("");
1568
+ console.log(chalk12.bold.cyan("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
1569
+ console.log(chalk12.bold.cyan(" DISCOVERY PHASE"));
1570
+ console.log(chalk12.bold.cyan("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
1571
+ console.log("");
1572
+ console.log(chalk12.dim("Answer questions to create a detailed execution plan."));
1573
+ console.log(chalk12.dim("Press Enter to skip optional questions."));
1574
+ console.log("");
1575
+ console.log(chalk12.bold("Issue:"), `${issue.identifier} - ${issue.title}`);
1576
+ if (complexity.subsystems.length > 0) {
1577
+ console.log(chalk12.bold("Detected subsystems:"), complexity.subsystems.join(", "));
1578
+ }
1579
+ console.log("");
1580
+ const scopeAnswer = await inquirer2.prompt([{
1581
+ type: "input",
1582
+ name: "scope",
1583
+ message: "What specific changes are needed? (be specific about files/components):",
1584
+ default: issue.description?.slice(0, 100) || ""
1585
+ }]);
1586
+ if (scopeAnswer.scope) {
1587
+ decisions.push({ question: "Scope", answer: scopeAnswer.scope });
1588
+ }
1589
+ const approachAnswer = await inquirer2.prompt([{
1590
+ type: "input",
1591
+ name: "approach",
1592
+ message: "Any specific technical approach or patterns to follow?"
1593
+ }]);
1594
+ if (approachAnswer.approach) {
1595
+ decisions.push({ question: "Technical approach", answer: approachAnswer.approach });
1596
+ }
1597
+ const edgeCasesAnswer = await inquirer2.prompt([{
1598
+ type: "input",
1599
+ name: "edgeCases",
1600
+ message: "Any edge cases or error scenarios to handle?"
1601
+ }]);
1602
+ if (edgeCasesAnswer.edgeCases) {
1603
+ decisions.push({ question: "Edge cases", answer: edgeCasesAnswer.edgeCases });
1604
+ }
1605
+ const testingAnswer = await inquirer2.prompt([{
1606
+ type: "checkbox",
1607
+ name: "testing",
1608
+ message: "What testing is required?",
1609
+ choices: [
1610
+ { name: "Unit tests", value: "unit", checked: true },
1611
+ { name: "Integration tests", value: "integration" },
1612
+ { name: "E2E tests (Playwright)", value: "e2e" },
1613
+ { name: "Manual testing only", value: "manual" }
1614
+ ]
1615
+ }]);
1616
+ if (testingAnswer.testing.length > 0) {
1617
+ decisions.push({ question: "Testing", answer: testingAnswer.testing.join(", ") });
1618
+ }
1619
+ const outOfScopeAnswer = await inquirer2.prompt([{
1620
+ type: "input",
1621
+ name: "outOfScope",
1622
+ message: "Anything explicitly OUT of scope for this issue?"
1623
+ }]);
1624
+ if (outOfScopeAnswer.outOfScope) {
1625
+ decisions.push({ question: "Out of scope", answer: outOfScopeAnswer.outOfScope });
1626
+ }
1627
+ console.log("");
1628
+ console.log(chalk12.bold("Define execution tasks:"));
1629
+ console.log(chalk12.dim("Enter tasks in order. Empty task name to finish."));
1630
+ console.log("");
1631
+ const suggestedTasks = [
1632
+ { name: "Understand requirements", description: "Review issue, PRD, and existing code" }
1633
+ ];
1634
+ if (complexity.subsystems.length > 1) {
1635
+ suggestedTasks.push({ name: "Design approach", description: "Document architecture decisions", dependsOn: "Understand requirements" });
1636
+ }
1637
+ for (const subsystem of complexity.subsystems) {
1638
+ suggestedTasks.push({
1639
+ name: `Implement ${subsystem}`,
1640
+ description: `Core ${subsystem} changes`,
1641
+ dependsOn: complexity.subsystems.length > 1 ? "Design approach" : "Understand requirements"
1642
+ });
1643
+ }
1644
+ if (suggestedTasks.length === 1) {
1645
+ suggestedTasks.push({ name: "Implement changes", description: "Core implementation", dependsOn: "Understand requirements" });
1646
+ }
1647
+ if (testingAnswer.testing.includes("unit") || testingAnswer.testing.includes("integration")) {
1648
+ suggestedTasks.push({ name: "Add tests", description: "Unit and/or integration tests", dependsOn: suggestedTasks[suggestedTasks.length - 1].name });
1649
+ }
1650
+ if (testingAnswer.testing.includes("e2e")) {
1651
+ suggestedTasks.push({ name: "Add E2E tests", description: "Playwright E2E tests", dependsOn: "Add tests" });
1652
+ }
1653
+ suggestedTasks.push({ name: "Verify and cleanup", description: "Lint, type check, final review", dependsOn: suggestedTasks[suggestedTasks.length - 1].name });
1654
+ console.log(chalk12.bold("Suggested tasks:"));
1655
+ for (let i = 0; i < suggestedTasks.length; i++) {
1656
+ const task = suggestedTasks[i];
1657
+ console.log(` ${i + 1}. ${task.name}${task.dependsOn ? chalk12.dim(` (after: ${task.dependsOn})`) : ""}`);
1658
+ }
1659
+ console.log("");
1660
+ const useDefaultAnswer = await inquirer2.prompt([{
1661
+ type: "confirm",
1662
+ name: "useDefault",
1663
+ message: "Use these suggested tasks?",
1664
+ default: true
1665
+ }]);
1666
+ if (useDefaultAnswer.useDefault) {
1667
+ tasks.push(...suggestedTasks);
1668
+ } else {
1669
+ let taskIndex = 1;
1670
+ let previousTask = "";
1671
+ while (true) {
1672
+ const taskAnswer = await inquirer2.prompt([{
1673
+ type: "input",
1674
+ name: "name",
1675
+ message: `Task ${taskIndex} name (empty to finish):`
1676
+ }]);
1677
+ if (!taskAnswer.name) break;
1678
+ const descAnswer = await inquirer2.prompt([{
1679
+ type: "input",
1680
+ name: "description",
1681
+ message: `Task ${taskIndex} description:`,
1682
+ default: taskAnswer.name
1683
+ }]);
1684
+ tasks.push({
1685
+ name: taskAnswer.name,
1686
+ description: descAnswer.description,
1687
+ dependsOn: previousTask || void 0
1688
+ });
1689
+ previousTask = taskAnswer.name;
1690
+ taskIndex++;
1691
+ }
1692
+ }
1693
+ return { tasks, decisions };
1694
+ }
1695
+ function generateStateFile(issue, decisions, tasks) {
1696
+ const lines = [
1697
+ `# Agent State: ${issue.identifier}`,
1698
+ "",
1699
+ `**Last Updated:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
1700
+ "",
1701
+ "## Current Position",
1702
+ "",
1703
+ `- **Issue:** ${issue.identifier}`,
1704
+ `- **Title:** ${issue.title}`,
1705
+ `- **Status:** Planning complete, ready for execution`,
1706
+ `- **Linear:** ${issue.url}`,
1707
+ "",
1708
+ "## Decisions Made During Planning",
1709
+ ""
1710
+ ];
1711
+ if (decisions.length > 0) {
1712
+ for (const decision of decisions) {
1713
+ lines.push(`- **${decision.question}:** ${decision.answer}`);
1714
+ }
1715
+ } else {
1716
+ lines.push("- No specific decisions recorded");
1717
+ }
1718
+ lines.push("");
1719
+ lines.push("## Planned Tasks");
1720
+ lines.push("");
1721
+ for (const task of tasks) {
1722
+ lines.push(`- [ ] ${task.name}${task.dependsOn ? ` (after: ${task.dependsOn})` : ""}`);
1723
+ }
1724
+ lines.push("");
1725
+ lines.push("## Blockers/Concerns");
1726
+ lines.push("");
1727
+ lines.push("- None identified during planning");
1728
+ lines.push("");
1729
+ lines.push("## Notes");
1730
+ lines.push("");
1731
+ lines.push("<!-- Add notes as work progresses -->");
1732
+ lines.push("");
1733
+ return lines.join("\n");
1734
+ }
1735
+ function generateWorkspaceFile(issue, prdFiles) {
1736
+ const lines = [
1737
+ `# Workspace: ${issue.identifier}`,
1738
+ "",
1739
+ `> ${issue.title}`,
1740
+ "",
1741
+ "## Quick Links",
1742
+ "",
1743
+ `- [Linear Issue](${issue.url})`
1744
+ ];
1745
+ for (const prd of prdFiles) {
1746
+ const relativePath = prd.replace(process.cwd() + "/", "");
1747
+ lines.push(`- [PRD](${relativePath})`);
1748
+ }
1749
+ lines.push("");
1750
+ lines.push("## Context Files");
1751
+ lines.push("");
1752
+ lines.push("- `STATE.md` - Current progress and decisions");
1753
+ lines.push("- `WORKSPACE.md` - This file");
1754
+ lines.push("");
1755
+ lines.push("## Beads");
1756
+ lines.push("");
1757
+ lines.push("Check current task status:");
1758
+ lines.push("```bash");
1759
+ lines.push("bd ready # Next actionable task");
1760
+ lines.push(`bd list --tag ${issue.identifier} # All tasks for this issue`);
1761
+ lines.push("```");
1762
+ lines.push("");
1763
+ lines.push("## Agent Instructions");
1764
+ lines.push("");
1765
+ lines.push("1. Run `bd ready` to get next task");
1766
+ lines.push("2. Complete the task following relevant skills");
1767
+ lines.push('3. Run `bd close "<task name>" --reason "..."` when done');
1768
+ lines.push("4. Update STATE.md with progress");
1769
+ lines.push("5. Repeat until all tasks complete");
1770
+ lines.push("");
1771
+ return lines.join("\n");
1772
+ }
1773
+ function createBeadsTasks(issue, tasks) {
1774
+ const created = [];
1775
+ const errors = [];
1776
+ const taskIds = /* @__PURE__ */ new Map();
1777
+ try {
1778
+ execSync4("which bd", { encoding: "utf-8" });
1779
+ } catch {
1780
+ return { success: false, created: [], errors: ["bd (beads) CLI not found in PATH"] };
1781
+ }
1782
+ for (const task of tasks) {
1783
+ const fullName = `${issue.identifier}: ${task.name}`;
1784
+ try {
1785
+ const escapedName = fullName.replace(/"/g, '\\"');
1786
+ let cmd = `bd create "${escapedName}" --type task -l "${issue.identifier},linear"`;
1787
+ if (task.dependsOn) {
1788
+ const depName = `${issue.identifier}: ${task.dependsOn}`;
1789
+ const depId = taskIds.get(depName);
1790
+ if (depId) {
1791
+ cmd += ` --deps "blocks:${depId}"`;
1792
+ }
1793
+ }
1794
+ if (task.description) {
1795
+ const escapedDesc = task.description.replace(/"/g, '\\"');
1796
+ cmd += ` -d "${escapedDesc}"`;
1797
+ }
1798
+ const result = execSync4(cmd, { encoding: "utf-8", cwd: process.cwd() });
1799
+ const idMatch = result.match(/bd-[a-f0-9]+/i) || result.match(/([a-f0-9-]{8,})/i);
1800
+ if (idMatch) {
1801
+ taskIds.set(fullName, idMatch[0]);
1802
+ }
1803
+ created.push(fullName);
1804
+ } catch (error) {
1805
+ const errMsg = error.stderr?.toString() || error.message;
1806
+ errors.push(`Failed to create "${task.name}": ${errMsg.split("\n")[0]}`);
1807
+ }
1808
+ }
1809
+ if (created.length > 0) {
1810
+ try {
1811
+ execSync4("bd flush", { encoding: "utf-8", cwd: process.cwd() });
1812
+ } catch {
1813
+ }
1814
+ }
1815
+ return { success: errors.length === 0, created, errors };
1816
+ }
1817
+ function copyToPRDDirectory(issue, stateContent) {
1818
+ const cwd = process.cwd();
1819
+ const prdDir = join9(cwd, "docs", "prds", "active");
1820
+ try {
1821
+ mkdirSync5(prdDir, { recursive: true });
1822
+ const filename = `${issue.identifier.toLowerCase()}-plan.md`;
1823
+ const prdPath = join9(prdDir, filename);
1824
+ writeFileSync5(prdPath, stateContent);
1825
+ return prdPath;
1826
+ } catch {
1827
+ return null;
1828
+ }
1196
1829
  }
1197
1830
  async function planCommand(id, options = {}) {
1198
1831
  const spinner = ora6(`Creating execution plan for ${id}...`).start();
@@ -1201,7 +1834,7 @@ async function planCommand(id, options = {}) {
1201
1834
  if (!apiKey) {
1202
1835
  spinner.fail("LINEAR_API_KEY not found");
1203
1836
  console.log("");
1204
- console.log(chalk11.dim("Set it in ~/.panopticon.env:"));
1837
+ console.log(chalk12.dim("Set it in ~/.panopticon.env:"));
1205
1838
  console.log(" LINEAR_API_KEY=lin_api_xxxxx");
1206
1839
  process.exit(1);
1207
1840
  }
@@ -1215,9 +1848,7 @@ async function planCommand(id, options = {}) {
1215
1848
  spinner.fail("No Linear team found");
1216
1849
  process.exit(1);
1217
1850
  }
1218
- const searchResult = await team.issues({
1219
- first: 100
1220
- });
1851
+ const searchResult = await team.issues({ first: 100 });
1221
1852
  const issue = searchResult.nodes.find(
1222
1853
  (i) => i.identifier.toUpperCase() === id.toUpperCase()
1223
1854
  );
@@ -1243,161 +1874,271 @@ async function planCommand(id, options = {}) {
1243
1874
  };
1244
1875
  spinner.text = "Searching for related PRDs...";
1245
1876
  const prdFiles = findPRDFiles(id);
1246
- spinner.text = "Generating execution plan...";
1247
- const plan = generatePlan(issueData, prdFiles);
1877
+ spinner.text = "Analyzing complexity...";
1878
+ const complexity = analyzeComplexity(issueData, prdFiles);
1879
+ spinner.stop();
1880
+ console.log("");
1881
+ console.log(chalk12.bold("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
1882
+ console.log(chalk12.bold(` ${issueData.identifier}: ${issueData.title}`));
1883
+ console.log(chalk12.bold("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
1884
+ console.log("");
1885
+ console.log(chalk12.bold("Complexity Analysis:"));
1886
+ console.log(` Level: ${complexity.isComplex ? chalk12.yellow("COMPLEX") : chalk12.green("SIMPLE")}`);
1887
+ console.log(` Estimated tasks: ${complexity.estimatedTasks}`);
1888
+ if (complexity.subsystems.length > 0) {
1889
+ console.log(` Subsystems: ${complexity.subsystems.join(", ")}`);
1890
+ }
1891
+ if (complexity.reasons.length > 0) {
1892
+ console.log(` Reasons:`);
1893
+ for (const reason of complexity.reasons) {
1894
+ console.log(` - ${reason}`);
1895
+ }
1896
+ }
1897
+ console.log("");
1898
+ if (prdFiles.length > 0) {
1899
+ console.log(chalk12.bold("Related PRDs found:"));
1900
+ for (const prd of prdFiles) {
1901
+ console.log(` - ${prd.replace(process.cwd() + "/", "")}`);
1902
+ }
1903
+ console.log("");
1904
+ }
1905
+ if (!complexity.isComplex && !options.force) {
1906
+ const skipAnswer = await inquirer2.prompt([{
1907
+ type: "confirm",
1908
+ name: "skip",
1909
+ message: "This looks simple. Skip planning and go straight to /work-issue?",
1910
+ default: true
1911
+ }]);
1912
+ if (skipAnswer.skip) {
1913
+ console.log("");
1914
+ console.log(chalk12.cyan(`Run: pan work issue ${id}`));
1915
+ console.log("");
1916
+ return;
1917
+ }
1918
+ }
1919
+ let tasks;
1920
+ let decisions;
1921
+ if (options.skipDiscovery) {
1922
+ tasks = [
1923
+ { name: "Understand requirements", description: "Review issue and existing code" },
1924
+ { name: "Implement changes", description: "Core implementation", dependsOn: "Understand requirements" },
1925
+ { name: "Add tests", description: "Unit/integration tests", dependsOn: "Implement changes" },
1926
+ { name: "Verify and cleanup", description: "Lint, type check, final review", dependsOn: "Add tests" }
1927
+ ];
1928
+ decisions = [];
1929
+ } else {
1930
+ const discovery = await runDiscoveryPhase(issueData, complexity);
1931
+ tasks = discovery.tasks;
1932
+ decisions = discovery.decisions;
1933
+ }
1934
+ const spinnerCreate = ora6("Creating context files...").start();
1935
+ const stateContent = generateStateFile(issueData, decisions, tasks);
1936
+ const workspaceContent = generateWorkspaceFile(issueData, prdFiles);
1937
+ const outputDir = options.output ? dirname3(options.output) : process.cwd();
1938
+ const planningDir = join9(outputDir, ".planning");
1939
+ mkdirSync5(planningDir, { recursive: true });
1940
+ const statePath = join9(planningDir, "STATE.md");
1941
+ writeFileSync5(statePath, stateContent);
1942
+ const workspacePath = join9(planningDir, "WORKSPACE.md");
1943
+ writeFileSync5(workspacePath, workspaceContent);
1944
+ spinnerCreate.succeed("Context files created");
1945
+ const spinnerBeads = ora6("Creating Beads tasks...").start();
1946
+ const beadsResult = createBeadsTasks(issueData, tasks);
1947
+ if (beadsResult.success) {
1948
+ spinnerBeads.succeed(`Created ${beadsResult.created.length} Beads tasks`);
1949
+ } else {
1950
+ spinnerBeads.warn(`Created ${beadsResult.created.length} tasks with errors`);
1951
+ for (const error of beadsResult.errors) {
1952
+ console.log(chalk12.red(` - ${error}`));
1953
+ }
1954
+ }
1955
+ const prdPath = copyToPRDDirectory(issueData, stateContent);
1956
+ if (prdPath) {
1957
+ console.log(chalk12.dim(`Plan copied to: ${prdPath.replace(process.cwd() + "/", "")}`));
1958
+ }
1248
1959
  if (options.json) {
1249
- spinner.stop();
1250
1960
  console.log(JSON.stringify({
1251
1961
  issue: issueData,
1252
- prdFiles,
1253
- plan
1962
+ complexity,
1963
+ tasks,
1964
+ decisions,
1965
+ files: {
1966
+ state: statePath,
1967
+ workspace: workspacePath,
1968
+ prd: prdPath
1969
+ },
1970
+ beads: beadsResult
1254
1971
  }, null, 2));
1255
1972
  return;
1256
1973
  }
1257
- const outputPath = options.output || `PLAN-${issue.identifier}.md`;
1258
- writeFileSync5(outputPath, plan);
1259
- spinner.succeed(`Execution plan created: ${outputPath}`);
1260
1974
  console.log("");
1261
- console.log(chalk11.bold("Issue Details:"));
1262
- console.log(` ${chalk11.cyan(issue.identifier)} ${issue.title}`);
1263
- console.log(` Status: ${state?.name}`);
1264
- if (prdFiles.length > 0) {
1265
- console.log(` PRDs found: ${chalk11.green(prdFiles.length)}`);
1266
- }
1975
+ console.log(chalk12.bold.green("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
1976
+ console.log(chalk12.bold.green(" PLAN COMPLETE"));
1977
+ console.log(chalk12.bold.green("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
1267
1978
  console.log("");
1268
- console.log(chalk11.bold("Next steps:"));
1269
- console.log(` 1. Review and edit ${chalk11.cyan(outputPath)}`);
1270
- console.log(` 2. Run ${chalk11.cyan(`pan work issue ${id}`)} to spawn agent`);
1979
+ console.log(chalk12.bold("Files created:"));
1980
+ console.log(` ${chalk12.cyan(statePath.replace(process.cwd() + "/", ""))}`);
1981
+ console.log(` ${chalk12.cyan(workspacePath.replace(process.cwd() + "/", ""))}`);
1271
1982
  console.log("");
1272
- if (prdFiles.length > 0) {
1273
- console.log(chalk11.yellow("PRD files found - agent will reference these:"));
1274
- for (const prd of prdFiles) {
1275
- console.log(` ${chalk11.dim(prd.replace(process.cwd() + "/", ""))}`);
1983
+ console.log(chalk12.bold("Beads tasks:"));
1984
+ for (const task of tasks) {
1985
+ console.log(` ${chalk12.dim("\u25CB")} ${issueData.identifier}: ${task.name}`);
1986
+ }
1987
+ console.log("");
1988
+ if (decisions.length > 0) {
1989
+ console.log(chalk12.bold("Decisions recorded:"));
1990
+ for (const decision of decisions) {
1991
+ console.log(` - ${decision.question}: ${chalk12.dim(decision.answer.slice(0, 50))}${decision.answer.length > 50 ? "..." : ""}`);
1276
1992
  }
1277
1993
  console.log("");
1278
1994
  }
1995
+ console.log(chalk12.bold("Next steps:"));
1996
+ console.log(` 1. Review ${chalk12.cyan(".planning/STATE.md")}`);
1997
+ console.log(` 2. Run ${chalk12.cyan(`pan work issue ${id}`)} to spawn agent`);
1998
+ console.log(` 3. Agent will use ${chalk12.cyan("bd ready")} to get tasks`);
1999
+ console.log("");
1279
2000
  } catch (error) {
1280
- spinner.fail(error.message);
2001
+ console.error(chalk12.red(`Error: ${error.message}`));
1281
2002
  process.exit(1);
1282
2003
  }
1283
2004
  }
1284
2005
 
1285
2006
  // src/cli/commands/work/list.ts
1286
- import chalk12 from "chalk";
2007
+ import chalk13 from "chalk";
1287
2008
  import ora7 from "ora";
1288
- import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
1289
- import { join as join8 } from "path";
1290
- import { homedir as homedir3 } from "os";
1291
- function getLinearApiKey3() {
1292
- const envFile = join8(homedir3(), ".panopticon.env");
1293
- if (existsSync9(envFile)) {
1294
- const content = readFileSync8(envFile, "utf-8");
1295
- const match = content.match(/LINEAR_API_KEY=(.+)/);
1296
- if (match) return match[1].trim();
1297
- }
1298
- return process.env.LINEAR_API_KEY || null;
1299
- }
1300
2009
  var PRIORITY_LABELS = {
1301
- 0: chalk12.dim("None"),
1302
- 1: chalk12.red("Urgent"),
1303
- 2: chalk12.yellow("High"),
1304
- 3: chalk12.blue("Medium"),
1305
- 4: chalk12.dim("Low")
2010
+ 0: chalk13.dim("None"),
2011
+ 1: chalk13.red("Urgent"),
2012
+ 2: chalk13.yellow("High"),
2013
+ 3: chalk13.blue("Medium"),
2014
+ 4: chalk13.dim("Low")
1306
2015
  };
1307
2016
  var STATE_COLORS = {
1308
- "Backlog": chalk12.dim,
1309
- "Todo": chalk12.white,
1310
- "In Progress": chalk12.yellow,
1311
- "In Review": chalk12.magenta,
1312
- "Done": chalk12.green,
1313
- "Canceled": chalk12.strikethrough
2017
+ "open": chalk13.white,
2018
+ "in_progress": chalk13.yellow,
2019
+ "closed": chalk13.green
1314
2020
  };
2021
+ function getTrackerConfig(trackerType) {
2022
+ const config = loadConfig();
2023
+ const trackerConfig = config.trackers[trackerType];
2024
+ if (!trackerConfig) {
2025
+ return null;
2026
+ }
2027
+ return {
2028
+ type: trackerType,
2029
+ apiKeyEnv: trackerConfig.api_key_env,
2030
+ team: trackerConfig.team,
2031
+ tokenEnv: trackerConfig.token_env,
2032
+ owner: trackerConfig.owner,
2033
+ repo: trackerConfig.repo,
2034
+ projectId: trackerConfig.project_id
2035
+ };
2036
+ }
2037
+ function getConfiguredTrackers() {
2038
+ const config = loadConfig();
2039
+ const trackers = [];
2040
+ if (config.trackers.linear) trackers.push("linear");
2041
+ if (config.trackers.github) trackers.push("github");
2042
+ if (config.trackers.gitlab) trackers.push("gitlab");
2043
+ return trackers;
2044
+ }
2045
+ function displayIssues(issues, trackerName) {
2046
+ if (issues.length === 0) {
2047
+ console.log(chalk13.dim(` No issues found in ${trackerName}`));
2048
+ return;
2049
+ }
2050
+ const byState = {};
2051
+ for (const issue of issues) {
2052
+ if (!byState[issue.state]) byState[issue.state] = [];
2053
+ byState[issue.state].push(issue);
2054
+ }
2055
+ const stateOrder = ["in_progress", "open", "closed"];
2056
+ for (const state of stateOrder) {
2057
+ const stateIssues = byState[state];
2058
+ if (!stateIssues || stateIssues.length === 0) continue;
2059
+ const colorFn = STATE_COLORS[state] || chalk13.white;
2060
+ const displayState = state.replace("_", " ").replace(/\b\w/g, (l) => l.toUpperCase());
2061
+ console.log(colorFn(` \u2500\u2500 ${displayState} (${stateIssues.length}) \u2500\u2500`));
2062
+ for (const issue of stateIssues) {
2063
+ const priorityLabel = issue.priority ? PRIORITY_LABELS[issue.priority] || "" : "";
2064
+ const assigneeStr = issue.assignee ? chalk13.dim(` @${issue.assignee.split(" ")[0]}`) : "";
2065
+ const priorityStr = issue.priority && issue.priority < 3 ? ` ${priorityLabel}` : "";
2066
+ console.log(` ${chalk13.cyan(issue.ref)} ${issue.title}${assigneeStr}${priorityStr}`);
2067
+ }
2068
+ console.log("");
2069
+ }
2070
+ }
1315
2071
  async function listCommand(options) {
1316
- const spinner = ora7("Fetching issues from Linear...").start();
2072
+ const spinner = ora7("Fetching issues...").start();
1317
2073
  try {
1318
- const apiKey = getLinearApiKey3();
1319
- if (!apiKey) {
1320
- spinner.fail("LINEAR_API_KEY not found");
1321
- console.log("");
1322
- console.log(chalk12.dim("Set it in ~/.panopticon.env:"));
1323
- console.log(" LINEAR_API_KEY=lin_api_xxxxx");
1324
- process.exit(1);
2074
+ const config = loadConfig();
2075
+ const trackersToQuery = [];
2076
+ if (options.tracker) {
2077
+ const trackerType = options.tracker;
2078
+ if (!["linear", "github", "gitlab"].includes(trackerType)) {
2079
+ spinner.fail(`Unknown tracker: ${options.tracker}`);
2080
+ console.log(chalk13.dim("Valid trackers: linear, github, gitlab"));
2081
+ process.exit(1);
2082
+ }
2083
+ trackersToQuery.push(trackerType);
2084
+ } else if (options.allTrackers) {
2085
+ trackersToQuery.push(...getConfiguredTrackers());
2086
+ } else {
2087
+ trackersToQuery.push(config.trackers.primary);
1325
2088
  }
1326
- const { LinearClient } = await import("@linear/sdk");
1327
- const client = new LinearClient({ apiKey });
1328
- const me = await client.viewer;
1329
- const teams = await me.teams();
1330
- const team = teams.nodes[0];
1331
- if (!team) {
1332
- spinner.fail("No Linear team found");
2089
+ if (trackersToQuery.length === 0) {
2090
+ spinner.fail("No trackers configured");
2091
+ console.log(chalk13.dim("Configure trackers in ~/.panopticon/config.toml"));
1333
2092
  process.exit(1);
1334
2093
  }
1335
- spinner.text = `Fetching issues from ${team.name}...`;
1336
- let issues;
1337
- if (options.mine) {
1338
- const assignedIssues = await me.assignedIssues({
1339
- first: 50,
1340
- filter: options.all ? {} : { state: { type: { neq: "completed" } } }
1341
- });
1342
- issues = assignedIssues.nodes;
1343
- } else {
1344
- const teamIssues = await team.issues({
1345
- first: 50,
1346
- filter: options.all ? {} : { state: { type: { neq: "completed" } } }
1347
- });
1348
- issues = teamIssues.nodes;
2094
+ const allIssues = [];
2095
+ for (const trackerType of trackersToQuery) {
2096
+ spinner.text = `Fetching from ${trackerType}...`;
2097
+ const trackerConfig = getTrackerConfig(trackerType);
2098
+ if (!trackerConfig) {
2099
+ console.log(chalk13.yellow(`
2100
+ Warning: ${trackerType} not configured, skipping`));
2101
+ continue;
2102
+ }
2103
+ try {
2104
+ const tracker = createTracker(trackerConfig);
2105
+ const issues = await tracker.listIssues({
2106
+ includeClosed: options.all,
2107
+ assignee: options.mine ? "me" : void 0
2108
+ });
2109
+ allIssues.push({ tracker: trackerType, issues });
2110
+ } catch (error) {
2111
+ console.log(chalk13.yellow(`
2112
+ Warning: Failed to fetch from ${trackerType}: ${error.message}`));
2113
+ }
1349
2114
  }
1350
2115
  spinner.stop();
1351
2116
  if (options.json) {
1352
- const formatted = await Promise.all(issues.map(async (issue) => {
1353
- const state = await issue.state;
1354
- const assignee = await issue.assignee;
1355
- return {
1356
- id: issue.id,
1357
- identifier: issue.identifier,
1358
- title: issue.title,
1359
- state: state?.name,
1360
- priority: issue.priority,
1361
- assignee: assignee?.name,
1362
- url: issue.url
1363
- };
1364
- }));
1365
- console.log(JSON.stringify(formatted, null, 2));
2117
+ const output = allIssues.flatMap(
2118
+ ({ tracker, issues }) => issues.map((issue) => ({ ...issue, source: tracker }))
2119
+ );
2120
+ console.log(JSON.stringify(output, null, 2));
1366
2121
  return;
1367
2122
  }
1368
- if (issues.length === 0) {
1369
- console.log(chalk12.dim("No issues found."));
2123
+ const totalIssues = allIssues.reduce((sum, { issues }) => sum + issues.length, 0);
2124
+ if (totalIssues === 0) {
2125
+ console.log(chalk13.dim("\nNo issues found."));
1370
2126
  return;
1371
2127
  }
1372
- console.log(chalk12.bold(`
1373
- ${team.name} Issues
2128
+ for (const { tracker, issues } of allIssues) {
2129
+ console.log(chalk13.bold(`
2130
+ ${tracker.toUpperCase()} (${issues.length} issues)
1374
2131
  `));
1375
- const byState = {};
1376
- for (const issue of issues) {
1377
- const state = await issue.state;
1378
- const stateName = state?.name || "Unknown";
1379
- if (!byState[stateName]) byState[stateName] = [];
1380
- byState[stateName].push(issue);
1381
- }
1382
- const stateOrder = ["In Progress", "In Review", "Todo", "Backlog", "Done", "Canceled"];
1383
- for (const stateName of stateOrder) {
1384
- const stateIssues = byState[stateName];
1385
- if (!stateIssues || stateIssues.length === 0) continue;
1386
- const colorFn = STATE_COLORS[stateName] || chalk12.white;
1387
- console.log(colorFn(`\u2500\u2500 ${stateName} (${stateIssues.length}) \u2500\u2500`));
1388
- console.log("");
1389
- for (const issue of stateIssues) {
1390
- const assignee = await issue.assignee;
1391
- const priorityLabel = PRIORITY_LABELS[issue.priority] || "";
1392
- const assigneeStr = assignee ? chalk12.dim(` @${assignee.name.split(" ")[0]}`) : "";
1393
- console.log(` ${chalk12.cyan(issue.identifier)} ${issue.title}${assigneeStr}`);
1394
- if (issue.priority > 0 && issue.priority < 3) {
1395
- console.log(` ${priorityLabel}`);
1396
- }
1397
- }
1398
- console.log("");
2132
+ displayIssues(issues, tracker);
2133
+ }
2134
+ const trackerNames = trackersToQuery.join(", ");
2135
+ console.log(chalk13.dim(`Showing ${totalIssues} issues from ${trackerNames}.`));
2136
+ if (!options.all) {
2137
+ console.log(chalk13.dim("Use --all to include closed issues."));
2138
+ }
2139
+ if (!options.allTrackers && trackersToQuery.length === 1) {
2140
+ console.log(chalk13.dim("Use --all-trackers to query all configured trackers."));
1399
2141
  }
1400
- console.log(chalk12.dim(`Showing ${issues.length} issues. Use --all to include completed.`));
1401
2142
  } catch (error) {
1402
2143
  spinner.fail(error.message);
1403
2144
  process.exit(1);
@@ -1405,164 +2146,175 @@ ${team.name} Issues
1405
2146
  }
1406
2147
 
1407
2148
  // src/cli/commands/work/triage.ts
1408
- import chalk13 from "chalk";
2149
+ import chalk14 from "chalk";
1409
2150
  import ora8 from "ora";
1410
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync10 } from "fs";
1411
- import { join as join9 } from "path";
1412
- import { homedir as homedir4 } from "os";
1413
- function getConfig() {
1414
- const envFile = join9(homedir4(), ".panopticon.env");
1415
- const config = {};
1416
- if (existsSync10(envFile)) {
1417
- const content = readFileSync9(envFile, "utf-8");
1418
- const ghMatch = content.match(/GITHUB_TOKEN=(.+)/);
1419
- if (ghMatch) config.githubToken = ghMatch[1].trim();
1420
- const repoMatch = content.match(/GITHUB_REPO=(.+)/);
1421
- if (repoMatch) config.githubRepo = repoMatch[1].trim();
1422
- const linearMatch = content.match(/LINEAR_API_KEY=(.+)/);
1423
- if (linearMatch) config.linearApiKey = linearMatch[1].trim();
1424
- }
1425
- config.githubToken = config.githubToken || process.env.GITHUB_TOKEN;
1426
- config.githubRepo = config.githubRepo || process.env.GITHUB_REPO;
1427
- config.linearApiKey = config.linearApiKey || process.env.LINEAR_API_KEY;
1428
- return config;
2151
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync10, mkdirSync as mkdirSync6 } from "fs";
2152
+ import { join as join10 } from "path";
2153
+ import { homedir as homedir3 } from "os";
2154
+ function getTrackerConfig2(trackerType) {
2155
+ const config = loadConfig();
2156
+ const trackerConfig = config.trackers[trackerType];
2157
+ if (!trackerConfig) {
2158
+ return null;
2159
+ }
2160
+ return {
2161
+ type: trackerType,
2162
+ apiKeyEnv: trackerConfig.api_key_env,
2163
+ team: trackerConfig.team,
2164
+ tokenEnv: trackerConfig.token_env,
2165
+ owner: trackerConfig.owner,
2166
+ repo: trackerConfig.repo,
2167
+ projectId: trackerConfig.project_id
2168
+ };
1429
2169
  }
1430
2170
  function getTriageStatePath() {
1431
- return join9(homedir4(), ".panopticon", "triage-state.json");
2171
+ return join10(homedir3(), ".panopticon", "triage-state.json");
1432
2172
  }
1433
2173
  function loadTriageState() {
1434
2174
  const path = getTriageStatePath();
1435
2175
  if (existsSync10(path)) {
1436
- return JSON.parse(readFileSync9(path, "utf-8"));
2176
+ try {
2177
+ return JSON.parse(readFileSync9(path, "utf-8"));
2178
+ } catch {
2179
+ return { dismissed: [], created: {} };
2180
+ }
1437
2181
  }
1438
2182
  return { dismissed: [], created: {} };
1439
2183
  }
1440
2184
  function saveTriageState(state) {
2185
+ const dir = join10(homedir3(), ".panopticon");
2186
+ if (!existsSync10(dir)) {
2187
+ mkdirSync6(dir, { recursive: true });
2188
+ }
1441
2189
  const path = getTriageStatePath();
1442
2190
  writeFileSync6(path, JSON.stringify(state, null, 2));
1443
2191
  }
1444
- async function fetchGitHubIssues(token, repo) {
1445
- const response = await fetch(`https://api.github.com/repos/${repo}/issues?state=open&per_page=50`, {
1446
- headers: {
1447
- Authorization: `Bearer ${token}`,
1448
- Accept: "application/vnd.github.v3+json"
1449
- }
1450
- });
1451
- if (!response.ok) {
1452
- throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
1453
- }
1454
- const issues = await response.json();
1455
- return issues.filter((i) => !("pull_request" in i));
1456
- }
1457
- async function createLinearIssue(apiKey, title, description, githubUrl) {
1458
- const { LinearClient } = await import("@linear/sdk");
1459
- const client = new LinearClient({ apiKey });
1460
- const me = await client.viewer;
1461
- const teams = await me.teams();
1462
- const team = teams.nodes[0];
1463
- if (!team) {
1464
- throw new Error("No Linear team found");
1465
- }
1466
- const fullDescription = `${description}
1467
-
1468
- ---
1469
-
1470
- **From GitHub:** ${githubUrl}`;
1471
- const result = await client.createIssue({
1472
- teamId: team.id,
1473
- title,
1474
- description: fullDescription
1475
- });
1476
- const issue = await result.issue;
1477
- return issue?.identifier || "unknown";
1478
- }
1479
2192
  async function triageCommand(id, options = {}) {
1480
2193
  const spinner = ora8("Loading triage queue...").start();
1481
2194
  try {
1482
- const config = getConfig();
1483
- if (!config.githubToken || !config.githubRepo) {
1484
- spinner.info("GitHub integration not configured");
1485
- console.log("");
1486
- console.log(chalk13.bold("Setup Instructions:"));
2195
+ const config = loadConfig();
2196
+ const primaryType = config.trackers.primary;
2197
+ const secondaryType = config.trackers.secondary;
2198
+ if (!secondaryType) {
2199
+ spinner.info("No secondary tracker configured");
1487
2200
  console.log("");
1488
- console.log("Add to ~/.panopticon.env:");
1489
- console.log(chalk13.dim(" GITHUB_TOKEN=ghp_xxxxx"));
1490
- console.log(chalk13.dim(" GITHUB_REPO=owner/repo"));
2201
+ console.log(chalk14.bold("Setup Instructions:"));
1491
2202
  console.log("");
1492
- console.log(chalk13.dim("Get a token at: https://github.com/settings/tokens"));
1493
- console.log(chalk13.dim("Required scopes: repo (for private repos) or public_repo"));
2203
+ console.log("Add secondary tracker to ~/.panopticon/config.toml:");
2204
+ console.log(chalk14.dim(`
2205
+ [trackers]
2206
+ primary = "linear"
2207
+ secondary = "github"
2208
+
2209
+ [trackers.github]
2210
+ type = "github"
2211
+ token_env = "GITHUB_TOKEN"
2212
+ owner = "your-org"
2213
+ repo = "your-repo"
2214
+ `));
2215
+ return;
2216
+ }
2217
+ const primaryConfig = getTrackerConfig2(primaryType);
2218
+ const secondaryConfig = getTrackerConfig2(secondaryType);
2219
+ if (!primaryConfig) {
2220
+ spinner.fail(`Primary tracker (${primaryType}) not configured`);
2221
+ return;
2222
+ }
2223
+ if (!secondaryConfig) {
2224
+ spinner.fail(`Secondary tracker (${secondaryType}) not configured`);
1494
2225
  return;
1495
2226
  }
1496
2227
  const triageState = loadTriageState();
1497
2228
  if (id) {
1498
- const issueNumber = parseInt(id.replace("#", ""), 10);
2229
+ const issueRef = id.startsWith("#") ? id : `#${id}`;
1499
2230
  if (options.dismiss) {
1500
- if (!triageState.dismissed.includes(issueNumber)) {
1501
- triageState.dismissed.push(issueNumber);
2231
+ if (!triageState.dismissed.includes(issueRef)) {
2232
+ triageState.dismissed.push(issueRef);
1502
2233
  saveTriageState(triageState);
1503
2234
  }
1504
- spinner.succeed(`Dismissed #${issueNumber}: ${options.dismiss}`);
2235
+ spinner.succeed(`Dismissed ${issueRef}: ${options.dismiss}`);
1505
2236
  return;
1506
2237
  }
1507
2238
  if (options.create) {
1508
- if (!config.linearApiKey) {
1509
- spinner.fail("LINEAR_API_KEY not configured");
2239
+ spinner.text = `Fetching ${secondaryType} issue ${issueRef}...`;
2240
+ let secondaryTracker2;
2241
+ try {
2242
+ secondaryTracker2 = createTracker(secondaryConfig);
2243
+ } catch (error) {
2244
+ spinner.fail(`Failed to connect to ${secondaryType}: ${error.message}`);
1510
2245
  return;
1511
2246
  }
1512
- spinner.text = `Fetching GitHub issue #${issueNumber}...`;
1513
- const response = await fetch(
1514
- `https://api.github.com/repos/${config.githubRepo}/issues/${issueNumber}`,
1515
- {
1516
- headers: {
1517
- Authorization: `Bearer ${config.githubToken}`,
1518
- Accept: "application/vnd.github.v3+json"
1519
- }
1520
- }
1521
- );
1522
- if (!response.ok) {
1523
- spinner.fail(`GitHub issue #${issueNumber} not found`);
2247
+ let sourceIssue;
2248
+ try {
2249
+ sourceIssue = await secondaryTracker2.getIssue(id);
2250
+ } catch (error) {
2251
+ spinner.fail(`Issue ${issueRef} not found in ${secondaryType}`);
1524
2252
  return;
1525
2253
  }
1526
- const ghIssue = await response.json();
1527
- spinner.text = "Creating Linear issue...";
1528
- const linearId = await createLinearIssue(
1529
- config.linearApiKey,
1530
- ghIssue.title,
1531
- ghIssue.body || "",
1532
- ghIssue.html_url
1533
- );
1534
- triageState.created[issueNumber] = linearId;
2254
+ spinner.text = `Creating ${primaryType} issue...`;
2255
+ let primaryTracker;
2256
+ try {
2257
+ primaryTracker = createTracker(primaryConfig);
2258
+ } catch (error) {
2259
+ spinner.fail(`Failed to connect to ${primaryType}: ${error.message}`);
2260
+ return;
2261
+ }
2262
+ const newIssue = await primaryTracker.createIssue({
2263
+ title: sourceIssue.title,
2264
+ description: `${sourceIssue.description}
2265
+
2266
+ ---
2267
+
2268
+ **From ${secondaryType}:** ${sourceIssue.url}`,
2269
+ team: primaryConfig.team
2270
+ });
2271
+ try {
2272
+ await secondaryTracker2.addComment(
2273
+ id,
2274
+ `Internal tracking: ${newIssue.ref} (${primaryType})`
2275
+ );
2276
+ } catch {
2277
+ console.log(chalk14.yellow("\nNote: Could not add link comment to source issue"));
2278
+ }
2279
+ triageState.created[sourceIssue.ref] = newIssue.ref;
1535
2280
  saveTriageState(triageState);
1536
- spinner.succeed(`Created ${linearId} from GitHub #${issueNumber}`);
2281
+ spinner.succeed(`Created ${newIssue.ref} from ${sourceIssue.ref}`);
1537
2282
  console.log("");
1538
- console.log(` GitHub: ${chalk13.dim(ghIssue.html_url)}`);
1539
- console.log(` Linear: ${chalk13.cyan(linearId)}`);
2283
+ console.log(` ${secondaryType}: ${chalk14.dim(sourceIssue.url)}`);
2284
+ console.log(` ${primaryType}: ${chalk14.cyan(newIssue.ref)}`);
1540
2285
  return;
1541
2286
  }
1542
2287
  }
1543
- spinner.text = "Fetching GitHub issues...";
1544
- const issues = await fetchGitHubIssues(config.githubToken, config.githubRepo);
2288
+ spinner.text = `Fetching ${secondaryType} issues...`;
2289
+ let secondaryTracker;
2290
+ try {
2291
+ secondaryTracker = createTracker(secondaryConfig);
2292
+ } catch (error) {
2293
+ spinner.fail(`Failed to connect to ${secondaryType}: ${error.message}`);
2294
+ return;
2295
+ }
2296
+ const issues = await secondaryTracker.listIssues({ includeClosed: false });
1545
2297
  const pending = issues.filter(
1546
- (i) => !triageState.dismissed.includes(i.number) && !triageState.created[i.number]
2298
+ (i) => !triageState.dismissed.includes(i.ref) && !triageState.created[i.ref]
1547
2299
  );
1548
2300
  spinner.stop();
1549
2301
  if (pending.length === 0) {
1550
- console.log(chalk13.green("No issues pending triage."));
1551
- console.log(chalk13.dim(`${issues.length} total open, ${triageState.dismissed.length} dismissed, ${Object.keys(triageState.created).length} created`));
2302
+ console.log(chalk14.green("No issues pending triage."));
2303
+ console.log(chalk14.dim(`${issues.length} total open, ${triageState.dismissed.length} dismissed, ${Object.keys(triageState.created).length} created`));
1552
2304
  return;
1553
2305
  }
1554
- console.log(chalk13.bold(`
1555
- GitHub Issues Pending Triage (${pending.length})
2306
+ console.log(chalk14.bold(`
2307
+ ${secondaryType.toUpperCase()} Issues Pending Triage (${pending.length})
1556
2308
  `));
1557
2309
  for (const issue of pending) {
1558
- const labels = issue.labels.map((l) => chalk13.dim(`[${l.name}]`)).join(" ");
1559
- console.log(` ${chalk13.cyan(`#${issue.number}`)} ${issue.title} ${labels}`);
1560
- console.log(` ${chalk13.dim(issue.html_url)}`);
2310
+ const labels = issue.labels.map((l) => chalk14.dim(`[${l}]`)).join(" ");
2311
+ console.log(` ${chalk14.cyan(issue.ref)} ${issue.title} ${labels}`);
2312
+ console.log(` ${chalk14.dim(issue.url)}`);
1561
2313
  }
1562
2314
  console.log("");
1563
- console.log(chalk13.bold("Commands:"));
1564
- console.log(` ${chalk13.dim("Create Linear issue:")} pan work triage <number> --create`);
1565
- console.log(` ${chalk13.dim("Dismiss from queue:")} pan work triage <number> --dismiss "reason"`);
2315
+ console.log(chalk14.bold("Commands:"));
2316
+ console.log(` ${chalk14.dim(`Create ${primaryType} issue:`)} pan work triage <id> --create`);
2317
+ console.log(` ${chalk14.dim("Dismiss from queue:")} pan work triage <id> --dismiss "reason"`);
1566
2318
  console.log("");
1567
2319
  } catch (error) {
1568
2320
  spinner.fail(error.message);
@@ -1571,7 +2323,7 @@ GitHub Issues Pending Triage (${pending.length})
1571
2323
  }
1572
2324
 
1573
2325
  // src/cli/commands/work/hook.ts
1574
- import chalk14 from "chalk";
2326
+ import chalk15 from "chalk";
1575
2327
  async function hookCommand(action, idOrMessage, options = {}) {
1576
2328
  const agentId = process.env.PANOPTICON_AGENT_ID || "default";
1577
2329
  switch (action) {
@@ -1582,20 +2334,20 @@ async function hookCommand(action, idOrMessage, options = {}) {
1582
2334
  return;
1583
2335
  }
1584
2336
  if (!result.hasWork) {
1585
- console.log(chalk14.green("\u2713 No pending work on hook"));
2337
+ console.log(chalk15.green("\u2713 No pending work on hook"));
1586
2338
  return;
1587
2339
  }
1588
- console.log(chalk14.yellow(`\u26A0 ${result.items.length} item(s) on hook`));
2340
+ console.log(chalk15.yellow(`\u26A0 ${result.items.length} item(s) on hook`));
1589
2341
  if (result.urgentCount > 0) {
1590
- console.log(chalk14.red(` ${result.urgentCount} URGENT`));
2342
+ console.log(chalk15.red(` ${result.urgentCount} URGENT`));
1591
2343
  }
1592
2344
  console.log("");
1593
2345
  for (const item of result.items) {
1594
2346
  const priorityColor = {
1595
- urgent: chalk14.red,
1596
- high: chalk14.yellow,
1597
- normal: chalk14.white,
1598
- low: chalk14.dim
2347
+ urgent: chalk15.red,
2348
+ high: chalk15.yellow,
2349
+ normal: chalk15.white,
2350
+ low: chalk15.dim
1599
2351
  }[item.priority];
1600
2352
  console.log(`${priorityColor(`[${item.priority.toUpperCase()}]`)} ${item.id}`);
1601
2353
  console.log(` Type: ${item.type}`);
@@ -1609,13 +2361,13 @@ async function hookCommand(action, idOrMessage, options = {}) {
1609
2361
  }
1610
2362
  case "push": {
1611
2363
  if (!idOrMessage) {
1612
- console.log(chalk14.red("Usage: pan work hook push <agent-id> <message>"));
2364
+ console.log(chalk15.red("Usage: pan work hook push <agent-id> <message>"));
1613
2365
  process.exit(1);
1614
2366
  }
1615
2367
  const [targetAgent, ...messageParts] = idOrMessage.split(" ");
1616
2368
  const message = messageParts.join(" ");
1617
2369
  if (!message) {
1618
- console.log(chalk14.red("Message required"));
2370
+ console.log(chalk15.red("Message required"));
1619
2371
  process.exit(1);
1620
2372
  }
1621
2373
  const item = pushToHook(targetAgent.startsWith("agent-") ? targetAgent : `agent-${targetAgent}`, {
@@ -1624,36 +2376,36 @@ async function hookCommand(action, idOrMessage, options = {}) {
1624
2376
  source: "cli",
1625
2377
  payload: { message }
1626
2378
  });
1627
- console.log(chalk14.green(`\u2713 Pushed to hook: ${item.id}`));
2379
+ console.log(chalk15.green(`\u2713 Pushed to hook: ${item.id}`));
1628
2380
  break;
1629
2381
  }
1630
2382
  case "pop": {
1631
2383
  if (!idOrMessage) {
1632
- console.log(chalk14.red("Usage: pan work hook pop <item-id>"));
2384
+ console.log(chalk15.red("Usage: pan work hook pop <item-id>"));
1633
2385
  process.exit(1);
1634
2386
  }
1635
2387
  const success = popFromHook(agentId, idOrMessage);
1636
2388
  if (success) {
1637
- console.log(chalk14.green(`\u2713 Popped: ${idOrMessage}`));
2389
+ console.log(chalk15.green(`\u2713 Popped: ${idOrMessage}`));
1638
2390
  } else {
1639
- console.log(chalk14.yellow(`Item not found: ${idOrMessage}`));
2391
+ console.log(chalk15.yellow(`Item not found: ${idOrMessage}`));
1640
2392
  }
1641
2393
  break;
1642
2394
  }
1643
2395
  case "clear": {
1644
2396
  clearHook(idOrMessage || agentId);
1645
- console.log(chalk14.green("\u2713 Hook cleared"));
2397
+ console.log(chalk15.green("\u2713 Hook cleared"));
1646
2398
  break;
1647
2399
  }
1648
2400
  case "mail": {
1649
2401
  if (!idOrMessage) {
1650
- console.log(chalk14.red("Usage: pan work hook mail <agent-id> <message>"));
2402
+ console.log(chalk15.red("Usage: pan work hook mail <agent-id> <message>"));
1651
2403
  process.exit(1);
1652
2404
  }
1653
2405
  const [targetAgent, ...messageParts] = idOrMessage.split(" ");
1654
2406
  const message = messageParts.join(" ");
1655
2407
  if (!message) {
1656
- console.log(chalk14.red("Message required"));
2408
+ console.log(chalk15.red("Message required"));
1657
2409
  process.exit(1);
1658
2410
  }
1659
2411
  sendMail(
@@ -1661,32 +2413,32 @@ async function hookCommand(action, idOrMessage, options = {}) {
1661
2413
  "cli",
1662
2414
  message
1663
2415
  );
1664
- console.log(chalk14.green(`\u2713 Mail sent to ${targetAgent}`));
2416
+ console.log(chalk15.green(`\u2713 Mail sent to ${targetAgent}`));
1665
2417
  break;
1666
2418
  }
1667
2419
  case "gupp": {
1668
2420
  const prompt = generateGUPPPrompt(idOrMessage || agentId);
1669
2421
  if (!prompt) {
1670
- console.log(chalk14.green("No GUPP work found"));
2422
+ console.log(chalk15.green("No GUPP work found"));
1671
2423
  return;
1672
2424
  }
1673
2425
  console.log(prompt);
1674
2426
  break;
1675
2427
  }
1676
2428
  default:
1677
- console.log(chalk14.bold("Hook Commands:"));
2429
+ console.log(chalk15.bold("Hook Commands:"));
1678
2430
  console.log("");
1679
- console.log(` ${chalk14.cyan("pan work hook check [agent-id]")} - Check for pending work`);
1680
- console.log(` ${chalk14.cyan("pan work hook push <agent-id> <msg>")} - Push task to hook`);
1681
- console.log(` ${chalk14.cyan("pan work hook pop <item-id>")} - Remove completed item`);
1682
- console.log(` ${chalk14.cyan("pan work hook clear [agent-id]")} - Clear all hook items`);
1683
- console.log(` ${chalk14.cyan("pan work hook mail <agent-id> <msg>")} - Send mail to agent`);
1684
- console.log(` ${chalk14.cyan("pan work hook gupp [agent-id]")} - Generate GUPP prompt`);
2431
+ console.log(` ${chalk15.cyan("pan work hook check [agent-id]")} - Check for pending work`);
2432
+ console.log(` ${chalk15.cyan("pan work hook push <agent-id> <msg>")} - Push task to hook`);
2433
+ console.log(` ${chalk15.cyan("pan work hook pop <item-id>")} - Remove completed item`);
2434
+ console.log(` ${chalk15.cyan("pan work hook clear [agent-id]")} - Clear all hook items`);
2435
+ console.log(` ${chalk15.cyan("pan work hook mail <agent-id> <msg>")} - Send mail to agent`);
2436
+ console.log(` ${chalk15.cyan("pan work hook gupp [agent-id]")} - Generate GUPP prompt`);
1685
2437
  }
1686
2438
  }
1687
2439
 
1688
2440
  // src/cli/commands/work/recover.ts
1689
- import chalk15 from "chalk";
2441
+ import chalk16 from "chalk";
1690
2442
  import ora9 from "ora";
1691
2443
  async function recoverCommand(id, options = {}) {
1692
2444
  const spinner = ora9("Checking for crashed agents...").start();
@@ -1701,7 +2453,7 @@ async function recoverCommand(id, options = {}) {
1701
2453
  spinner.stop();
1702
2454
  console.log(JSON.stringify({ crashed: crashed.map((a) => a.id) }, null, 2));
1703
2455
  if (!options.all) {
1704
- console.log(chalk15.dim("\nUse --all to auto-recover all crashed agents"));
2456
+ console.log(chalk16.dim("\nUse --all to auto-recover all crashed agents"));
1705
2457
  return;
1706
2458
  }
1707
2459
  }
@@ -1709,12 +2461,12 @@ async function recoverCommand(id, options = {}) {
1709
2461
  spinner.info(`Found ${crashed.length} crashed agent(s)`);
1710
2462
  console.log("");
1711
2463
  for (const agent of crashed) {
1712
- console.log(` ${chalk15.red("\u25CF")} ${chalk15.cyan(agent.id)}`);
2464
+ console.log(` ${chalk16.red("\u25CF")} ${chalk16.cyan(agent.id)}`);
1713
2465
  console.log(` Issue: ${agent.issueId}`);
1714
2466
  console.log(` Started: ${agent.startedAt}`);
1715
2467
  console.log("");
1716
2468
  }
1717
- console.log(chalk15.dim("Use --all to auto-recover, or specify an agent ID"));
2469
+ console.log(chalk16.dim("Use --all to auto-recover, or specify an agent ID"));
1718
2470
  return;
1719
2471
  }
1720
2472
  spinner.text = "Auto-recovering agents...";
@@ -1725,15 +2477,15 @@ async function recoverCommand(id, options = {}) {
1725
2477
  return;
1726
2478
  }
1727
2479
  if (result.recovered.length > 0) {
1728
- console.log(chalk15.green(`\u2713 Recovered ${result.recovered.length} agent(s):`));
2480
+ console.log(chalk16.green(`\u2713 Recovered ${result.recovered.length} agent(s):`));
1729
2481
  for (const agentId2 of result.recovered) {
1730
- console.log(` ${chalk15.cyan(agentId2)}`);
2482
+ console.log(` ${chalk16.cyan(agentId2)}`);
1731
2483
  }
1732
2484
  }
1733
2485
  if (result.failed.length > 0) {
1734
- console.log(chalk15.red(`\u2717 Failed to recover ${result.failed.length} agent(s):`));
2486
+ console.log(chalk16.red(`\u2717 Failed to recover ${result.failed.length} agent(s):`));
1735
2487
  for (const agentId2 of result.failed) {
1736
- console.log(` ${chalk15.dim(agentId2)}`);
2488
+ console.log(` ${chalk16.dim(agentId2)}`);
1737
2489
  }
1738
2490
  }
1739
2491
  return;
@@ -1747,12 +2499,12 @@ async function recoverCommand(id, options = {}) {
1747
2499
  }
1748
2500
  spinner.succeed(`Recovered: ${agentId}`);
1749
2501
  console.log("");
1750
- console.log(chalk15.bold("Agent Details:"));
1751
- console.log(` Issue: ${chalk15.cyan(state.issueId)}`);
1752
- console.log(` Workspace: ${chalk15.dim(state.workspace)}`);
2502
+ console.log(chalk16.bold("Agent Details:"));
2503
+ console.log(` Issue: ${chalk16.cyan(state.issueId)}`);
2504
+ console.log(` Workspace: ${chalk16.dim(state.workspace)}`);
1753
2505
  console.log(` Model: ${state.model}`);
1754
2506
  console.log("");
1755
- console.log(chalk15.dim("Commands:"));
2507
+ console.log(chalk16.dim("Commands:"));
1756
2508
  console.log(` Attach: tmux attach -t ${state.id}`);
1757
2509
  console.log(` Message: pan work tell ${state.issueId} "your message"`);
1758
2510
  } catch (error) {
@@ -1762,7 +2514,7 @@ async function recoverCommand(id, options = {}) {
1762
2514
  }
1763
2515
 
1764
2516
  // src/cli/commands/work/cv.ts
1765
- import chalk16 from "chalk";
2517
+ import chalk17 from "chalk";
1766
2518
  async function cvCommand(agentId, options = {}) {
1767
2519
  if (options.rankings || !agentId) {
1768
2520
  const rankings = getAgentRankings();
@@ -1771,15 +2523,15 @@ async function cvCommand(agentId, options = {}) {
1771
2523
  return;
1772
2524
  }
1773
2525
  if (rankings.length === 0) {
1774
- console.log(chalk16.dim("No agent work history yet."));
1775
- console.log(chalk16.dim("CVs are created as agents complete work."));
2526
+ console.log(chalk17.dim("No agent work history yet."));
2527
+ console.log(chalk17.dim("CVs are created as agents complete work."));
1776
2528
  return;
1777
2529
  }
1778
- console.log(chalk16.bold("\nAgent Rankings\n"));
2530
+ console.log(chalk17.bold("\nAgent Rankings\n"));
1779
2531
  console.log(
1780
2532
  `${"Agent".padEnd(25)} ${"Success".padStart(8)} ${"Total".padStart(6)} ${"Avg Time".padStart(10)}`
1781
2533
  );
1782
- console.log(chalk16.dim("\u2500".repeat(52)));
2534
+ console.log(chalk17.dim("\u2500".repeat(52)));
1783
2535
  for (let i = 0; i < rankings.length; i++) {
1784
2536
  const r = rankings[i];
1785
2537
  const medal = i === 0 ? "\u{1F947}" : i === 1 ? "\u{1F948}" : i === 2 ? "\u{1F949}" : " ";
@@ -1790,7 +2542,7 @@ async function cvCommand(agentId, options = {}) {
1790
2542
  );
1791
2543
  }
1792
2544
  console.log("");
1793
- console.log(chalk16.dim(`Use: pan work cv <agent-id> for details`));
2545
+ console.log(chalk17.dim(`Use: pan work cv <agent-id> for details`));
1794
2546
  return;
1795
2547
  }
1796
2548
  const normalizedId = agentId.startsWith("agent-") ? agentId : `agent-${agentId.toLowerCase()}`;
@@ -1804,13 +2556,13 @@ async function cvCommand(agentId, options = {}) {
1804
2556
  }
1805
2557
 
1806
2558
  // src/cli/commands/work/context.ts
1807
- import chalk17 from "chalk";
2559
+ import chalk18 from "chalk";
1808
2560
 
1809
2561
  // src/lib/context.ts
1810
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync7, appendFileSync, readdirSync as readdirSync6 } from "fs";
1811
- import { join as join10 } from "path";
2562
+ import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7, appendFileSync, readdirSync as readdirSync8 } from "fs";
2563
+ import { join as join11 } from "path";
1812
2564
  function getStateFile(agentId) {
1813
- return join10(AGENTS_DIR, agentId, "STATE.md");
2565
+ return join11(AGENTS_DIR, agentId, "STATE.md");
1814
2566
  }
1815
2567
  function readAgentState(agentId) {
1816
2568
  const stateFile = getStateFile(agentId);
@@ -1823,8 +2575,8 @@ function readAgentState(agentId) {
1823
2575
  }
1824
2576
  }
1825
2577
  function writeAgentState(agentId, state) {
1826
- const dir = join10(AGENTS_DIR, agentId);
1827
- mkdirSync5(dir, { recursive: true });
2578
+ const dir = join11(AGENTS_DIR, agentId);
2579
+ mkdirSync7(dir, { recursive: true });
1828
2580
  const content = generateStateMd(state);
1829
2581
  writeFileSync7(getStateFile(agentId), content);
1830
2582
  }
@@ -1900,11 +2652,11 @@ function parseStateMd(content) {
1900
2652
  return state;
1901
2653
  }
1902
2654
  function getSummaryFile(agentId) {
1903
- return join10(AGENTS_DIR, agentId, "SUMMARY.md");
2655
+ return join11(AGENTS_DIR, agentId, "SUMMARY.md");
1904
2656
  }
1905
2657
  function appendSummary(agentId, summary) {
1906
- const dir = join10(AGENTS_DIR, agentId);
1907
- mkdirSync5(dir, { recursive: true });
2658
+ const dir = join11(AGENTS_DIR, agentId);
2659
+ mkdirSync7(dir, { recursive: true });
1908
2660
  const summaryFile = getSummaryFile(agentId);
1909
2661
  const content = generateSummaryEntry(summary);
1910
2662
  if (existsSync11(summaryFile)) {
@@ -1948,14 +2700,14 @@ function generateSummaryEntry(summary) {
1948
2700
  return lines.join("\n");
1949
2701
  }
1950
2702
  function getHistoryDir(agentId) {
1951
- return join10(AGENTS_DIR, agentId, "history");
2703
+ return join11(AGENTS_DIR, agentId, "history");
1952
2704
  }
1953
2705
  function logHistory(agentId, action, details) {
1954
2706
  const historyDir = getHistoryDir(agentId);
1955
- mkdirSync5(historyDir, { recursive: true });
2707
+ mkdirSync7(historyDir, { recursive: true });
1956
2708
  const date = /* @__PURE__ */ new Date();
1957
2709
  const dateStr = date.toISOString().split("T")[0];
1958
- const historyFile = join10(historyDir, `${dateStr}.log`);
2710
+ const historyFile = join11(historyDir, `${dateStr}.log`);
1959
2711
  const timestamp = date.toISOString();
1960
2712
  const detailsStr = details ? ` ${JSON.stringify(details)}` : "";
1961
2713
  const logLine = `[${timestamp}] ${action}${detailsStr}
@@ -1967,10 +2719,10 @@ function searchHistory(agentId, pattern) {
1967
2719
  if (!existsSync11(historyDir)) return [];
1968
2720
  const results = [];
1969
2721
  const regex = new RegExp(pattern, "i");
1970
- const files = readdirSync6(historyDir).filter((f) => f.endsWith(".log"));
2722
+ const files = readdirSync8(historyDir).filter((f) => f.endsWith(".log"));
1971
2723
  files.sort().reverse();
1972
2724
  for (const file of files) {
1973
- const content = readFileSync10(join10(historyDir, file), "utf-8");
2725
+ const content = readFileSync10(join11(historyDir, file), "utf-8");
1974
2726
  const lines = content.split("\n");
1975
2727
  for (const line of lines) {
1976
2728
  if (regex.test(line)) {
@@ -1984,11 +2736,11 @@ function getRecentHistory(agentId, limit = 20) {
1984
2736
  const historyDir = getHistoryDir(agentId);
1985
2737
  if (!existsSync11(historyDir)) return [];
1986
2738
  const results = [];
1987
- const files = readdirSync6(historyDir).filter((f) => f.endsWith(".log"));
2739
+ const files = readdirSync8(historyDir).filter((f) => f.endsWith(".log"));
1988
2740
  files.sort().reverse();
1989
2741
  for (const file of files) {
1990
2742
  if (results.length >= limit) break;
1991
- const content = readFileSync10(join10(historyDir, file), "utf-8");
2743
+ const content = readFileSync10(join11(historyDir, file), "utf-8");
1992
2744
  const lines = content.split("\n").filter((l) => l.trim());
1993
2745
  for (const line of lines.reverse()) {
1994
2746
  if (results.length >= limit) break;
@@ -2001,18 +2753,18 @@ function estimateTokens(text) {
2001
2753
  return Math.ceil(text.length / 4);
2002
2754
  }
2003
2755
  function getMaterializedDir(agentId) {
2004
- return join10(AGENTS_DIR, agentId, "materialized");
2756
+ return join11(AGENTS_DIR, agentId, "materialized");
2005
2757
  }
2006
2758
  function listMaterialized(agentId) {
2007
2759
  const dir = getMaterializedDir(agentId);
2008
2760
  if (!existsSync11(dir)) return [];
2009
- return readdirSync6(dir).filter((f) => f.endsWith(".md")).map((f) => {
2761
+ return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => {
2010
2762
  const match = f.match(/^(.+)-(\d+)\.md$/);
2011
2763
  if (!match) return null;
2012
2764
  return {
2013
2765
  tool: match[1],
2014
2766
  timestamp: parseInt(match[2], 10),
2015
- file: join10(dir, f)
2767
+ file: join11(dir, f)
2016
2768
  };
2017
2769
  }).filter(Boolean);
2018
2770
  }
@@ -2033,34 +2785,34 @@ async function contextCommand(action, arg1, arg2, options = {}) {
2033
2785
  return;
2034
2786
  }
2035
2787
  if (!state) {
2036
- console.log(chalk17.dim("No state found for agent."));
2037
- console.log(chalk17.dim("Initialize with: pan work context init <agent-id> <issue-id>"));
2788
+ console.log(chalk18.dim("No state found for agent."));
2789
+ console.log(chalk18.dim("Initialize with: pan work context init <agent-id> <issue-id>"));
2038
2790
  return;
2039
2791
  }
2040
- console.log(chalk17.bold(`
2792
+ console.log(chalk18.bold(`
2041
2793
  Agent State: ${state.issueId}
2042
2794
  `));
2043
- console.log(`Status: ${chalk17.cyan(state.status)}`);
2044
- console.log(`Last Activity: ${chalk17.dim(state.lastActivity)}`);
2795
+ console.log(`Status: ${chalk18.cyan(state.status)}`);
2796
+ console.log(`Last Activity: ${chalk18.dim(state.lastActivity)}`);
2045
2797
  if (state.lastCheckpoint) {
2046
2798
  console.log("");
2047
- console.log(chalk17.bold("Session Continuity:"));
2048
- console.log(` Checkpoint: ${chalk17.yellow(state.lastCheckpoint)}`);
2799
+ console.log(chalk18.bold("Session Continuity:"));
2800
+ console.log(` Checkpoint: ${chalk18.yellow(state.lastCheckpoint)}`);
2049
2801
  if (state.resumePoint) {
2050
- console.log(` Resume: ${chalk17.green(state.resumePoint)}`);
2802
+ console.log(` Resume: ${chalk18.green(state.resumePoint)}`);
2051
2803
  }
2052
2804
  }
2053
2805
  if (state.contextRefs.workspace || state.contextRefs.prd) {
2054
2806
  console.log("");
2055
- console.log(chalk17.bold("Context References:"));
2807
+ console.log(chalk18.bold("Context References:"));
2056
2808
  if (state.contextRefs.workspace) {
2057
- console.log(` Workspace: ${chalk17.dim(state.contextRefs.workspace)}`);
2809
+ console.log(` Workspace: ${chalk18.dim(state.contextRefs.workspace)}`);
2058
2810
  }
2059
2811
  if (state.contextRefs.prd) {
2060
- console.log(` PRD: ${chalk17.dim(state.contextRefs.prd)}`);
2812
+ console.log(` PRD: ${chalk18.dim(state.contextRefs.prd)}`);
2061
2813
  }
2062
2814
  if (state.contextRefs.beads) {
2063
- console.log(` Beads: ${chalk17.dim(state.contextRefs.beads)}`);
2815
+ console.log(` Beads: ${chalk18.dim(state.contextRefs.beads)}`);
2064
2816
  }
2065
2817
  }
2066
2818
  console.log("");
@@ -2077,22 +2829,22 @@ Agent State: ${state.issueId}
2077
2829
  };
2078
2830
  writeAgentState(targetAgent, state);
2079
2831
  logHistory(targetAgent, "context:init", { issueId });
2080
- console.log(chalk17.green(`\u2713 Initialized state for ${targetAgent}`));
2832
+ console.log(chalk18.green(`\u2713 Initialized state for ${targetAgent}`));
2081
2833
  break;
2082
2834
  }
2083
2835
  case "checkpoint": {
2084
2836
  const checkpoint = arg1;
2085
2837
  const resume = arg2;
2086
2838
  if (!checkpoint) {
2087
- console.log(chalk17.red("Checkpoint message required"));
2088
- console.log(chalk17.dim('Usage: pan work context checkpoint "message" ["resume point"]'));
2839
+ console.log(chalk18.red("Checkpoint message required"));
2840
+ console.log(chalk18.dim('Usage: pan work context checkpoint "message" ["resume point"]'));
2089
2841
  return;
2090
2842
  }
2091
2843
  updateCheckpoint(agentId, checkpoint, resume);
2092
2844
  logHistory(agentId, "context:checkpoint", { checkpoint, resume });
2093
- console.log(chalk17.green(`\u2713 Checkpoint saved: "${checkpoint}"`));
2845
+ console.log(chalk18.green(`\u2713 Checkpoint saved: "${checkpoint}"`));
2094
2846
  if (resume) {
2095
- console.log(chalk17.dim(` Resume point: "${resume}"`));
2847
+ console.log(chalk18.dim(` Resume point: "${resume}"`));
2096
2848
  }
2097
2849
  break;
2098
2850
  }
@@ -2105,7 +2857,7 @@ Agent State: ${state.issueId}
2105
2857
  };
2106
2858
  appendSummary(agentId, summary);
2107
2859
  logHistory(agentId, "context:summary", { title });
2108
- console.log(chalk17.green(`\u2713 Summary added: "${title}"`));
2860
+ console.log(chalk18.green(`\u2713 Summary added: "${title}"`));
2109
2861
  break;
2110
2862
  }
2111
2863
  case "history": {
@@ -2113,10 +2865,10 @@ Agent State: ${state.issueId}
2113
2865
  if (pattern) {
2114
2866
  const results = searchHistory(agentId, pattern);
2115
2867
  if (results.length === 0) {
2116
- console.log(chalk17.dim("No matches found."));
2868
+ console.log(chalk18.dim("No matches found."));
2117
2869
  return;
2118
2870
  }
2119
- console.log(chalk17.bold(`
2871
+ console.log(chalk18.bold(`
2120
2872
  History matches for "${pattern}":
2121
2873
  `));
2122
2874
  for (const line of results.slice(0, 50)) {
@@ -2125,10 +2877,10 @@ History matches for "${pattern}":
2125
2877
  } else {
2126
2878
  const recent = getRecentHistory(agentId, 20);
2127
2879
  if (recent.length === 0) {
2128
- console.log(chalk17.dim("No history yet."));
2880
+ console.log(chalk18.dim("No history yet."));
2129
2881
  return;
2130
2882
  }
2131
- console.log(chalk17.bold("\nRecent History:\n"));
2883
+ console.log(chalk18.bold("\nRecent History:\n"));
2132
2884
  for (const line of recent) {
2133
2885
  console.log(line);
2134
2886
  }
@@ -2147,14 +2899,14 @@ History matches for "${pattern}":
2147
2899
  }
2148
2900
  const outputs = listMaterialized(agentId);
2149
2901
  if (outputs.length === 0) {
2150
- console.log(chalk17.dim("No materialized outputs."));
2902
+ console.log(chalk18.dim("No materialized outputs."));
2151
2903
  return;
2152
2904
  }
2153
- console.log(chalk17.bold("\nMaterialized Outputs:\n"));
2905
+ console.log(chalk18.bold("\nMaterialized Outputs:\n"));
2154
2906
  for (const out of outputs) {
2155
2907
  const date = new Date(out.timestamp).toLocaleString();
2156
- console.log(` ${chalk17.cyan(out.tool)} ${chalk17.dim(date)}`);
2157
- console.log(` ${chalk17.dim(out.file)}`);
2908
+ console.log(` ${chalk18.cyan(out.tool)} ${chalk18.dim(date)}`);
2909
+ console.log(` ${chalk18.dim(out.file)}`);
2158
2910
  }
2159
2911
  console.log("");
2160
2912
  break;
@@ -2162,7 +2914,7 @@ History matches for "${pattern}":
2162
2914
  case "tokens": {
2163
2915
  const target = arg1;
2164
2916
  if (!target) {
2165
- console.log(chalk17.dim("Usage: pan work context tokens <file-or-text>"));
2917
+ console.log(chalk18.dim("Usage: pan work context tokens <file-or-text>"));
2166
2918
  return;
2167
2919
  }
2168
2920
  let text = target;
@@ -2170,36 +2922,36 @@ History matches for "${pattern}":
2170
2922
  text = readFileSync11(target, "utf-8");
2171
2923
  }
2172
2924
  const tokens = estimateTokens(text);
2173
- console.log(`Estimated tokens: ${chalk17.cyan(tokens.toLocaleString())}`);
2925
+ console.log(`Estimated tokens: ${chalk18.cyan(tokens.toLocaleString())}`);
2174
2926
  break;
2175
2927
  }
2176
2928
  default:
2177
- console.log(chalk17.bold("Context Commands:"));
2929
+ console.log(chalk18.bold("Context Commands:"));
2178
2930
  console.log("");
2179
- console.log(` ${chalk17.cyan("pan work context state [agent-id]")} - Show current state`);
2180
- console.log(` ${chalk17.cyan("pan work context init <agent> <issue>")} - Initialize state`);
2181
- console.log(` ${chalk17.cyan('pan work context checkpoint "msg"')} - Save checkpoint`);
2182
- console.log(` ${chalk17.cyan("pan work context summary [title]")} - Add work summary`);
2183
- console.log(` ${chalk17.cyan("pan work context history [pattern]")} - Search history`);
2184
- console.log(` ${chalk17.cyan("pan work context materialize [file]")} - List/read outputs`);
2185
- console.log(` ${chalk17.cyan("pan work context tokens <file>")} - Estimate token count`);
2931
+ console.log(` ${chalk18.cyan("pan work context state [agent-id]")} - Show current state`);
2932
+ console.log(` ${chalk18.cyan("pan work context init <agent> <issue>")} - Initialize state`);
2933
+ console.log(` ${chalk18.cyan('pan work context checkpoint "msg"')} - Save checkpoint`);
2934
+ console.log(` ${chalk18.cyan("pan work context summary [title]")} - Add work summary`);
2935
+ console.log(` ${chalk18.cyan("pan work context history [pattern]")} - Search history`);
2936
+ console.log(` ${chalk18.cyan("pan work context materialize [file]")} - List/read outputs`);
2937
+ console.log(` ${chalk18.cyan("pan work context tokens <file>")} - Estimate token count`);
2186
2938
  console.log("");
2187
2939
  }
2188
2940
  }
2189
2941
 
2190
2942
  // src/cli/commands/work/health.ts
2191
- import chalk18 from "chalk";
2943
+ import chalk19 from "chalk";
2192
2944
 
2193
2945
  // src/lib/health.ts
2194
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
2195
- import { join as join11 } from "path";
2196
- import { execSync as execSync4 } from "child_process";
2946
+ import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
2947
+ import { join as join12 } from "path";
2948
+ import { execSync as execSync5 } from "child_process";
2197
2949
  var DEFAULT_PING_TIMEOUT_MS = 30 * 1e3;
2198
2950
  var DEFAULT_CONSECUTIVE_FAILURES = 3;
2199
2951
  var DEFAULT_COOLDOWN_MS = 5 * 60 * 1e3;
2200
2952
  var DEFAULT_CHECK_INTERVAL_MS = 30 * 1e3;
2201
2953
  function getHealthFile(agentId) {
2202
- return join11(AGENTS_DIR, agentId, "health.json");
2954
+ return join12(AGENTS_DIR, agentId, "health.json");
2203
2955
  }
2204
2956
  function getAgentHealth(agentId) {
2205
2957
  const healthFile = getHealthFile(agentId);
@@ -2221,13 +2973,13 @@ function getAgentHealth(agentId) {
2221
2973
  return defaultHealth;
2222
2974
  }
2223
2975
  function saveAgentHealth(health) {
2224
- const dir = join11(AGENTS_DIR, health.agentId);
2225
- mkdirSync6(dir, { recursive: true });
2976
+ const dir = join12(AGENTS_DIR, health.agentId);
2977
+ mkdirSync8(dir, { recursive: true });
2226
2978
  writeFileSync8(getHealthFile(health.agentId), JSON.stringify(health, null, 2));
2227
2979
  }
2228
2980
  function isAgentAlive(agentId) {
2229
2981
  try {
2230
- execSync4(`tmux has-session -t "${agentId}" 2>/dev/null`, { encoding: "utf-8" });
2982
+ execSync5(`tmux has-session -t "${agentId}" 2>/dev/null`, { encoding: "utf-8" });
2231
2983
  return true;
2232
2984
  } catch {
2233
2985
  return false;
@@ -2337,7 +3089,7 @@ async function runHealthCheck(config = {
2337
3089
  };
2338
3090
  let sessions = [];
2339
3091
  try {
2340
- const output = execSync4(
3092
+ const output = execSync5(
2341
3093
  'tmux list-sessions -F "#{session_name}" 2>/dev/null || true',
2342
3094
  { encoding: "utf-8" }
2343
3095
  );
@@ -2345,8 +3097,8 @@ async function runHealthCheck(config = {
2345
3097
  } catch {
2346
3098
  }
2347
3099
  if (existsSync13(AGENTS_DIR)) {
2348
- const { readdirSync: readdirSync10 } = await import("fs");
2349
- const dirs = readdirSync10(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("agent-")).map((d) => d.name);
3100
+ const { readdirSync: readdirSync13 } = await import("fs");
3101
+ const dirs = readdirSync13(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("agent-")).map((d) => d.name);
2350
3102
  for (const dir of dirs) {
2351
3103
  if (!sessions.includes(dir)) {
2352
3104
  sessions.push(dir);
@@ -2444,20 +3196,20 @@ async function healthCommand(action, arg, options = {}) {
2444
3196
  };
2445
3197
  switch (action) {
2446
3198
  case "check": {
2447
- console.log(chalk18.bold("Running health check...\n"));
3199
+ console.log(chalk19.bold("Running health check...\n"));
2448
3200
  const results = await runHealthCheck(config);
2449
3201
  if (options.json) {
2450
3202
  console.log(JSON.stringify(results, null, 2));
2451
3203
  return;
2452
3204
  }
2453
3205
  console.log(`Checked: ${results.checked} agents`);
2454
- console.log(` ${chalk18.green("\u2705 Healthy:")} ${results.healthy}`);
2455
- console.log(` ${chalk18.yellow("\u26A0\uFE0F Warning:")} ${results.warning}`);
2456
- console.log(` ${chalk18.hex("#FFA500")("\u{1F7E0} Stuck:")} ${results.stuck}`);
2457
- console.log(` ${chalk18.red("\u274C Dead:")} ${results.dead}`);
3206
+ console.log(` ${chalk19.green("\u2705 Healthy:")} ${results.healthy}`);
3207
+ console.log(` ${chalk19.yellow("\u26A0\uFE0F Warning:")} ${results.warning}`);
3208
+ console.log(` ${chalk19.hex("#FFA500")("\u{1F7E0} Stuck:")} ${results.stuck}`);
3209
+ console.log(` ${chalk19.red("\u274C Dead:")} ${results.dead}`);
2458
3210
  if (results.recovered.length > 0) {
2459
3211
  console.log("");
2460
- console.log(chalk18.green("Recovered agents:"));
3212
+ console.log(chalk19.green("Recovered agents:"));
2461
3213
  for (const agentId of results.recovered) {
2462
3214
  console.log(` - ${agentId}`);
2463
3215
  }
@@ -2467,7 +3219,7 @@ async function healthCommand(action, arg, options = {}) {
2467
3219
  case "status": {
2468
3220
  const agents = listRunningAgents();
2469
3221
  if (agents.length === 0) {
2470
- console.log(chalk18.dim("No agents found."));
3222
+ console.log(chalk19.dim("No agents found."));
2471
3223
  return;
2472
3224
  }
2473
3225
  const healthData = agents.map((agent) => {
@@ -2478,7 +3230,7 @@ async function healthCommand(action, arg, options = {}) {
2478
3230
  console.log(JSON.stringify(healthData.map((d) => d.health), null, 2));
2479
3231
  return;
2480
3232
  }
2481
- console.log(chalk18.bold("Agent Health Status:\n"));
3233
+ console.log(chalk19.bold("Agent Health Status:\n"));
2482
3234
  for (const { health } of healthData) {
2483
3235
  console.log(formatHealthStatus(health));
2484
3236
  console.log("");
@@ -2487,12 +3239,12 @@ async function healthCommand(action, arg, options = {}) {
2487
3239
  }
2488
3240
  case "ping": {
2489
3241
  if (!arg) {
2490
- console.log(chalk18.red("Agent ID required"));
2491
- console.log(chalk18.dim("Usage: pan work health ping <agent-id>"));
3242
+ console.log(chalk19.red("Agent ID required"));
3243
+ console.log(chalk19.dim("Usage: pan work health ping <agent-id>"));
2492
3244
  return;
2493
3245
  }
2494
3246
  const agentId = arg.startsWith("agent-") ? arg : `agent-${arg.toLowerCase()}`;
2495
- console.log(chalk18.dim(`Pinging ${agentId}...`));
3247
+ console.log(chalk19.dim(`Pinging ${agentId}...`));
2496
3248
  const health = pingAgent(agentId, config);
2497
3249
  if (options.json) {
2498
3250
  console.log(JSON.stringify(health, null, 2));
@@ -2504,12 +3256,12 @@ async function healthCommand(action, arg, options = {}) {
2504
3256
  }
2505
3257
  case "recover": {
2506
3258
  if (!arg) {
2507
- console.log(chalk18.red("Agent ID required"));
2508
- console.log(chalk18.dim("Usage: pan work health recover <agent-id>"));
3259
+ console.log(chalk19.red("Agent ID required"));
3260
+ console.log(chalk19.dim("Usage: pan work health recover <agent-id>"));
2509
3261
  return;
2510
3262
  }
2511
3263
  const agentId = arg.startsWith("agent-") ? arg : `agent-${arg.toLowerCase()}`;
2512
- console.log(chalk18.dim(`Attempting recovery of ${agentId}...`));
3264
+ console.log(chalk19.dim(`Attempting recovery of ${agentId}...`));
2513
3265
  const forceConfig = { ...config, consecutiveFailures: 0 };
2514
3266
  const result = await handleStuckAgent(agentId, forceConfig);
2515
3267
  if (options.json) {
@@ -2517,21 +3269,21 @@ async function healthCommand(action, arg, options = {}) {
2517
3269
  return;
2518
3270
  }
2519
3271
  if (result.action === "recovered") {
2520
- console.log(chalk18.green(`\u2705 ${result.reason}`));
3272
+ console.log(chalk19.green(`\u2705 ${result.reason}`));
2521
3273
  } else if (result.action === "cooldown") {
2522
- console.log(chalk18.yellow(`\u26A0\uFE0F ${result.reason}`));
3274
+ console.log(chalk19.yellow(`\u26A0\uFE0F ${result.reason}`));
2523
3275
  } else {
2524
- console.log(chalk18.dim(result.reason));
3276
+ console.log(chalk19.dim(result.reason));
2525
3277
  }
2526
3278
  break;
2527
3279
  }
2528
3280
  case "daemon": {
2529
- console.log(chalk18.bold("Starting Panopticon Health Daemon"));
2530
- console.log(chalk18.dim(`Check interval: ${config.checkIntervalMs / 1e3}s`));
2531
- console.log(chalk18.dim(`Failure threshold: ${config.consecutiveFailures}`));
2532
- console.log(chalk18.dim(`Cooldown: ${config.cooldownMs / (1e3 * 60)}m`));
3281
+ console.log(chalk19.bold("Starting Panopticon Health Daemon"));
3282
+ console.log(chalk19.dim(`Check interval: ${config.checkIntervalMs / 1e3}s`));
3283
+ console.log(chalk19.dim(`Failure threshold: ${config.consecutiveFailures}`));
3284
+ console.log(chalk19.dim(`Cooldown: ${config.cooldownMs / (1e3 * 60)}m`));
2533
3285
  console.log("");
2534
- console.log(chalk18.dim("Press Ctrl+C to stop...\n"));
3286
+ console.log(chalk19.dim("Press Ctrl+C to stop...\n"));
2535
3287
  const stop = startHealthDaemon(config, (results) => {
2536
3288
  const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
2537
3289
  const statusParts = [
@@ -2542,12 +3294,12 @@ async function healthCommand(action, arg, options = {}) {
2542
3294
  `\u274C${results.dead}`
2543
3295
  ];
2544
3296
  if (results.recovered.length > 0) {
2545
- statusParts.push(chalk18.green(`+${results.recovered.length} recovered`));
3297
+ statusParts.push(chalk19.green(`+${results.recovered.length} recovered`));
2546
3298
  }
2547
3299
  console.log(statusParts.join(" "));
2548
3300
  });
2549
3301
  process.on("SIGINT", () => {
2550
- console.log("\n" + chalk18.dim("Stopping health daemon..."));
3302
+ console.log("\n" + chalk19.dim("Stopping health daemon..."));
2551
3303
  stop();
2552
3304
  process.exit(0);
2553
3305
  });
@@ -2556,19 +3308,19 @@ async function healthCommand(action, arg, options = {}) {
2556
3308
  break;
2557
3309
  }
2558
3310
  default:
2559
- console.log(chalk18.bold("Health Monitoring Commands:"));
3311
+ console.log(chalk19.bold("Health Monitoring Commands:"));
2560
3312
  console.log("");
2561
- console.log(` ${chalk18.cyan("pan work health check")} - Run single health check`);
2562
- console.log(` ${chalk18.cyan("pan work health status")} - Show all agent health`);
2563
- console.log(` ${chalk18.cyan("pan work health ping <id>")} - Ping specific agent`);
2564
- console.log(` ${chalk18.cyan("pan work health recover <id>")} - Force recover agent`);
2565
- console.log(` ${chalk18.cyan("pan work health daemon")} - Start health daemon`);
3313
+ console.log(` ${chalk19.cyan("pan work health check")} - Run single health check`);
3314
+ console.log(` ${chalk19.cyan("pan work health status")} - Show all agent health`);
3315
+ console.log(` ${chalk19.cyan("pan work health ping <id>")} - Ping specific agent`);
3316
+ console.log(` ${chalk19.cyan("pan work health recover <id>")} - Force recover agent`);
3317
+ console.log(` ${chalk19.cyan("pan work health daemon")} - Start health daemon`);
2566
3318
  console.log("");
2567
- console.log(chalk18.bold("Options:"));
2568
- console.log(` ${chalk18.dim("--json")} Output as JSON`);
2569
- console.log(` ${chalk18.dim("--interval <sec>")} Check interval for daemon (default: 30)`);
3319
+ console.log(chalk19.bold("Options:"));
3320
+ console.log(` ${chalk19.dim("--json")} Output as JSON`);
3321
+ console.log(` ${chalk19.dim("--interval <sec>")} Check interval for daemon (default: 30)`);
2570
3322
  console.log("");
2571
- console.log(chalk18.bold("Deacon Pattern Defaults:"));
3323
+ console.log(chalk19.bold("Deacon Pattern Defaults:"));
2572
3324
  console.log(` Ping timeout: ${DEFAULT_PING_TIMEOUT_MS / 1e3}s`);
2573
3325
  console.log(` Consecutive failures: ${DEFAULT_CONSECUTIVE_FAILURES}`);
2574
3326
  console.log(` Cooldown after kill: ${DEFAULT_COOLDOWN_MS / (1e3 * 60)}m`);
@@ -2585,8 +3337,8 @@ function registerWorkCommands(program2) {
2585
3337
  work.command("kill <id>").description("Kill an agent").option("--force", "Kill without confirmation").action(killCommand);
2586
3338
  work.command("pending").description("Show completed work awaiting review").action(pendingCommand);
2587
3339
  work.command("approve <id>").description("Approve agent work, merge MR, update Linear").option("--no-merge", "Skip MR merge").option("--no-linear", "Skip Linear status update").action(approveCommand);
2588
- work.command("plan <id>").description("Create execution plan before spawning").option("-o, --output <path>", "Output file path").option("--json", "Output as JSON").action(planCommand);
2589
- work.command("list").description("List Linear issues").option("--all", "Include completed issues").option("--mine", "Show only my assigned issues").option("--json", "Output as JSON").action(listCommand);
3340
+ work.command("plan <id>").description("Create execution plan before spawning").option("-o, --output <path>", "Output file path").option("--json", "Output as JSON").option("--skip-discovery", "Skip interactive discovery phase").option("--force", "Force planning even for simple issues").action(planCommand);
3341
+ work.command("list").description("List issues from configured trackers").option("--all", "Include closed issues").option("--mine", "Show only my assigned issues").option("--json", "Output as JSON").option("--tracker <type>", "Query specific tracker (linear/github/gitlab)").option("--all-trackers", "Query all configured trackers").action(listCommand);
2590
3342
  work.command("triage [id]").description("Triage secondary tracker issues").option("--create", "Create primary issue from secondary").option("--dismiss <reason>", "Dismiss from triage").action(triageCommand);
2591
3343
  work.command("hook [action] [idOrMessage...]").description("GUPP hooks: check, push, pop, clear, mail, gupp").option("--json", "Output as JSON").action((action, idOrMessage, options) => {
2592
3344
  hookCommand(action || "help", idOrMessage?.join(" "), options);
@@ -2605,17 +3357,17 @@ function registerWorkCommands(program2) {
2605
3357
  }
2606
3358
 
2607
3359
  // src/cli/commands/workspace.ts
2608
- import chalk19 from "chalk";
3360
+ import chalk20 from "chalk";
2609
3361
  import ora10 from "ora";
2610
- import { existsSync as existsSync16, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
2611
- import { join as join14, basename as basename2 } from "path";
3362
+ import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9 } from "fs";
3363
+ import { join as join15, basename as basename2 } from "path";
2612
3364
 
2613
3365
  // src/lib/worktree.ts
2614
- import { execSync as execSync5 } from "child_process";
2615
- import { mkdirSync as mkdirSync7 } from "fs";
2616
- import { dirname } from "path";
3366
+ import { execSync as execSync6 } from "child_process";
3367
+ import { mkdirSync as mkdirSync9 } from "fs";
3368
+ import { dirname as dirname4 } from "path";
2617
3369
  function listWorktrees(repoPath) {
2618
- const output = execSync5("git worktree list --porcelain", {
3370
+ const output = execSync6("git worktree list --porcelain", {
2619
3371
  cwd: repoPath,
2620
3372
  encoding: "utf8"
2621
3373
  });
@@ -2637,32 +3389,32 @@ function listWorktrees(repoPath) {
2637
3389
  return worktrees;
2638
3390
  }
2639
3391
  function createWorktree(repoPath, targetPath, branchName) {
2640
- mkdirSync7(dirname(targetPath), { recursive: true });
3392
+ mkdirSync9(dirname4(targetPath), { recursive: true });
2641
3393
  try {
2642
- execSync5(`git show-ref --verify --quiet refs/heads/${branchName}`, {
3394
+ execSync6(`git show-ref --verify --quiet refs/heads/${branchName}`, {
2643
3395
  cwd: repoPath
2644
3396
  });
2645
- execSync5(`git worktree add "${targetPath}" "${branchName}"`, {
3397
+ execSync6(`git worktree add "${targetPath}" "${branchName}"`, {
2646
3398
  cwd: repoPath,
2647
3399
  stdio: "pipe"
2648
3400
  });
2649
3401
  } catch {
2650
- execSync5(`git worktree add -b "${branchName}" "${targetPath}"`, {
3402
+ execSync6(`git worktree add -b "${branchName}" "${targetPath}"`, {
2651
3403
  cwd: repoPath,
2652
3404
  stdio: "pipe"
2653
3405
  });
2654
3406
  }
2655
3407
  }
2656
3408
  function removeWorktree(repoPath, worktreePath) {
2657
- execSync5(`git worktree remove "${worktreePath}" --force`, {
3409
+ execSync6(`git worktree remove "${worktreePath}" --force`, {
2658
3410
  cwd: repoPath,
2659
3411
  stdio: "pipe"
2660
3412
  });
2661
3413
  }
2662
3414
 
2663
3415
  // src/lib/template.ts
2664
- import { readFileSync as readFileSync13, existsSync as existsSync14, readdirSync as readdirSync7 } from "fs";
2665
- import { join as join12 } from "path";
3416
+ import { readFileSync as readFileSync13, existsSync as existsSync14, readdirSync as readdirSync9 } from "fs";
3417
+ import { join as join13 } from "path";
2666
3418
  function loadTemplate(templatePath) {
2667
3419
  if (!existsSync14(templatePath)) {
2668
3420
  throw new Error(`Template not found: ${templatePath}`);
@@ -2688,17 +3440,17 @@ function generateClaudeMd(projectPath, variables) {
2688
3440
  "warnings.md"
2689
3441
  ];
2690
3442
  for (const section of defaultOrder) {
2691
- const sectionPath = join12(CLAUDE_MD_TEMPLATES, section);
3443
+ const sectionPath = join13(CLAUDE_MD_TEMPLATES, section);
2692
3444
  if (existsSync14(sectionPath)) {
2693
3445
  const content = loadTemplate(sectionPath);
2694
3446
  sections.push(substituteVariables(content, variables));
2695
3447
  }
2696
3448
  }
2697
- const projectSections = join12(projectPath, ".panopticon", "claude-md", "sections");
3449
+ const projectSections = join13(projectPath, ".panopticon", "claude-md", "sections");
2698
3450
  if (existsSync14(projectSections)) {
2699
- const projectFiles = readdirSync7(projectSections).filter((f) => f.endsWith(".md")).sort();
3451
+ const projectFiles = readdirSync9(projectSections).filter((f) => f.endsWith(".md")).sort();
2700
3452
  for (const file of projectFiles) {
2701
- const content = loadTemplate(join12(projectSections, file));
3453
+ const content = loadTemplate(join13(projectSections, file));
2702
3454
  sections.push(substituteVariables(content, variables));
2703
3455
  }
2704
3456
  }
@@ -2720,15 +3472,15 @@ This workspace was created by Panopticon. Use \`bd\` commands to track your work
2720
3472
  // src/lib/skills-merge.ts
2721
3473
  import {
2722
3474
  existsSync as existsSync15,
2723
- readdirSync as readdirSync8,
3475
+ readdirSync as readdirSync10,
2724
3476
  lstatSync,
2725
3477
  readlinkSync,
2726
3478
  symlinkSync,
2727
- mkdirSync as mkdirSync8,
3479
+ mkdirSync as mkdirSync10,
2728
3480
  appendFileSync as appendFileSync2
2729
3481
  } from "fs";
2730
- import { join as join13 } from "path";
2731
- import { execSync as execSync6 } from "child_process";
3482
+ import { join as join14 } from "path";
3483
+ import { execSync as execSync7 } from "child_process";
2732
3484
  function detectContentOrigin(path, repoPath) {
2733
3485
  try {
2734
3486
  const stat = lstatSync(path);
@@ -2739,7 +3491,7 @@ function detectContentOrigin(path, repoPath) {
2739
3491
  }
2740
3492
  }
2741
3493
  try {
2742
- execSync6(`git ls-files --error-unmatch "${path}" 2>/dev/null`, {
3494
+ execSync7(`git ls-files --error-unmatch "${path}" 2>/dev/null`, {
2743
3495
  cwd: repoPath,
2744
3496
  stdio: "pipe"
2745
3497
  });
@@ -2752,21 +3504,21 @@ function detectContentOrigin(path, repoPath) {
2752
3504
  }
2753
3505
  }
2754
3506
  function mergeSkillsIntoWorkspace(workspacePath) {
2755
- const skillsTarget = join13(workspacePath, ".claude", "skills");
3507
+ const skillsTarget = join14(workspacePath, ".claude", "skills");
2756
3508
  const added = [];
2757
3509
  const skipped = [];
2758
- mkdirSync8(skillsTarget, { recursive: true });
3510
+ mkdirSync10(skillsTarget, { recursive: true });
2759
3511
  const existingSkills = /* @__PURE__ */ new Set();
2760
3512
  if (existsSync15(skillsTarget)) {
2761
- for (const item of readdirSync8(skillsTarget)) {
3513
+ for (const item of readdirSync10(skillsTarget)) {
2762
3514
  existingSkills.add(item);
2763
3515
  }
2764
3516
  }
2765
3517
  if (!existsSync15(SKILLS_DIR)) return { added, skipped };
2766
- const panopticonSkills = readdirSync8(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
3518
+ const panopticonSkills = readdirSync10(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
2767
3519
  for (const skill of panopticonSkills) {
2768
- const targetPath = join13(skillsTarget, skill);
2769
- const sourcePath = join13(SKILLS_DIR, skill);
3520
+ const targetPath = join14(skillsTarget, skill);
3521
+ const sourcePath = join14(SKILLS_DIR, skill);
2770
3522
  if (existingSkills.has(skill)) {
2771
3523
  const origin = detectContentOrigin(targetPath, workspacePath);
2772
3524
  if (origin === "git-tracked") {
@@ -2792,7 +3544,7 @@ function mergeSkillsIntoWorkspace(workspacePath) {
2792
3544
  return { added, skipped };
2793
3545
  }
2794
3546
  function updateGitignore(skillsDir, skills) {
2795
- const gitignorePath = join13(skillsDir, ".gitignore");
3547
+ const gitignorePath = join14(skillsDir, ".gitignore");
2796
3548
  const content = `# Panopticon-managed symlinks (not committed)
2797
3549
  ${skills.join("\n")}
2798
3550
  `;
@@ -2816,17 +3568,17 @@ async function createCommand(issueId, options) {
2816
3568
  const branchName = `feature/${normalizedId}`;
2817
3569
  const folderName = `feature-${normalizedId}`;
2818
3570
  const projectRoot = process.cwd();
2819
- const workspacesDir = join14(projectRoot, "workspaces");
2820
- const workspacePath = join14(workspacesDir, folderName);
3571
+ const workspacesDir = join15(projectRoot, "workspaces");
3572
+ const workspacePath = join15(workspacesDir, folderName);
2821
3573
  if (options.dryRun) {
2822
3574
  spinner.info("Dry run mode");
2823
3575
  console.log("");
2824
- console.log(chalk19.bold("Would create:"));
2825
- console.log(` Workspace: ${chalk19.cyan(workspacePath)}`);
2826
- console.log(` Branch: ${chalk19.cyan(branchName)}`);
2827
- console.log(` CLAUDE.md: ${chalk19.dim(join14(workspacePath, "CLAUDE.md"))}`);
3576
+ console.log(chalk20.bold("Would create:"));
3577
+ console.log(` Workspace: ${chalk20.cyan(workspacePath)}`);
3578
+ console.log(` Branch: ${chalk20.cyan(branchName)}`);
3579
+ console.log(` CLAUDE.md: ${chalk20.dim(join15(workspacePath, "CLAUDE.md"))}`);
2828
3580
  if (options.skills !== false) {
2829
- console.log(` Skills: ${chalk19.dim(join14(workspacePath, ".claude", "skills"))}`);
3581
+ console.log(` Skills: ${chalk20.dim(join15(workspacePath, ".claude", "skills"))}`);
2830
3582
  }
2831
3583
  return;
2832
3584
  }
@@ -2834,7 +3586,7 @@ async function createCommand(issueId, options) {
2834
3586
  spinner.fail(`Workspace already exists: ${workspacePath}`);
2835
3587
  process.exit(1);
2836
3588
  }
2837
- if (!existsSync16(join14(projectRoot, ".git"))) {
3589
+ if (!existsSync16(join15(projectRoot, ".git"))) {
2838
3590
  spinner.fail("Not a git repository. Run this from the project root.");
2839
3591
  process.exit(1);
2840
3592
  }
@@ -2850,28 +3602,28 @@ async function createCommand(issueId, options) {
2850
3602
  API_URL: `https://api-${folderName}.localhost:8080`
2851
3603
  };
2852
3604
  const claudeMd = generateClaudeMd(projectRoot, variables);
2853
- writeFileSync9(join14(workspacePath, "CLAUDE.md"), claudeMd);
3605
+ writeFileSync9(join15(workspacePath, "CLAUDE.md"), claudeMd);
2854
3606
  let skillsResult = { added: [], skipped: [] };
2855
3607
  if (options.skills !== false) {
2856
3608
  spinner.text = "Merging skills...";
2857
- mkdirSync9(join14(workspacePath, ".claude", "skills"), { recursive: true });
3609
+ mkdirSync11(join15(workspacePath, ".claude", "skills"), { recursive: true });
2858
3610
  skillsResult = mergeSkillsIntoWorkspace(workspacePath);
2859
3611
  }
2860
3612
  spinner.succeed("Workspace created!");
2861
3613
  console.log("");
2862
- console.log(chalk19.bold("Workspace Details:"));
2863
- console.log(` Path: ${chalk19.cyan(workspacePath)}`);
2864
- console.log(` Branch: ${chalk19.dim(branchName)}`);
3614
+ console.log(chalk20.bold("Workspace Details:"));
3615
+ console.log(` Path: ${chalk20.cyan(workspacePath)}`);
3616
+ console.log(` Branch: ${chalk20.dim(branchName)}`);
2865
3617
  console.log("");
2866
3618
  if (options.skills !== false) {
2867
- console.log(chalk19.bold("Skills:"));
3619
+ console.log(chalk20.bold("Skills:"));
2868
3620
  console.log(` Added: ${skillsResult.added.length} Panopticon skills`);
2869
3621
  if (skillsResult.skipped.length > 0) {
2870
- console.log(` Skipped: ${chalk19.dim(skillsResult.skipped.join(", "))}`);
3622
+ console.log(` Skipped: ${chalk20.dim(skillsResult.skipped.join(", "))}`);
2871
3623
  }
2872
3624
  console.log("");
2873
3625
  }
2874
- console.log(chalk19.dim(`Next: cd ${workspacePath}`));
3626
+ console.log(chalk20.dim(`Next: cd ${workspacePath}`));
2875
3627
  } catch (error) {
2876
3628
  spinner.fail(error.message);
2877
3629
  process.exit(1);
@@ -2879,8 +3631,8 @@ async function createCommand(issueId, options) {
2879
3631
  }
2880
3632
  async function listCommand2(options) {
2881
3633
  const projectRoot = process.cwd();
2882
- if (!existsSync16(join14(projectRoot, ".git"))) {
2883
- console.error(chalk19.red("Not a git repository."));
3634
+ if (!existsSync16(join15(projectRoot, ".git"))) {
3635
+ console.error(chalk20.red("Not a git repository."));
2884
3636
  process.exit(1);
2885
3637
  }
2886
3638
  const worktrees = listWorktrees(projectRoot);
@@ -2892,17 +3644,17 @@ async function listCommand2(options) {
2892
3644
  return;
2893
3645
  }
2894
3646
  if (workspaces.length === 0) {
2895
- console.log(chalk19.dim("No workspaces found."));
2896
- console.log(chalk19.dim("Create one with: pan workspace create <issue-id>"));
3647
+ console.log(chalk20.dim("No workspaces found."));
3648
+ console.log(chalk20.dim("Create one with: pan workspace create <issue-id>"));
2897
3649
  return;
2898
3650
  }
2899
- console.log(chalk19.bold("\nWorkspaces\n"));
3651
+ console.log(chalk20.bold("\nWorkspaces\n"));
2900
3652
  for (const ws of workspaces) {
2901
3653
  const name = basename2(ws.path);
2902
- const status = ws.prunable ? chalk19.yellow(" (prunable)") : "";
2903
- console.log(`${chalk19.cyan(name)}${status}`);
2904
- console.log(` Branch: ${ws.branch || chalk19.dim("(detached)")}`);
2905
- console.log(` Path: ${chalk19.dim(ws.path)}`);
3654
+ const status = ws.prunable ? chalk20.yellow(" (prunable)") : "";
3655
+ console.log(`${chalk20.cyan(name)}${status}`);
3656
+ console.log(` Branch: ${ws.branch || chalk20.dim("(detached)")}`);
3657
+ console.log(` Path: ${chalk20.dim(ws.path)}`);
2906
3658
  console.log("");
2907
3659
  }
2908
3660
  }
@@ -2912,7 +3664,7 @@ async function destroyCommand(issueId, options) {
2912
3664
  const normalizedId = issueId.toLowerCase().replace(/[^a-z0-9-]/g, "-");
2913
3665
  const folderName = `feature-${normalizedId}`;
2914
3666
  const projectRoot = process.cwd();
2915
- const workspacePath = join14(projectRoot, "workspaces", folderName);
3667
+ const workspacePath = join15(projectRoot, "workspaces", folderName);
2916
3668
  if (!existsSync16(workspacePath)) {
2917
3669
  spinner.fail(`Workspace not found: ${workspacePath}`);
2918
3670
  process.exit(1);
@@ -2923,19 +3675,19 @@ async function destroyCommand(issueId, options) {
2923
3675
  } catch (error) {
2924
3676
  spinner.fail(error.message);
2925
3677
  if (!options.force) {
2926
- console.log(chalk19.dim("Tip: Use --force to remove even with uncommitted changes"));
3678
+ console.log(chalk20.dim("Tip: Use --force to remove even with uncommitted changes"));
2927
3679
  }
2928
3680
  process.exit(1);
2929
3681
  }
2930
3682
  }
2931
3683
 
2932
3684
  // src/cli/commands/install.ts
2933
- import chalk20 from "chalk";
3685
+ import chalk21 from "chalk";
2934
3686
  import ora11 from "ora";
2935
- import { execSync as execSync7 } from "child_process";
2936
- import { existsSync as existsSync17, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10, readFileSync as readFileSync14 } from "fs";
2937
- import { join as join15 } from "path";
2938
- import { platform } from "os";
3687
+ import { execSync as execSync8 } from "child_process";
3688
+ import { existsSync as existsSync17, mkdirSync as mkdirSync12, writeFileSync as writeFileSync10, readFileSync as readFileSync14, copyFileSync, readdirSync as readdirSync11, statSync } from "fs";
3689
+ import { join as join16 } from "path";
3690
+ import { homedir as homedir4, platform } from "os";
2939
3691
  function registerInstallCommand(program2) {
2940
3692
  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);
2941
3693
  }
@@ -2953,9 +3705,26 @@ function detectPlatform() {
2953
3705
  }
2954
3706
  return os;
2955
3707
  }
3708
+ function copyDirectoryRecursive(source, dest) {
3709
+ if (!existsSync17(source)) {
3710
+ throw new Error(`Source directory not found: ${source}`);
3711
+ }
3712
+ mkdirSync12(dest, { recursive: true });
3713
+ const entries = readdirSync11(source);
3714
+ for (const entry of entries) {
3715
+ const sourcePath = join16(source, entry);
3716
+ const destPath = join16(dest, entry);
3717
+ const stat = statSync(sourcePath);
3718
+ if (stat.isDirectory()) {
3719
+ copyDirectoryRecursive(sourcePath, destPath);
3720
+ } else {
3721
+ copyFileSync(sourcePath, destPath);
3722
+ }
3723
+ }
3724
+ }
2956
3725
  function checkCommand(cmd) {
2957
3726
  try {
2958
- execSync7(`which ${cmd}`, { stdio: "pipe" });
3727
+ execSync8(`which ${cmd}`, { stdio: "pipe" });
2959
3728
  return true;
2960
3729
  } catch {
2961
3730
  return false;
@@ -2982,7 +3751,7 @@ function checkPrerequisites() {
2982
3751
  let dockerRunning = false;
2983
3752
  if (hasDocker) {
2984
3753
  try {
2985
- execSync7("docker info", { stdio: "pipe" });
3754
+ execSync8("docker info", { stdio: "pipe" });
2986
3755
  dockerRunning = true;
2987
3756
  } catch {
2988
3757
  }
@@ -3014,27 +3783,34 @@ function checkPrerequisites() {
3014
3783
  message: hasBeads ? "installed" : "not found",
3015
3784
  fix: "cargo install beads-cli"
3016
3785
  });
3786
+ const hasTtyd = checkCommand("ttyd") || existsSync17(join16(homedir4(), "bin", "ttyd"));
3787
+ results.push({
3788
+ name: "ttyd",
3789
+ passed: hasTtyd,
3790
+ message: hasTtyd ? "installed" : "not found",
3791
+ fix: "brew install ttyd / Download from https://github.com/tsl0922/ttyd/releases"
3792
+ });
3017
3793
  return {
3018
3794
  results,
3019
- allPassed: results.filter((r) => r.name !== "mkcert").every((r) => r.passed)
3795
+ allPassed: results.filter((r) => r.name !== "mkcert" && r.name !== "ttyd").every((r) => r.passed)
3020
3796
  };
3021
3797
  }
3022
3798
  function printPrereqStatus(prereqs) {
3023
- console.log(chalk20.bold("Prerequisites:\n"));
3799
+ console.log(chalk21.bold("Prerequisites:\n"));
3024
3800
  for (const result of prereqs.results) {
3025
- const icon = result.passed ? chalk20.green("\u2713") : chalk20.red("\u2717");
3026
- const msg = result.passed ? chalk20.dim(result.message) : chalk20.yellow(result.message);
3801
+ const icon = result.passed ? chalk21.green("\u2713") : chalk21.red("\u2717");
3802
+ const msg = result.passed ? chalk21.dim(result.message) : chalk21.yellow(result.message);
3027
3803
  console.log(` ${icon} ${result.name}: ${msg}`);
3028
3804
  if (!result.passed && result.fix) {
3029
- console.log(` ${chalk20.dim("\u2192 " + result.fix)}`);
3805
+ console.log(` ${chalk21.dim("\u2192 " + result.fix)}`);
3030
3806
  }
3031
3807
  }
3032
3808
  console.log("");
3033
3809
  }
3034
3810
  async function installCommand(options) {
3035
- console.log(chalk20.bold("\nPanopticon Installation\n"));
3811
+ console.log(chalk21.bold("\nPanopticon Installation\n"));
3036
3812
  const plat = detectPlatform();
3037
- console.log(`Platform: ${chalk20.cyan(plat)}
3813
+ console.log(`Platform: ${chalk21.cyan(plat)}
3038
3814
  `);
3039
3815
  const prereqs = checkPrerequisites();
3040
3816
  if (options.check) {
@@ -3043,19 +3819,19 @@ async function installCommand(options) {
3043
3819
  }
3044
3820
  printPrereqStatus(prereqs);
3045
3821
  if (!prereqs.allPassed) {
3046
- console.log(chalk20.red("Fix prerequisites above before continuing."));
3047
- console.log(chalk20.dim("Tip: Run with --minimal to skip optional components"));
3822
+ console.log(chalk21.red("Fix prerequisites above before continuing."));
3823
+ console.log(chalk21.dim("Tip: Run with --minimal to skip optional components"));
3048
3824
  process.exit(1);
3049
3825
  }
3050
3826
  const spinner = ora11("Initializing Panopticon directories...").start();
3051
3827
  for (const dir of INIT_DIRS) {
3052
- mkdirSync10(dir, { recursive: true });
3828
+ mkdirSync12(dir, { recursive: true });
3053
3829
  }
3054
3830
  spinner.succeed("Directories initialized");
3055
3831
  if (!options.skipDocker) {
3056
3832
  spinner.start("Creating Docker network...");
3057
3833
  try {
3058
- execSync7("docker network create panopticon 2>/dev/null || true", { stdio: "pipe" });
3834
+ execSync8("docker network create panopticon 2>/dev/null || true", { stdio: "pipe" });
3059
3835
  spinner.succeed("Docker network ready");
3060
3836
  } catch (error) {
3061
3837
  spinner.warn("Docker network setup failed (may already exist)");
@@ -3066,14 +3842,20 @@ async function installCommand(options) {
3066
3842
  if (hasMkcert) {
3067
3843
  spinner.start("Setting up mkcert CA...");
3068
3844
  try {
3069
- execSync7("mkcert -install", { stdio: "pipe" });
3070
- const certsDir = join15(PANOPTICON_HOME, "certs");
3071
- mkdirSync10(certsDir, { recursive: true });
3072
- execSync7(
3073
- `mkcert -cert-file "${join15(certsDir, "localhost.pem")}" -key-file "${join15(certsDir, "localhost-key.pem")}" localhost "*.localhost" 127.0.0.1 ::1`,
3845
+ execSync8("mkcert -install", { stdio: "pipe" });
3846
+ spinner.succeed("mkcert CA installed");
3847
+ spinner.start("Generating wildcard certificates...");
3848
+ const traefikCertFile = join16(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost.pem");
3849
+ const traefikKeyFile = join16(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost-key.pem");
3850
+ execSync8(
3851
+ `mkcert -cert-file "${traefikCertFile}" -key-file "${traefikKeyFile}" "*.pan.localhost" "*.localhost" localhost 127.0.0.1 ::1`,
3074
3852
  { stdio: "pipe" }
3075
3853
  );
3076
- spinner.succeed("mkcert certificates generated");
3854
+ const legacyCertFile = join16(CERTS_DIR, "localhost.pem");
3855
+ const legacyKeyFile = join16(CERTS_DIR, "localhost-key.pem");
3856
+ copyFileSync(traefikCertFile, legacyCertFile);
3857
+ copyFileSync(traefikKeyFile, legacyKeyFile);
3858
+ spinner.succeed("Wildcard certificates generated (*.pan.localhost, *.localhost)");
3077
3859
  } catch (error) {
3078
3860
  spinner.warn("mkcert setup failed (HTTPS may not work)");
3079
3861
  }
@@ -3081,9 +3863,60 @@ async function installCommand(options) {
3081
3863
  spinner.info("Skipping mkcert (not installed)");
3082
3864
  }
3083
3865
  }
3084
- const configFile = join15(PANOPTICON_HOME, "config.toml");
3866
+ const hasTtyd = checkCommand("ttyd") || existsSync17(join16(homedir4(), "bin", "ttyd"));
3867
+ if (!hasTtyd) {
3868
+ spinner.start("Installing ttyd (web terminal)...");
3869
+ try {
3870
+ const binDir = join16(homedir4(), "bin");
3871
+ mkdirSync12(binDir, { recursive: true });
3872
+ const ttydPath = join16(binDir, "ttyd");
3873
+ const plat2 = detectPlatform();
3874
+ let downloadUrl = "";
3875
+ if (plat2 === "darwin") {
3876
+ try {
3877
+ execSync8("brew install ttyd", { stdio: "pipe" });
3878
+ spinner.succeed("ttyd installed via Homebrew");
3879
+ } catch {
3880
+ spinner.warn("ttyd installation failed - install manually: brew install ttyd");
3881
+ }
3882
+ } else {
3883
+ downloadUrl = "https://github.com/tsl0922/ttyd/releases/latest/download/ttyd.x86_64";
3884
+ try {
3885
+ execSync8(`curl -sL "${downloadUrl}" -o "${ttydPath}" && chmod +x "${ttydPath}"`, {
3886
+ stdio: "pipe",
3887
+ timeout: 6e4
3888
+ });
3889
+ spinner.succeed(`ttyd installed to ${ttydPath}`);
3890
+ } catch (error) {
3891
+ spinner.warn("ttyd download failed - install manually from https://github.com/tsl0922/ttyd/releases");
3892
+ }
3893
+ }
3894
+ } catch (error) {
3895
+ spinner.warn("ttyd installation failed (planning sessions will not work)");
3896
+ }
3897
+ } else {
3898
+ spinner.info("ttyd already installed");
3899
+ }
3900
+ if (!options.minimal) {
3901
+ spinner.start("Setting up Traefik configuration...");
3902
+ try {
3903
+ if (!existsSync17(join16(TRAEFIK_DIR, "docker-compose.yml"))) {
3904
+ copyDirectoryRecursive(SOURCE_TRAEFIK_TEMPLATES, TRAEFIK_DIR);
3905
+ spinner.succeed("Traefik configuration created from templates");
3906
+ } else {
3907
+ spinner.info("Traefik configuration already exists (skipping)");
3908
+ }
3909
+ } catch (error) {
3910
+ spinner.fail(`Failed to set up Traefik configuration: ${error}`);
3911
+ console.log(chalk21.yellow("You can set up Traefik manually later"));
3912
+ }
3913
+ }
3914
+ const configFile = join16(PANOPTICON_HOME, "config.toml");
3085
3915
  if (!existsSync17(configFile)) {
3086
3916
  spinner.start("Creating default config...");
3917
+ const traefikConfig = options.minimal ? "enabled = false" : `enabled = true
3918
+ dashboard_port = 8080
3919
+ domain = "pan.localhost"`;
3087
3920
  writeFileSync10(
3088
3921
  configFile,
3089
3922
  `# Panopticon configuration
@@ -3095,6 +3928,9 @@ default_runtime = "claude"
3095
3928
  port = 3001
3096
3929
  api_port = 3002
3097
3930
 
3931
+ [traefik]
3932
+ ${traefikConfig}
3933
+
3098
3934
  [sync]
3099
3935
  auto_sync = true
3100
3936
  strategy = "symlink"
@@ -3107,20 +3943,27 @@ consecutive_failures = 3
3107
3943
  spinner.succeed("Config created");
3108
3944
  }
3109
3945
  console.log("");
3110
- console.log(chalk20.green.bold("Installation complete!"));
3946
+ console.log(chalk21.green.bold("Installation complete!"));
3111
3947
  console.log("");
3112
- console.log(chalk20.bold("Next steps:"));
3113
- console.log(` 1. Run ${chalk20.cyan("pan sync")} to sync skills to ~/.claude/`);
3114
- console.log(` 2. Run ${chalk20.cyan("pan up")} to start the dashboard`);
3115
- console.log(` 3. Create a workspace with ${chalk20.cyan("pan workspace create <issue-id>")}`);
3948
+ console.log(chalk21.bold("Next steps:"));
3949
+ console.log(` 1. Run ${chalk21.cyan("pan sync")} to sync skills to ~/.claude/`);
3950
+ if (!options.minimal) {
3951
+ console.log(` 2. Add to ${chalk21.yellow("/etc/hosts")}: ${chalk21.cyan("127.0.0.1 pan.localhost")}`);
3952
+ console.log(` 3. Run ${chalk21.cyan("pan up")} to start Traefik and dashboard`);
3953
+ console.log(` 4. Access dashboard at ${chalk21.cyan("https://pan.localhost")}`);
3954
+ } else {
3955
+ console.log(` 2. Run ${chalk21.cyan("pan up")} to start the dashboard`);
3956
+ console.log(` 3. Access dashboard at ${chalk21.cyan("http://localhost:3001")}`);
3957
+ }
3958
+ console.log(` ${!options.minimal ? "5" : "4"}. Create a workspace with ${chalk21.cyan("pan workspace create <issue-id>")}`);
3116
3959
  console.log("");
3117
3960
  }
3118
3961
 
3119
3962
  // src/cli/commands/project.ts
3120
- import chalk21 from "chalk";
3121
- import { existsSync as existsSync18, readFileSync as readFileSync15, writeFileSync as writeFileSync11, mkdirSync as mkdirSync11 } from "fs";
3122
- import { join as join16, resolve } from "path";
3123
- var PROJECTS_FILE = join16(PANOPTICON_HOME, "projects.json");
3963
+ import chalk22 from "chalk";
3964
+ import { existsSync as existsSync18, readFileSync as readFileSync15, writeFileSync as writeFileSync11, mkdirSync as mkdirSync13 } from "fs";
3965
+ import { join as join17, resolve } from "path";
3966
+ var PROJECTS_FILE = join17(PANOPTICON_HOME, "projects.json");
3124
3967
  function loadProjects() {
3125
3968
  if (!existsSync18(PROJECTS_FILE)) {
3126
3969
  return [];
@@ -3132,25 +3975,25 @@ function loadProjects() {
3132
3975
  }
3133
3976
  }
3134
3977
  function saveProjects(projects) {
3135
- mkdirSync11(PANOPTICON_HOME, { recursive: true });
3978
+ mkdirSync13(PANOPTICON_HOME, { recursive: true });
3136
3979
  writeFileSync11(PROJECTS_FILE, JSON.stringify(projects, null, 2));
3137
3980
  }
3138
3981
  async function projectAddCommand(projectPath, options = {}) {
3139
3982
  const fullPath = resolve(projectPath);
3140
3983
  if (!existsSync18(fullPath)) {
3141
- console.log(chalk21.red(`Path does not exist: ${fullPath}`));
3984
+ console.log(chalk22.red(`Path does not exist: ${fullPath}`));
3142
3985
  return;
3143
3986
  }
3144
3987
  const projects = loadProjects();
3145
3988
  const existing = projects.find((p) => p.path === fullPath);
3146
3989
  if (existing) {
3147
- console.log(chalk21.yellow(`Project already registered: ${existing.name}`));
3990
+ console.log(chalk22.yellow(`Project already registered: ${existing.name}`));
3148
3991
  return;
3149
3992
  }
3150
3993
  const name = options.name || fullPath.split("/").pop() || "unknown";
3151
3994
  let linearTeam = options.linearTeam;
3152
3995
  if (!linearTeam) {
3153
- const projectToml = join16(fullPath, ".panopticon", "project.toml");
3996
+ const projectToml = join17(fullPath, ".panopticon", "project.toml");
3154
3997
  if (existsSync18(projectToml)) {
3155
3998
  const content = readFileSync15(projectToml, "utf-8");
3156
3999
  const match = content.match(/team\s*=\s*"([^"]+)"/);
@@ -3166,33 +4009,33 @@ async function projectAddCommand(projectPath, options = {}) {
3166
4009
  };
3167
4010
  projects.push(project2);
3168
4011
  saveProjects(projects);
3169
- console.log(chalk21.green(`\u2713 Added project: ${name}`));
3170
- console.log(chalk21.dim(` Path: ${fullPath}`));
4012
+ console.log(chalk22.green(`\u2713 Added project: ${name}`));
4013
+ console.log(chalk22.dim(` Path: ${fullPath}`));
3171
4014
  if (linearTeam) {
3172
- console.log(chalk21.dim(` Linear team: ${linearTeam}`));
4015
+ console.log(chalk22.dim(` Linear team: ${linearTeam}`));
3173
4016
  }
3174
4017
  }
3175
4018
  async function projectListCommand(options = {}) {
3176
4019
  const projects = loadProjects();
3177
4020
  if (projects.length === 0) {
3178
- console.log(chalk21.dim("No projects registered."));
3179
- console.log(chalk21.dim("Add one with: pan project add <path>"));
4021
+ console.log(chalk22.dim("No projects registered."));
4022
+ console.log(chalk22.dim("Add one with: pan project add <path>"));
3180
4023
  return;
3181
4024
  }
3182
4025
  if (options.json) {
3183
4026
  console.log(JSON.stringify(projects, null, 2));
3184
4027
  return;
3185
4028
  }
3186
- console.log(chalk21.bold("\nRegistered Projects:\n"));
4029
+ console.log(chalk22.bold("\nRegistered Projects:\n"));
3187
4030
  for (const project2 of projects) {
3188
4031
  const exists = existsSync18(project2.path);
3189
- const statusIcon = exists ? chalk21.green("\u2713") : chalk21.red("\u2717");
3190
- console.log(`${statusIcon} ${chalk21.bold(project2.name)}`);
3191
- console.log(` ${chalk21.dim(project2.path)}`);
4032
+ const statusIcon = exists ? chalk22.green("\u2713") : chalk22.red("\u2717");
4033
+ console.log(`${statusIcon} ${chalk22.bold(project2.name)}`);
4034
+ console.log(` ${chalk22.dim(project2.path)}`);
3192
4035
  if (project2.linearTeam) {
3193
- console.log(` ${chalk21.cyan(`Linear: ${project2.linearTeam}`)}`);
4036
+ console.log(` ${chalk22.cyan(`Linear: ${project2.linearTeam}`)}`);
3194
4037
  }
3195
- console.log(` ${chalk21.dim(`Type: ${project2.type}`)}`);
4038
+ console.log(` ${chalk22.dim(`Type: ${project2.type}`)}`);
3196
4039
  console.log("");
3197
4040
  }
3198
4041
  }
@@ -3202,23 +4045,23 @@ async function projectRemoveCommand(nameOrPath) {
3202
4045
  (p) => p.name === nameOrPath || p.path === resolve(nameOrPath)
3203
4046
  );
3204
4047
  if (index === -1) {
3205
- console.log(chalk21.red(`Project not found: ${nameOrPath}`));
4048
+ console.log(chalk22.red(`Project not found: ${nameOrPath}`));
3206
4049
  return;
3207
4050
  }
3208
4051
  const removed = projects.splice(index, 1)[0];
3209
4052
  saveProjects(projects);
3210
- console.log(chalk21.green(`\u2713 Removed project: ${removed.name}`));
4053
+ console.log(chalk22.green(`\u2713 Removed project: ${removed.name}`));
3211
4054
  }
3212
4055
 
3213
4056
  // src/cli/commands/doctor.ts
3214
- import chalk22 from "chalk";
3215
- import { existsSync as existsSync19, readdirSync as readdirSync9 } from "fs";
3216
- import { execSync as execSync8 } from "child_process";
3217
- import { homedir as homedir6 } from "os";
3218
- import { join as join17 } from "path";
4057
+ import chalk23 from "chalk";
4058
+ import { existsSync as existsSync19, readdirSync as readdirSync12 } from "fs";
4059
+ import { execSync as execSync9 } from "child_process";
4060
+ import { homedir as homedir5 } from "os";
4061
+ import { join as join18 } from "path";
3219
4062
  function checkCommand2(cmd) {
3220
4063
  try {
3221
- execSync8(`which ${cmd}`, { encoding: "utf-8", stdio: "pipe" });
4064
+ execSync9(`which ${cmd}`, { encoding: "utf-8", stdio: "pipe" });
3222
4065
  return true;
3223
4066
  } catch {
3224
4067
  return false;
@@ -3230,14 +4073,14 @@ function checkDirectory(path) {
3230
4073
  function countItems(path) {
3231
4074
  if (!existsSync19(path)) return 0;
3232
4075
  try {
3233
- return readdirSync9(path).length;
4076
+ return readdirSync12(path).length;
3234
4077
  } catch {
3235
4078
  return 0;
3236
4079
  }
3237
4080
  }
3238
4081
  async function doctorCommand() {
3239
- console.log(chalk22.bold("\nPanopticon Doctor\n"));
3240
- console.log(chalk22.dim("Checking system health...\n"));
4082
+ console.log(chalk23.bold("\nPanopticon Doctor\n"));
4083
+ console.log(chalk23.dim("Checking system health...\n"));
3241
4084
  const checks = [];
3242
4085
  const requiredCommands = [
3243
4086
  { cmd: "git", name: "Git", fix: "Install git" },
@@ -3279,8 +4122,8 @@ async function doctorCommand() {
3279
4122
  }
3280
4123
  }
3281
4124
  if (checkDirectory(CLAUDE_DIR)) {
3282
- const skillsCount = countItems(join17(CLAUDE_DIR, "skills"));
3283
- const commandsCount = countItems(join17(CLAUDE_DIR, "commands"));
4125
+ const skillsCount = countItems(join18(CLAUDE_DIR, "skills"));
4126
+ const commandsCount = countItems(join18(CLAUDE_DIR, "commands"));
3284
4127
  checks.push({
3285
4128
  name: "Claude Code Skills",
3286
4129
  status: skillsCount > 0 ? "ok" : "warn",
@@ -3301,7 +4144,7 @@ async function doctorCommand() {
3301
4144
  fix: "Install Claude Code first"
3302
4145
  });
3303
4146
  }
3304
- const envFile = join17(homedir6(), ".panopticon.env");
4147
+ const envFile = join18(homedir5(), ".panopticon.env");
3305
4148
  if (existsSync19(envFile)) {
3306
4149
  checks.push({ name: "Config File", status: "ok", message: "~/.panopticon.env exists" });
3307
4150
  } else {
@@ -3335,7 +4178,7 @@ async function doctorCommand() {
3335
4178
  });
3336
4179
  }
3337
4180
  try {
3338
- const sessions = execSync8("tmux list-sessions 2>/dev/null || true", { encoding: "utf-8" });
4181
+ const sessions = execSync9("tmux list-sessions 2>/dev/null || true", { encoding: "utf-8" });
3339
4182
  const agentSessions = sessions.split("\n").filter((s) => s.includes("agent-")).length;
3340
4183
  checks.push({
3341
4184
  name: "Running Agents",
@@ -3350,52 +4193,175 @@ async function doctorCommand() {
3350
4193
  });
3351
4194
  }
3352
4195
  const icons = {
3353
- ok: chalk22.green("\u2713"),
3354
- warn: chalk22.yellow("\u26A0"),
3355
- error: chalk22.red("\u2717")
4196
+ ok: chalk23.green("\u2713"),
4197
+ warn: chalk23.yellow("\u26A0"),
4198
+ error: chalk23.red("\u2717")
3356
4199
  };
3357
4200
  let hasErrors = false;
3358
4201
  let hasWarnings = false;
3359
4202
  for (const check of checks) {
3360
4203
  const icon = icons[check.status];
3361
- const message = check.status === "error" ? chalk22.red(check.message) : check.status === "warn" ? chalk22.yellow(check.message) : chalk22.dim(check.message);
4204
+ const message = check.status === "error" ? chalk23.red(check.message) : check.status === "warn" ? chalk23.yellow(check.message) : chalk23.dim(check.message);
3362
4205
  console.log(`${icon} ${check.name}: ${message}`);
3363
4206
  if (check.fix && check.status !== "ok") {
3364
- console.log(chalk22.dim(` Fix: ${check.fix}`));
4207
+ console.log(chalk23.dim(` Fix: ${check.fix}`));
3365
4208
  }
3366
4209
  if (check.status === "error") hasErrors = true;
3367
4210
  if (check.status === "warn") hasWarnings = true;
3368
4211
  }
3369
4212
  console.log("");
3370
4213
  if (hasErrors) {
3371
- console.log(chalk22.red("Some required components are missing."));
3372
- console.log(chalk22.dim("Fix the errors above before using Panopticon."));
4214
+ console.log(chalk23.red("Some required components are missing."));
4215
+ console.log(chalk23.dim("Fix the errors above before using Panopticon."));
3373
4216
  } else if (hasWarnings) {
3374
- console.log(chalk22.yellow("System is functional with some optional features missing."));
4217
+ console.log(chalk23.yellow("System is functional with some optional features missing."));
3375
4218
  } else {
3376
- console.log(chalk22.green("All systems operational!"));
4219
+ console.log(chalk23.green("All systems operational!"));
3377
4220
  }
3378
4221
  console.log("");
3379
4222
  }
3380
4223
 
4224
+ // src/cli/commands/update.ts
4225
+ import { execSync as execSync10 } from "child_process";
4226
+ import chalk24 from "chalk";
4227
+ function getCurrentVersion() {
4228
+ try {
4229
+ const pkg = require_package();
4230
+ return pkg.version;
4231
+ } catch {
4232
+ return "unknown";
4233
+ }
4234
+ }
4235
+ async function getLatestVersion() {
4236
+ try {
4237
+ const result = execSync10("npm view panopticon-cli version", {
4238
+ encoding: "utf8",
4239
+ stdio: ["pipe", "pipe", "pipe"]
4240
+ });
4241
+ return result.trim();
4242
+ } catch {
4243
+ throw new Error("Failed to check npm for latest version");
4244
+ }
4245
+ }
4246
+ function isNewer(latest, current) {
4247
+ const parseVersion = (v) => {
4248
+ const parts = v.replace(/^v/, "").split(".");
4249
+ return {
4250
+ major: parseInt(parts[0] || "0", 10),
4251
+ minor: parseInt(parts[1] || "0", 10),
4252
+ patch: parseInt(parts[2] || "0", 10)
4253
+ };
4254
+ };
4255
+ const l = parseVersion(latest);
4256
+ const c = parseVersion(current);
4257
+ if (l.major !== c.major) return l.major > c.major;
4258
+ if (l.minor !== c.minor) return l.minor > c.minor;
4259
+ return l.patch > c.patch;
4260
+ }
4261
+ async function updateCommand(options) {
4262
+ console.log(chalk24.bold("Panopticon Update\n"));
4263
+ const currentVersion = getCurrentVersion();
4264
+ console.log(`Current version: ${chalk24.cyan(currentVersion)}`);
4265
+ let latestVersion;
4266
+ try {
4267
+ console.log(chalk24.dim("Checking npm for latest version..."));
4268
+ latestVersion = await getLatestVersion();
4269
+ console.log(`Latest version: ${chalk24.cyan(latestVersion)}`);
4270
+ } catch (error) {
4271
+ console.error(chalk24.red("Failed to check for updates"));
4272
+ console.error(chalk24.dim("Make sure you have internet connectivity"));
4273
+ process.exit(1);
4274
+ }
4275
+ const needsUpdate = isNewer(latestVersion, currentVersion);
4276
+ if (!needsUpdate) {
4277
+ console.log(chalk24.green("\n\u2713 You are on the latest version"));
4278
+ return;
4279
+ }
4280
+ console.log(
4281
+ chalk24.yellow(`
4282
+ \u2191 Update available: ${currentVersion} \u2192 ${latestVersion}`)
4283
+ );
4284
+ if (options.check) {
4285
+ console.log(chalk24.dim("\nRun `pan update` to install"));
4286
+ return;
4287
+ }
4288
+ console.log(chalk24.dim("\nUpdating Panopticon..."));
4289
+ try {
4290
+ execSync10("npm install -g panopticon-cli@latest", {
4291
+ stdio: "inherit"
4292
+ });
4293
+ console.log(chalk24.green(`
4294
+ \u2713 Updated to ${latestVersion}`));
4295
+ const config = loadConfig();
4296
+ if (config.sync.auto_sync) {
4297
+ console.log(chalk24.dim("\nRunning auto-sync..."));
4298
+ await syncCommand({});
4299
+ }
4300
+ console.log(chalk24.dim("\nRestart any running agents to use the new version."));
4301
+ } catch (error) {
4302
+ console.error(chalk24.red("\nUpdate failed"));
4303
+ console.error(
4304
+ chalk24.dim("Try running with sudo: sudo npm install -g panopticon-cli@latest")
4305
+ );
4306
+ process.exit(1);
4307
+ }
4308
+ }
4309
+
3381
4310
  // src/cli/index.ts
3382
4311
  var program = new Command();
3383
- program.name("pan").description("Multi-agent orchestration for AI coding assistants").version("0.1.0");
4312
+ program.name("pan").description("Multi-agent orchestration for AI coding assistants").version("0.1.3");
3384
4313
  program.command("init").description("Initialize Panopticon (~/.panopticon/)").action(initCommand);
3385
4314
  program.command("sync").description("Sync skills/commands to AI tools").option("--dry-run", "Show what would be synced").option("--force", "Overwrite without prompts").option("--backup-only", "Only create backup").action(syncCommand);
3386
4315
  program.command("restore [timestamp]").description("Restore from backup").action(restoreCommand);
4316
+ var backup = program.command("backup").description("Manage backups");
4317
+ backup.command("list").description("List all backups").option("--json", "Output as JSON").action(backupListCommand);
4318
+ backup.command("clean").description("Remove old backups").option("--keep <count>", "Number of backups to keep", "10").action(backupCleanCommand);
3387
4319
  program.command("skills").description("List and manage skills").option("--json", "Output as JSON").action(skillsCommand);
3388
4320
  registerWorkCommands(program);
3389
4321
  registerWorkspaceCommands(program);
3390
4322
  registerInstallCommand(program);
3391
4323
  program.command("status").description("Show running agents (shorthand for work status)").option("--json", "Output as JSON").action(statusCommand);
3392
- program.command("up").description("Start dashboard").option("--detach", "Run in background").action(async (options) => {
3393
- const { spawn, execSync: execSync9 } = await import("child_process");
3394
- const { join: join18, dirname: dirname2 } = await import("path");
3395
- const { fileURLToPath } = await import("url");
3396
- const __dirname2 = dirname2(fileURLToPath(import.meta.url));
3397
- const dashboardDir = join18(__dirname2, "..", "dashboard");
3398
- console.log(chalk23.bold("Starting Panopticon dashboard...\n"));
4324
+ program.command("up").description("Start dashboard (and Traefik if enabled)").option("--detach", "Run in background").option("--skip-traefik", "Skip Traefik startup").action(async (options) => {
4325
+ const { spawn, execSync: execSync11 } = await import("child_process");
4326
+ const { join: join19, dirname: dirname5 } = await import("path");
4327
+ const { fileURLToPath: fileURLToPath2 } = await import("url");
4328
+ const { readFileSync: readFileSync16, existsSync: existsSync20 } = await import("fs");
4329
+ const { parse } = await import("@iarna/toml");
4330
+ const __dirname3 = dirname5(fileURLToPath2(import.meta.url));
4331
+ const dashboardDir = join19(__dirname3, "..", "dashboard");
4332
+ const configFile = join19(process.env.HOME || "", ".panopticon", "config.toml");
4333
+ let traefikEnabled = false;
4334
+ let traefikDomain = "pan.localhost";
4335
+ if (existsSync20(configFile)) {
4336
+ try {
4337
+ const configContent = readFileSync16(configFile, "utf-8");
4338
+ const config = parse(configContent);
4339
+ traefikEnabled = config.traefik?.enabled === true;
4340
+ traefikDomain = config.traefik?.domain || "pan.localhost";
4341
+ } catch (error) {
4342
+ console.log(chalk25.yellow("Warning: Could not read config.toml"));
4343
+ }
4344
+ }
4345
+ console.log(chalk25.bold("Starting Panopticon...\n"));
4346
+ if (traefikEnabled && !options.skipTraefik) {
4347
+ const traefikDir = join19(process.env.HOME || "", ".panopticon", "traefik");
4348
+ if (existsSync20(traefikDir)) {
4349
+ try {
4350
+ console.log(chalk25.dim("Starting Traefik..."));
4351
+ execSync11("docker-compose up -d", {
4352
+ cwd: traefikDir,
4353
+ stdio: "pipe"
4354
+ });
4355
+ console.log(chalk25.green("\u2713 Traefik started"));
4356
+ console.log(chalk25.dim(` Dashboard: https://traefik.${traefikDomain}:8080
4357
+ `));
4358
+ } catch (error) {
4359
+ console.log(chalk25.yellow("\u26A0 Failed to start Traefik (continuing anyway)"));
4360
+ console.log(chalk25.dim(" Run with --skip-traefik to suppress this message\n"));
4361
+ }
4362
+ }
4363
+ }
4364
+ console.log(chalk25.dim("Starting dashboard..."));
3399
4365
  if (options.detach) {
3400
4366
  const child = spawn("npm", ["run", "dev"], {
3401
4367
  cwd: dashboardDir,
@@ -3403,37 +4369,79 @@ program.command("up").description("Start dashboard").option("--detach", "Run in
3403
4369
  stdio: "ignore"
3404
4370
  });
3405
4371
  child.unref();
3406
- console.log(chalk23.green("Dashboard started in background"));
3407
- console.log(`Frontend: ${chalk23.cyan("http://localhost:3001")}`);
3408
- console.log(`API: ${chalk23.cyan("http://localhost:3002")}`);
4372
+ console.log(chalk25.green("\u2713 Dashboard started in background"));
4373
+ if (traefikEnabled) {
4374
+ console.log(` Frontend: ${chalk25.cyan(`https://${traefikDomain}`)}`);
4375
+ console.log(` API: ${chalk25.cyan(`https://${traefikDomain}/api`)}`);
4376
+ } else {
4377
+ console.log(` Frontend: ${chalk25.cyan("http://localhost:3001")}`);
4378
+ console.log(` API: ${chalk25.cyan("http://localhost:3002")}`);
4379
+ }
3409
4380
  } else {
3410
- console.log(`Frontend: ${chalk23.cyan("http://localhost:3001")}`);
3411
- console.log(`API: ${chalk23.cyan("http://localhost:3002")}`);
3412
- console.log(chalk23.dim("\nPress Ctrl+C to stop\n"));
4381
+ if (traefikEnabled) {
4382
+ console.log(` Frontend: ${chalk25.cyan(`https://${traefikDomain}`)}`);
4383
+ console.log(` API: ${chalk25.cyan(`https://${traefikDomain}/api`)}`);
4384
+ } else {
4385
+ console.log(` Frontend: ${chalk25.cyan("http://localhost:3001")}`);
4386
+ console.log(` API: ${chalk25.cyan("http://localhost:3002")}`);
4387
+ }
4388
+ console.log(chalk25.dim("\nPress Ctrl+C to stop\n"));
3413
4389
  const child = spawn("npm", ["run", "dev"], {
3414
4390
  cwd: dashboardDir,
3415
4391
  stdio: "inherit"
3416
4392
  });
3417
4393
  child.on("error", (err) => {
3418
- console.error(chalk23.red("Failed to start dashboard:"), err.message);
4394
+ console.error(chalk25.red("Failed to start dashboard:"), err.message);
3419
4395
  process.exit(1);
3420
4396
  });
3421
4397
  }
3422
4398
  });
3423
- program.command("down").description("Stop dashboard").action(async () => {
3424
- const { execSync: execSync9 } = await import("child_process");
4399
+ program.command("down").description("Stop dashboard (and Traefik if enabled)").option("--skip-traefik", "Skip Traefik shutdown").action(async (options) => {
4400
+ const { execSync: execSync11 } = await import("child_process");
4401
+ const { join: join19 } = await import("path");
4402
+ const { readFileSync: readFileSync16, existsSync: existsSync20 } = await import("fs");
4403
+ const { parse } = await import("@iarna/toml");
4404
+ console.log(chalk25.bold("Stopping Panopticon...\n"));
4405
+ console.log(chalk25.dim("Stopping dashboard..."));
3425
4406
  try {
3426
- execSync9("lsof -ti:3001 | xargs kill -9 2>/dev/null || true", { stdio: "pipe" });
3427
- execSync9("lsof -ti:3002 | xargs kill -9 2>/dev/null || true", { stdio: "pipe" });
3428
- console.log(chalk23.green("Dashboard stopped"));
4407
+ execSync11("lsof -ti:3001 | xargs kill -9 2>/dev/null || true", { stdio: "pipe" });
4408
+ execSync11("lsof -ti:3002 | xargs kill -9 2>/dev/null || true", { stdio: "pipe" });
4409
+ console.log(chalk25.green("\u2713 Dashboard stopped"));
3429
4410
  } catch {
3430
- console.log(chalk23.dim("No dashboard processes found"));
4411
+ console.log(chalk25.dim(" No dashboard processes found"));
4412
+ }
4413
+ const configFile = join19(process.env.HOME || "", ".panopticon", "config.toml");
4414
+ let traefikEnabled = false;
4415
+ if (existsSync20(configFile)) {
4416
+ try {
4417
+ const configContent = readFileSync16(configFile, "utf-8");
4418
+ const config = parse(configContent);
4419
+ traefikEnabled = config.traefik?.enabled === true;
4420
+ } catch (error) {
4421
+ }
3431
4422
  }
4423
+ if (traefikEnabled && !options.skipTraefik) {
4424
+ const traefikDir = join19(process.env.HOME || "", ".panopticon", "traefik");
4425
+ if (existsSync20(traefikDir)) {
4426
+ console.log(chalk25.dim("Stopping Traefik..."));
4427
+ try {
4428
+ execSync11("docker-compose down", {
4429
+ cwd: traefikDir,
4430
+ stdio: "pipe"
4431
+ });
4432
+ console.log(chalk25.green("\u2713 Traefik stopped"));
4433
+ } catch (error) {
4434
+ console.log(chalk25.yellow("\u26A0 Failed to stop Traefik"));
4435
+ }
4436
+ }
4437
+ }
4438
+ console.log("");
3432
4439
  });
3433
4440
  var project = program.command("project").description("Project management");
3434
4441
  project.command("add <path>").description("Register a project with Panopticon").option("--name <name>", "Project name").option("--type <type>", "Project type (standalone/monorepo)", "standalone").option("--linear-team <team>", "Linear team prefix").action(projectAddCommand);
3435
4442
  project.command("list").description("List all managed projects").option("--json", "Output as JSON").action(projectListCommand);
3436
4443
  project.command("remove <nameOrPath>").description("Remove a project from Panopticon").action(projectRemoveCommand);
3437
4444
  program.command("doctor").description("Check system health and dependencies").action(doctorCommand);
4445
+ program.command("update").description("Update Panopticon to latest version").option("--check", "Only check for updates, don't install").option("--force", "Force update even if on latest").action(updateCommand);
3438
4446
  program.parse();
3439
4447
  //# sourceMappingURL=index.js.map