panopticon-cli 0.3.4 → 0.3.7

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
@@ -46,7 +46,7 @@ import {
46
46
 
47
47
  // src/cli/index.ts
48
48
  import { Command } from "commander";
49
- import chalk33 from "chalk";
49
+ import chalk34 from "chalk";
50
50
 
51
51
  // src/cli/commands/init.ts
52
52
  import { existsSync, mkdirSync, readdirSync, cpSync } from "fs";
@@ -606,13 +606,13 @@ function sendMail(toAgentId, from, message, priority = "normal") {
606
606
  JSON.stringify(mailItem, null, 2)
607
607
  );
608
608
  }
609
- function generateGUPPPrompt(agentId) {
609
+ function generateFixedPointPrompt(agentId) {
610
610
  const { hasWork, urgentCount, items } = checkHook(agentId);
611
611
  if (!hasWork) return null;
612
612
  const lines = [
613
- "# GUPP: Work Found on Your Hook",
613
+ "# FPP: Work Found on Your Hook",
614
614
  "",
615
- '> "If there is work on your Hook, YOU MUST RUN IT."',
615
+ '> "Any runnable action is a fixed point and must resolve before the system can rest."',
616
616
  ""
617
617
  ];
618
618
  if (urgentCount > 0) {
@@ -820,9 +820,9 @@ function spawnAgent(options) {
820
820
  let prompt = options.prompt || "";
821
821
  const { hasWork, items } = checkHook(agentId);
822
822
  if (hasWork) {
823
- const guppPrompt = generateGUPPPrompt(agentId);
824
- if (guppPrompt) {
825
- prompt = guppPrompt + "\n\n---\n\n" + prompt;
823
+ const fixedPointPrompt = generateFixedPointPrompt(agentId);
824
+ if (fixedPointPrompt) {
825
+ prompt = fixedPointPrompt + "\n\n---\n\n" + prompt;
826
826
  }
827
827
  }
828
828
  const promptFile = join5(getAgentDir(agentId), "initial-prompt.md");
@@ -959,17 +959,17 @@ function generateRecoveryPrompt(state) {
959
959
  "3. Check hook for pending work: `pan work hook check`",
960
960
  "4. Resume from last known state",
961
961
  "",
962
- "## GUPP Reminder",
963
- '> "If there is work on your Hook, YOU MUST RUN IT."',
962
+ "## FPP Reminder",
963
+ '> "Any runnable action is a fixed point and must resolve before the system can rest."',
964
964
  ""
965
965
  ];
966
966
  const { hasWork } = checkHook(state.id);
967
967
  if (hasWork) {
968
- const guppPrompt = generateGUPPPrompt(state.id);
969
- if (guppPrompt) {
968
+ const fixedPointPrompt = generateFixedPointPrompt(state.id);
969
+ if (fixedPointPrompt) {
970
970
  lines.push("---");
971
971
  lines.push("");
972
- lines.push(guppPrompt);
972
+ lines.push(fixedPointPrompt);
973
973
  }
974
974
  }
975
975
  return lines.join("\n");
@@ -1328,12 +1328,12 @@ import chalk9 from "chalk";
1328
1328
  async function killCommand(id, options) {
1329
1329
  const agentId = id.startsWith("agent-") ? id : `agent-${id.toLowerCase()}`;
1330
1330
  const state = getAgentState(agentId);
1331
- const isRunning2 = sessionExists(agentId);
1332
- if (!state && !isRunning2) {
1331
+ const isRunning3 = sessionExists(agentId);
1332
+ if (!state && !isRunning3) {
1333
1333
  console.log(chalk9.yellow(`Agent ${agentId} not found.`));
1334
1334
  return;
1335
1335
  }
1336
- if (!options.force && isRunning2) {
1336
+ if (!options.force && isRunning3) {
1337
1337
  }
1338
1338
  try {
1339
1339
  stopAgent(agentId);
@@ -1420,7 +1420,7 @@ function findPRForBranch(workspace) {
1420
1420
  }
1421
1421
  function mergePR(workspace, prNumber) {
1422
1422
  try {
1423
- execSync3(`gh pr merge ${prNumber} --squash --delete-branch`, {
1423
+ execSync3(`gh pr merge ${prNumber} --squash`, {
1424
1424
  cwd: workspace,
1425
1425
  encoding: "utf-8",
1426
1426
  stdio: ["pipe", "pipe", "pipe"]
@@ -1432,8 +1432,8 @@ function mergePR(workspace, prNumber) {
1432
1432
  }
1433
1433
  async function updateLinearStatus(apiKey, issueIdentifier) {
1434
1434
  try {
1435
- const { LinearClient } = await import("@linear/sdk");
1436
- const client = new LinearClient({ apiKey });
1435
+ const { LinearClient: LinearClient2 } = await import("@linear/sdk");
1436
+ const client = new LinearClient2({ apiKey });
1437
1437
  const me = await client.viewer;
1438
1438
  const teams = await me.teams();
1439
1439
  const team = teams.nodes[0];
@@ -1938,8 +1938,8 @@ async function planCommand(id, options = {}) {
1938
1938
  process.exit(1);
1939
1939
  }
1940
1940
  spinner.text = "Fetching issue from Linear...";
1941
- const { LinearClient } = await import("@linear/sdk");
1942
- const client = new LinearClient({ apiKey });
1941
+ const { LinearClient: LinearClient2 } = await import("@linear/sdk");
1942
+ const client = new LinearClient2({ apiKey });
1943
1943
  const me = await client.viewer;
1944
1944
  const teams = await me.teams();
1945
1945
  const team = teams.nodes[0];
@@ -2519,10 +2519,10 @@ async function hookCommand(action, idOrMessage, options = {}) {
2519
2519
  console.log(chalk15.green(`\u2713 Mail sent to ${targetAgent}`));
2520
2520
  break;
2521
2521
  }
2522
- case "gupp": {
2523
- const prompt = generateGUPPPrompt(idOrMessage || agentId);
2522
+ case "fpp": {
2523
+ const prompt = generateFixedPointPrompt(idOrMessage || agentId);
2524
2524
  if (!prompt) {
2525
- console.log(chalk15.green("No GUPP work found"));
2525
+ console.log(chalk15.green("No fixed point work found"));
2526
2526
  return;
2527
2527
  }
2528
2528
  console.log(prompt);
@@ -2536,7 +2536,7 @@ async function hookCommand(action, idOrMessage, options = {}) {
2536
2536
  console.log(` ${chalk15.cyan("pan work hook pop <item-id>")} - Remove completed item`);
2537
2537
  console.log(` ${chalk15.cyan("pan work hook clear [agent-id]")} - Clear all hook items`);
2538
2538
  console.log(` ${chalk15.cyan("pan work hook mail <agent-id> <msg>")} - Send mail to agent`);
2539
- console.log(` ${chalk15.cyan("pan work hook gupp [agent-id]")} - Generate GUPP prompt`);
2539
+ console.log(` ${chalk15.cyan("pan work hook fpp [agent-id]")} - Generate FPP prompt`);
2540
2540
  }
2541
2541
  }
2542
2542
 
@@ -3431,6 +3431,178 @@ async function healthCommand(action, arg, options = {}) {
3431
3431
  }
3432
3432
  }
3433
3433
 
3434
+ // src/cli/commands/work/reopen.ts
3435
+ import chalk20 from "chalk";
3436
+ import ora10 from "ora";
3437
+ import inquirer3 from "inquirer";
3438
+ import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
3439
+ import { join as join13 } from "path";
3440
+ import { homedir as homedir5 } from "os";
3441
+ import { LinearClient } from "@linear/sdk";
3442
+ function getLinearApiKey3() {
3443
+ const envFile = join13(homedir5(), ".panopticon.env");
3444
+ if (existsSync14(envFile)) {
3445
+ const content = readFileSync13(envFile, "utf-8");
3446
+ const match = content.match(/LINEAR_API_KEY=(.+)/);
3447
+ if (match) return match[1].trim();
3448
+ }
3449
+ return process.env.LINEAR_API_KEY || null;
3450
+ }
3451
+ async function fetchIssueWithComments(client, issueId) {
3452
+ const results = await client.searchIssues(issueId, { first: 1 });
3453
+ if (results.nodes.length === 0) {
3454
+ throw new Error(`Issue not found: ${issueId}`);
3455
+ }
3456
+ const linearIssue = results.nodes[0];
3457
+ const state = await linearIssue.state;
3458
+ const commentsData = await linearIssue.comments();
3459
+ const comments = [];
3460
+ for (const comment of commentsData.nodes) {
3461
+ const user = await comment.user;
3462
+ comments.push({
3463
+ id: comment.id,
3464
+ body: comment.body,
3465
+ author: user?.name ?? "Unknown",
3466
+ createdAt: comment.createdAt.toISOString()
3467
+ });
3468
+ }
3469
+ return {
3470
+ issue: {
3471
+ id: linearIssue.id,
3472
+ identifier: linearIssue.identifier,
3473
+ title: linearIssue.title,
3474
+ description: linearIssue.description || void 0,
3475
+ state: state?.name || "Unknown",
3476
+ url: linearIssue.url
3477
+ },
3478
+ comments
3479
+ };
3480
+ }
3481
+ async function reopenIssue(client, issueId) {
3482
+ const results = await client.searchIssues(issueId, { first: 1 });
3483
+ if (results.nodes.length === 0) {
3484
+ throw new Error(`Issue not found: ${issueId}`);
3485
+ }
3486
+ const linearIssue = results.nodes[0];
3487
+ const team = await linearIssue.team;
3488
+ if (!team) {
3489
+ throw new Error("Could not determine issue team");
3490
+ }
3491
+ const states = await team.states();
3492
+ const backlogState = states.nodes.find((s) => s.type === "backlog");
3493
+ const unstartedState = states.nodes.find((s) => s.type === "unstarted");
3494
+ const targetState = backlogState || unstartedState;
3495
+ if (!targetState) {
3496
+ throw new Error("No backlog or unstarted state found");
3497
+ }
3498
+ await client.updateIssue(linearIssue.id, {
3499
+ stateId: targetState.id
3500
+ });
3501
+ }
3502
+ function formatComments(comments) {
3503
+ if (comments.length === 0) {
3504
+ return "No comments";
3505
+ }
3506
+ return comments.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()).map((c) => {
3507
+ const date = new Date(c.createdAt).toLocaleString();
3508
+ const truncatedBody = c.body.length > 200 ? c.body.slice(0, 200) + "..." : c.body;
3509
+ return ` [${date}] ${c.author}:
3510
+ ${truncatedBody.replace(/\n/g, "\n ")}`;
3511
+ }).join("\n\n");
3512
+ }
3513
+ async function reopenCommand(id, options = {}) {
3514
+ const spinner = ora10(`Fetching issue ${id}...`).start();
3515
+ try {
3516
+ const apiKey = getLinearApiKey3();
3517
+ if (!apiKey) {
3518
+ spinner.fail("LINEAR_API_KEY not found");
3519
+ console.log("");
3520
+ console.log(chalk20.dim("Set it in ~/.panopticon.env:"));
3521
+ console.log(" LINEAR_API_KEY=lin_api_xxxxx");
3522
+ process.exit(1);
3523
+ }
3524
+ const client = new LinearClient({ apiKey });
3525
+ spinner.text = "Fetching issue and comments...";
3526
+ const { issue, comments } = await fetchIssueWithComments(client, id);
3527
+ spinner.stop();
3528
+ console.log("");
3529
+ console.log(chalk20.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"));
3530
+ console.log(chalk20.bold(` Reopen: ${issue.identifier}`));
3531
+ console.log(chalk20.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"));
3532
+ console.log("");
3533
+ console.log(chalk20.bold("Title:"), issue.title);
3534
+ console.log(chalk20.bold("Current State:"), issue.state);
3535
+ console.log(chalk20.bold("URL:"), issue.url);
3536
+ console.log("");
3537
+ if (issue.description) {
3538
+ console.log(chalk20.bold("Description:"));
3539
+ const descPreview = issue.description.length > 300 ? issue.description.slice(0, 300) + "..." : issue.description;
3540
+ console.log(chalk20.dim(descPreview));
3541
+ console.log("");
3542
+ }
3543
+ console.log(chalk20.bold(`Comments (${comments.length}):`));
3544
+ if (comments.length > 0) {
3545
+ console.log(formatComments(comments));
3546
+ } else {
3547
+ console.log(chalk20.dim(" No comments"));
3548
+ }
3549
+ console.log("");
3550
+ if (options.json) {
3551
+ console.log(
3552
+ JSON.stringify(
3553
+ {
3554
+ issue,
3555
+ comments
3556
+ },
3557
+ null,
3558
+ 2
3559
+ )
3560
+ );
3561
+ return;
3562
+ }
3563
+ if (!options.force) {
3564
+ const confirm2 = await inquirer3.prompt([
3565
+ {
3566
+ type: "confirm",
3567
+ name: "proceed",
3568
+ message: `Reopen ${issue.identifier} and run planning?`,
3569
+ default: true
3570
+ }
3571
+ ]);
3572
+ if (!confirm2.proceed) {
3573
+ console.log(chalk20.yellow("Cancelled"));
3574
+ return;
3575
+ }
3576
+ }
3577
+ const reopenSpinner = ora10("Transitioning issue to backlog...").start();
3578
+ await reopenIssue(client, id);
3579
+ reopenSpinner.succeed(`Issue ${issue.identifier} reopened`);
3580
+ const commentSpinner = ora10("Adding reopen comment...").start();
3581
+ await client.createComment({
3582
+ issueId: issue.id,
3583
+ body: `Issue reopened for re-planning via Panopticon.
3584
+
3585
+ Previous state: ${issue.state}`
3586
+ });
3587
+ commentSpinner.succeed("Added reopen comment");
3588
+ if (!options.skipPlan) {
3589
+ console.log("");
3590
+ console.log(chalk20.cyan("Running planning workflow..."));
3591
+ console.log("");
3592
+ await planCommand(id, { force: true });
3593
+ } else {
3594
+ console.log("");
3595
+ console.log(chalk20.green("Issue reopened. Run planning manually:"));
3596
+ console.log(` pan work plan ${id}`);
3597
+ console.log("");
3598
+ }
3599
+ } catch (error) {
3600
+ if (spinner.isSpinning) spinner.fail();
3601
+ console.error(chalk20.red(`Error: ${error.message}`));
3602
+ process.exit(1);
3603
+ }
3604
+ }
3605
+
3434
3606
  // src/cli/commands/work/index.ts
3435
3607
  function registerWorkCommands(program2) {
3436
3608
  const work = program2.command("work").description("Agent and work management");
@@ -3443,7 +3615,7 @@ function registerWorkCommands(program2) {
3443
3615
  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);
3444
3616
  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);
3445
3617
  work.command("triage [id]").description("Triage secondary tracker issues").option("--create", "Create primary issue from secondary").option("--dismiss <reason>", "Dismiss from triage").action(triageCommand);
3446
- work.command("hook [action] [idOrMessage...]").description("GUPP hooks: check, push, pop, clear, mail, gupp").option("--json", "Output as JSON").action((action, idOrMessage, options) => {
3618
+ work.command("hook [action] [idOrMessage...]").description("FPP hooks: check, push, pop, clear, mail, fpp").option("--json", "Output as JSON").action((action, idOrMessage, options) => {
3447
3619
  hookCommand(action || "help", idOrMessage?.join(" "), options);
3448
3620
  });
3449
3621
  work.command("recover [id]").description("Recover crashed agents").option("--all", "Auto-recover all crashed agents").option("--json", "Output as JSON").action(recoverCommand);
@@ -3457,13 +3629,14 @@ function registerWorkCommands(program2) {
3457
3629
  interval: parseInt(options.interval, 10)
3458
3630
  });
3459
3631
  });
3632
+ work.command("reopen <id>").description("Reopen a closed/done issue and re-run planning").option("--skip-plan", "Skip automatic planning after reopen").option("--json", "Output as JSON").option("--force", "Skip confirmation").action(reopenCommand);
3460
3633
  }
3461
3634
 
3462
3635
  // src/cli/commands/workspace.ts
3463
- import chalk20 from "chalk";
3464
- import ora10 from "ora";
3465
- import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "fs";
3466
- import { join as join15, basename as basename2 } from "path";
3636
+ import chalk21 from "chalk";
3637
+ import ora11 from "ora";
3638
+ import { existsSync as existsSync17, mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "fs";
3639
+ import { join as join16, basename as basename2 } from "path";
3467
3640
 
3468
3641
  // src/lib/worktree.ts
3469
3642
  import { execSync as execSync6 } from "child_process";
@@ -3516,13 +3689,13 @@ function removeWorktree(repoPath, worktreePath) {
3516
3689
  }
3517
3690
 
3518
3691
  // src/lib/template.ts
3519
- import { readFileSync as readFileSync13, existsSync as existsSync14, readdirSync as readdirSync9 } from "fs";
3520
- import { join as join13 } from "path";
3692
+ import { readFileSync as readFileSync14, existsSync as existsSync15, readdirSync as readdirSync9 } from "fs";
3693
+ import { join as join14 } from "path";
3521
3694
  function loadTemplate(templatePath) {
3522
- if (!existsSync14(templatePath)) {
3695
+ if (!existsSync15(templatePath)) {
3523
3696
  throw new Error(`Template not found: ${templatePath}`);
3524
3697
  }
3525
- return readFileSync13(templatePath, "utf8");
3698
+ return readFileSync14(templatePath, "utf8");
3526
3699
  }
3527
3700
  function substituteVariables(template, variables) {
3528
3701
  let result = template;
@@ -3543,17 +3716,17 @@ function generateClaudeMd(projectPath, variables) {
3543
3716
  "warnings.md"
3544
3717
  ];
3545
3718
  for (const section of defaultOrder) {
3546
- const sectionPath = join13(CLAUDE_MD_TEMPLATES, section);
3547
- if (existsSync14(sectionPath)) {
3719
+ const sectionPath = join14(CLAUDE_MD_TEMPLATES, section);
3720
+ if (existsSync15(sectionPath)) {
3548
3721
  const content = loadTemplate(sectionPath);
3549
3722
  sections.push(substituteVariables(content, variables));
3550
3723
  }
3551
3724
  }
3552
- const projectSections = join13(projectPath, ".panopticon", "claude-md", "sections");
3553
- if (existsSync14(projectSections)) {
3725
+ const projectSections = join14(projectPath, ".panopticon", "claude-md", "sections");
3726
+ if (existsSync15(projectSections)) {
3554
3727
  const projectFiles = readdirSync9(projectSections).filter((f) => f.endsWith(".md")).sort();
3555
3728
  for (const file of projectFiles) {
3556
- const content = loadTemplate(join13(projectSections, file));
3729
+ const content = loadTemplate(join14(projectSections, file));
3557
3730
  sections.push(substituteVariables(content, variables));
3558
3731
  }
3559
3732
  }
@@ -3574,7 +3747,7 @@ This workspace was created by Panopticon. Use \`bd\` commands to track your work
3574
3747
 
3575
3748
  // src/lib/skills-merge.ts
3576
3749
  import {
3577
- existsSync as existsSync15,
3750
+ existsSync as existsSync16,
3578
3751
  readdirSync as readdirSync10,
3579
3752
  lstatSync,
3580
3753
  readlinkSync,
@@ -3582,7 +3755,7 @@ import {
3582
3755
  mkdirSync as mkdirSync10,
3583
3756
  appendFileSync as appendFileSync2
3584
3757
  } from "fs";
3585
- import { join as join14 } from "path";
3758
+ import { join as join15 } from "path";
3586
3759
  import { execSync as execSync7 } from "child_process";
3587
3760
  function detectContentOrigin(path, repoPath) {
3588
3761
  try {
@@ -3607,21 +3780,21 @@ function detectContentOrigin(path, repoPath) {
3607
3780
  }
3608
3781
  }
3609
3782
  function mergeSkillsIntoWorkspace(workspacePath) {
3610
- const skillsTarget = join14(workspacePath, ".claude", "skills");
3783
+ const skillsTarget = join15(workspacePath, ".claude", "skills");
3611
3784
  const added = [];
3612
3785
  const skipped = [];
3613
3786
  mkdirSync10(skillsTarget, { recursive: true });
3614
3787
  const existingSkills = /* @__PURE__ */ new Set();
3615
- if (existsSync15(skillsTarget)) {
3788
+ if (existsSync16(skillsTarget)) {
3616
3789
  for (const item of readdirSync10(skillsTarget)) {
3617
3790
  existingSkills.add(item);
3618
3791
  }
3619
3792
  }
3620
- if (!existsSync15(SKILLS_DIR)) return { added, skipped };
3793
+ if (!existsSync16(SKILLS_DIR)) return { added, skipped };
3621
3794
  const panopticonSkills = readdirSync10(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
3622
3795
  for (const skill of panopticonSkills) {
3623
- const targetPath = join14(skillsTarget, skill);
3624
- const sourcePath = join14(SKILLS_DIR, skill);
3796
+ const targetPath = join15(skillsTarget, skill);
3797
+ const sourcePath = join15(SKILLS_DIR, skill);
3625
3798
  if (existingSkills.has(skill)) {
3626
3799
  const origin = detectContentOrigin(targetPath, workspacePath);
3627
3800
  if (origin === "git-tracked") {
@@ -3647,7 +3820,7 @@ function mergeSkillsIntoWorkspace(workspacePath) {
3647
3820
  return { added, skipped };
3648
3821
  }
3649
3822
  function updateGitignore(skillsDir, skills) {
3650
- const gitignorePath = join14(skillsDir, ".gitignore");
3823
+ const gitignorePath = join15(skillsDir, ".gitignore");
3651
3824
  const content = `# Panopticon-managed symlinks (not committed)
3652
3825
  ${skills.join("\n")}
3653
3826
  `;
@@ -3665,7 +3838,7 @@ function registerWorkspaceCommands(program2) {
3665
3838
  workspace.command("destroy <issueId>").description("Destroy workspace").option("--force", "Force removal even with uncommitted changes").option("--project <path>", "Explicit project path (overrides registry)").action(destroyCommand);
3666
3839
  }
3667
3840
  async function createCommand(issueId, options) {
3668
- const spinner = ora10("Creating workspace...").start();
3841
+ const spinner = ora11("Creating workspace...").start();
3669
3842
  try {
3670
3843
  const normalizedId = issueId.toLowerCase().replace(/[^a-z0-9-]/g, "-");
3671
3844
  const branchName = `feature/${normalizedId}`;
@@ -3689,29 +3862,29 @@ async function createCommand(issueId, options) {
3689
3862
  projectRoot = process.cwd();
3690
3863
  }
3691
3864
  }
3692
- const workspacesDir = join15(projectRoot, "workspaces");
3693
- const workspacePath = join15(workspacesDir, folderName);
3865
+ const workspacesDir = join16(projectRoot, "workspaces");
3866
+ const workspacePath = join16(workspacesDir, folderName);
3694
3867
  if (options.dryRun) {
3695
3868
  spinner.info("Dry run mode");
3696
3869
  console.log("");
3697
- console.log(chalk20.bold("Would create:"));
3870
+ console.log(chalk21.bold("Would create:"));
3698
3871
  if (projectName) {
3699
- console.log(` Project: ${chalk20.green(projectName)}`);
3872
+ console.log(` Project: ${chalk21.green(projectName)}`);
3700
3873
  }
3701
- console.log(` Root: ${chalk20.dim(projectRoot)}`);
3702
- console.log(` Workspace: ${chalk20.cyan(workspacePath)}`);
3703
- console.log(` Branch: ${chalk20.cyan(branchName)}`);
3704
- console.log(` CLAUDE.md: ${chalk20.dim(join15(workspacePath, "CLAUDE.md"))}`);
3874
+ console.log(` Root: ${chalk21.dim(projectRoot)}`);
3875
+ console.log(` Workspace: ${chalk21.cyan(workspacePath)}`);
3876
+ console.log(` Branch: ${chalk21.cyan(branchName)}`);
3877
+ console.log(` CLAUDE.md: ${chalk21.dim(join16(workspacePath, "CLAUDE.md"))}`);
3705
3878
  if (options.skills !== false) {
3706
- console.log(` Skills: ${chalk20.dim(join15(workspacePath, ".claude", "skills"))}`);
3879
+ console.log(` Skills: ${chalk21.dim(join16(workspacePath, ".claude", "skills"))}`);
3707
3880
  }
3708
3881
  return;
3709
3882
  }
3710
- if (existsSync16(workspacePath)) {
3883
+ if (existsSync17(workspacePath)) {
3711
3884
  spinner.fail(`Workspace already exists: ${workspacePath}`);
3712
3885
  process.exit(1);
3713
3886
  }
3714
- if (!existsSync16(join15(projectRoot, ".git"))) {
3887
+ if (!existsSync17(join16(projectRoot, ".git"))) {
3715
3888
  spinner.fail("Not a git repository. Run this from the project root.");
3716
3889
  process.exit(1);
3717
3890
  }
@@ -3728,31 +3901,31 @@ async function createCommand(issueId, options) {
3728
3901
  PROJECT_NAME: projectName
3729
3902
  };
3730
3903
  const claudeMd = generateClaudeMd(projectRoot, variables);
3731
- writeFileSync10(join15(workspacePath, "CLAUDE.md"), claudeMd);
3904
+ writeFileSync10(join16(workspacePath, "CLAUDE.md"), claudeMd);
3732
3905
  let skillsResult = { added: [], skipped: [] };
3733
3906
  if (options.skills !== false) {
3734
3907
  spinner.text = "Merging skills...";
3735
- mkdirSync11(join15(workspacePath, ".claude", "skills"), { recursive: true });
3908
+ mkdirSync11(join16(workspacePath, ".claude", "skills"), { recursive: true });
3736
3909
  skillsResult = mergeSkillsIntoWorkspace(workspacePath);
3737
3910
  }
3738
3911
  spinner.succeed("Workspace created!");
3739
3912
  console.log("");
3740
- console.log(chalk20.bold("Workspace Details:"));
3913
+ console.log(chalk21.bold("Workspace Details:"));
3741
3914
  if (projectName) {
3742
- console.log(` Project: ${chalk20.green(projectName)}`);
3915
+ console.log(` Project: ${chalk21.green(projectName)}`);
3743
3916
  }
3744
- console.log(` Path: ${chalk20.cyan(workspacePath)}`);
3745
- console.log(` Branch: ${chalk20.dim(branchName)}`);
3917
+ console.log(` Path: ${chalk21.cyan(workspacePath)}`);
3918
+ console.log(` Branch: ${chalk21.dim(branchName)}`);
3746
3919
  console.log("");
3747
3920
  if (options.skills !== false) {
3748
- console.log(chalk20.bold("Skills:"));
3921
+ console.log(chalk21.bold("Skills:"));
3749
3922
  console.log(` Added: ${skillsResult.added.length} Panopticon skills`);
3750
3923
  if (skillsResult.skipped.length > 0) {
3751
- console.log(` Skipped: ${chalk20.dim(skillsResult.skipped.join(", "))}`);
3924
+ console.log(` Skipped: ${chalk21.dim(skillsResult.skipped.join(", "))}`);
3752
3925
  }
3753
3926
  console.log("");
3754
3927
  }
3755
- console.log(chalk20.dim(`Next: cd ${workspacePath}`));
3928
+ console.log(chalk21.dim(`Next: cd ${workspacePath}`));
3756
3929
  } catch (error) {
3757
3930
  spinner.fail(error.message);
3758
3931
  process.exit(1);
@@ -3764,7 +3937,7 @@ async function listCommand2(options) {
3764
3937
  if (projects.length > 0 && options.all) {
3765
3938
  const allWorkspaces = [];
3766
3939
  for (const { key, config } of projects) {
3767
- if (!existsSync16(join15(config.path, ".git"))) {
3940
+ if (!existsSync17(join16(config.path, ".git"))) {
3768
3941
  continue;
3769
3942
  }
3770
3943
  const worktrees2 = listWorktrees(config.path);
@@ -3784,29 +3957,29 @@ async function listCommand2(options) {
3784
3957
  return;
3785
3958
  }
3786
3959
  if (allWorkspaces.length === 0) {
3787
- console.log(chalk20.dim("No workspaces found in any registered project."));
3788
- console.log(chalk20.dim("Create one with: pan workspace create <issue-id>"));
3960
+ console.log(chalk21.dim("No workspaces found in any registered project."));
3961
+ console.log(chalk21.dim("Create one with: pan workspace create <issue-id>"));
3789
3962
  return;
3790
3963
  }
3791
3964
  for (const proj of allWorkspaces) {
3792
- console.log(chalk20.bold(`
3965
+ console.log(chalk21.bold(`
3793
3966
  ${proj.projectName}
3794
3967
  `));
3795
3968
  for (const ws of proj.workspaces) {
3796
3969
  const name = basename2(ws.path);
3797
- const status = ws.prunable ? chalk20.yellow(" (prunable)") : "";
3798
- console.log(` ${chalk20.cyan(name)}${status}`);
3799
- console.log(` Branch: ${ws.branch || chalk20.dim("(detached)")}`);
3800
- console.log(` Path: ${chalk20.dim(ws.path)}`);
3970
+ const status = ws.prunable ? chalk21.yellow(" (prunable)") : "";
3971
+ console.log(` ${chalk21.cyan(name)}${status}`);
3972
+ console.log(` Branch: ${ws.branch || chalk21.dim("(detached)")}`);
3973
+ console.log(` Path: ${chalk21.dim(ws.path)}`);
3801
3974
  }
3802
3975
  }
3803
3976
  return;
3804
3977
  }
3805
3978
  const projectRoot = process.cwd();
3806
- if (!existsSync16(join15(projectRoot, ".git"))) {
3807
- console.error(chalk20.red("Not a git repository."));
3979
+ if (!existsSync17(join16(projectRoot, ".git"))) {
3980
+ console.error(chalk21.red("Not a git repository."));
3808
3981
  if (projects.length > 0) {
3809
- console.log(chalk20.dim("Tip: Use --all to list workspaces across all registered projects."));
3982
+ console.log(chalk21.dim("Tip: Use --all to list workspaces across all registered projects."));
3810
3983
  }
3811
3984
  process.exit(1);
3812
3985
  }
@@ -3819,25 +3992,25 @@ ${proj.projectName}
3819
3992
  return;
3820
3993
  }
3821
3994
  if (workspaces.length === 0) {
3822
- console.log(chalk20.dim("No workspaces found."));
3823
- console.log(chalk20.dim("Create one with: pan workspace create <issue-id>"));
3995
+ console.log(chalk21.dim("No workspaces found."));
3996
+ console.log(chalk21.dim("Create one with: pan workspace create <issue-id>"));
3824
3997
  if (projects.length > 0) {
3825
- console.log(chalk20.dim("Tip: Use --all to list workspaces across all registered projects."));
3998
+ console.log(chalk21.dim("Tip: Use --all to list workspaces across all registered projects."));
3826
3999
  }
3827
4000
  return;
3828
4001
  }
3829
- console.log(chalk20.bold("\nWorkspaces\n"));
4002
+ console.log(chalk21.bold("\nWorkspaces\n"));
3830
4003
  for (const ws of workspaces) {
3831
4004
  const name = basename2(ws.path);
3832
- const status = ws.prunable ? chalk20.yellow(" (prunable)") : "";
3833
- console.log(`${chalk20.cyan(name)}${status}`);
3834
- console.log(` Branch: ${ws.branch || chalk20.dim("(detached)")}`);
3835
- console.log(` Path: ${chalk20.dim(ws.path)}`);
4005
+ const status = ws.prunable ? chalk21.yellow(" (prunable)") : "";
4006
+ console.log(`${chalk21.cyan(name)}${status}`);
4007
+ console.log(` Branch: ${ws.branch || chalk21.dim("(detached)")}`);
4008
+ console.log(` Path: ${chalk21.dim(ws.path)}`);
3836
4009
  console.log("");
3837
4010
  }
3838
4011
  }
3839
4012
  async function destroyCommand(issueId, options) {
3840
- const spinner = ora10("Destroying workspace...").start();
4013
+ const spinner = ora11("Destroying workspace...").start();
3841
4014
  try {
3842
4015
  const normalizedId = issueId.toLowerCase().replace(/[^a-z0-9-]/g, "-");
3843
4016
  const folderName = `feature-${normalizedId}`;
@@ -3852,36 +4025,36 @@ async function destroyCommand(issueId, options) {
3852
4025
  projectRoot = process.cwd();
3853
4026
  }
3854
4027
  }
3855
- const workspacePath = join15(projectRoot, "workspaces", folderName);
3856
- if (!existsSync16(workspacePath)) {
3857
- const cwdPath = join15(process.cwd(), "workspaces", folderName);
3858
- if (projectRoot !== process.cwd() && existsSync16(cwdPath)) {
4028
+ const workspacePath = join16(projectRoot, "workspaces", folderName);
4029
+ if (!existsSync17(workspacePath)) {
4030
+ const cwdPath = join16(process.cwd(), "workspaces", folderName);
4031
+ if (projectRoot !== process.cwd() && existsSync17(cwdPath)) {
3859
4032
  projectRoot = process.cwd();
3860
4033
  } else {
3861
4034
  spinner.fail(`Workspace not found: ${workspacePath}`);
3862
4035
  process.exit(1);
3863
4036
  }
3864
4037
  }
3865
- const finalWorkspacePath = join15(projectRoot, "workspaces", folderName);
4038
+ const finalWorkspacePath = join16(projectRoot, "workspaces", folderName);
3866
4039
  spinner.text = "Removing git worktree...";
3867
4040
  removeWorktree(projectRoot, finalWorkspacePath);
3868
4041
  spinner.succeed(`Workspace destroyed: ${folderName}`);
3869
4042
  } catch (error) {
3870
4043
  spinner.fail(error.message);
3871
4044
  if (!options.force) {
3872
- console.log(chalk20.dim("Tip: Use --force to remove even with uncommitted changes"));
4045
+ console.log(chalk21.dim("Tip: Use --force to remove even with uncommitted changes"));
3873
4046
  }
3874
4047
  process.exit(1);
3875
4048
  }
3876
4049
  }
3877
4050
 
3878
4051
  // src/cli/commands/install.ts
3879
- import chalk21 from "chalk";
3880
- import ora11 from "ora";
4052
+ import chalk22 from "chalk";
4053
+ import ora12 from "ora";
3881
4054
  import { execSync as execSync8 } from "child_process";
3882
- import { existsSync as existsSync17, mkdirSync as mkdirSync12, readFileSync as readFileSync14, copyFileSync, readdirSync as readdirSync11, statSync } from "fs";
3883
- import { join as join16 } from "path";
3884
- import { homedir as homedir5, platform } from "os";
4055
+ import { existsSync as existsSync18, mkdirSync as mkdirSync12, writeFileSync as writeFileSync11, readFileSync as readFileSync15, copyFileSync, readdirSync as readdirSync11, statSync } from "fs";
4056
+ import { join as join17 } from "path";
4057
+ import { homedir as homedir6, platform } from "os";
3885
4058
  function registerInstallCommand(program2) {
3886
4059
  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);
3887
4060
  }
@@ -3889,7 +4062,7 @@ function detectPlatform() {
3889
4062
  const os = platform();
3890
4063
  if (os === "linux") {
3891
4064
  try {
3892
- const release = readFileSync14("/proc/version", "utf8").toLowerCase();
4065
+ const release = readFileSync15("/proc/version", "utf8").toLowerCase();
3893
4066
  if (release.includes("microsoft") || release.includes("wsl")) {
3894
4067
  return "wsl";
3895
4068
  }
@@ -3900,14 +4073,14 @@ function detectPlatform() {
3900
4073
  return os;
3901
4074
  }
3902
4075
  function copyDirectoryRecursive(source, dest) {
3903
- if (!existsSync17(source)) {
4076
+ if (!existsSync18(source)) {
3904
4077
  throw new Error(`Source directory not found: ${source}`);
3905
4078
  }
3906
4079
  mkdirSync12(dest, { recursive: true });
3907
4080
  const entries = readdirSync11(source);
3908
4081
  for (const entry of entries) {
3909
- const sourcePath = join16(source, entry);
3910
- const destPath = join16(dest, entry);
4082
+ const sourcePath = join17(source, entry);
4083
+ const destPath = join17(dest, entry);
3911
4084
  const stat = statSync(sourcePath);
3912
4085
  if (stat.isDirectory()) {
3913
4086
  copyDirectoryRecursive(sourcePath, destPath);
@@ -3977,7 +4150,7 @@ function checkPrerequisites() {
3977
4150
  message: hasBeads ? "installed" : "not found",
3978
4151
  fix: "cargo install beads-cli"
3979
4152
  });
3980
- const hasTtyd = checkCommand("ttyd") || existsSync17(join16(homedir5(), "bin", "ttyd"));
4153
+ const hasTtyd = checkCommand("ttyd") || existsSync18(join17(homedir6(), "bin", "ttyd"));
3981
4154
  results.push({
3982
4155
  name: "ttyd",
3983
4156
  passed: hasTtyd,
@@ -3990,21 +4163,21 @@ function checkPrerequisites() {
3990
4163
  };
3991
4164
  }
3992
4165
  function printPrereqStatus(prereqs) {
3993
- console.log(chalk21.bold("Prerequisites:\n"));
4166
+ console.log(chalk22.bold("Prerequisites:\n"));
3994
4167
  for (const result of prereqs.results) {
3995
- const icon = result.passed ? chalk21.green("\u2713") : chalk21.red("\u2717");
3996
- const msg = result.passed ? chalk21.dim(result.message) : chalk21.yellow(result.message);
4168
+ const icon = result.passed ? chalk22.green("\u2713") : chalk22.red("\u2717");
4169
+ const msg = result.passed ? chalk22.dim(result.message) : chalk22.yellow(result.message);
3997
4170
  console.log(` ${icon} ${result.name}: ${msg}`);
3998
4171
  if (!result.passed && result.fix) {
3999
- console.log(` ${chalk21.dim("\u2192 " + result.fix)}`);
4172
+ console.log(` ${chalk22.dim("\u2192 " + result.fix)}`);
4000
4173
  }
4001
4174
  }
4002
4175
  console.log("");
4003
4176
  }
4004
4177
  async function installCommand(options) {
4005
- console.log(chalk21.bold("\nPanopticon Installation\n"));
4178
+ console.log(chalk22.bold("\nPanopticon Installation\n"));
4006
4179
  const plat = detectPlatform();
4007
- console.log(`Platform: ${chalk21.cyan(plat)}
4180
+ console.log(`Platform: ${chalk22.cyan(plat)}
4008
4181
  `);
4009
4182
  const prereqs = checkPrerequisites();
4010
4183
  if (options.check) {
@@ -4013,11 +4186,11 @@ async function installCommand(options) {
4013
4186
  }
4014
4187
  printPrereqStatus(prereqs);
4015
4188
  if (!prereqs.allPassed) {
4016
- console.log(chalk21.red("Fix prerequisites above before continuing."));
4017
- console.log(chalk21.dim("Tip: Run with --minimal to skip optional components"));
4189
+ console.log(chalk22.red("Fix prerequisites above before continuing."));
4190
+ console.log(chalk22.dim("Tip: Run with --minimal to skip optional components"));
4018
4191
  process.exit(1);
4019
4192
  }
4020
- const spinner = ora11("Initializing Panopticon directories...").start();
4193
+ const spinner = ora12("Initializing Panopticon directories...").start();
4021
4194
  for (const dir of INIT_DIRS) {
4022
4195
  mkdirSync12(dir, { recursive: true });
4023
4196
  }
@@ -4039,14 +4212,14 @@ async function installCommand(options) {
4039
4212
  execSync8("mkcert -install", { stdio: "pipe" });
4040
4213
  spinner.succeed("mkcert CA installed");
4041
4214
  spinner.start("Generating wildcard certificates...");
4042
- const traefikCertFile = join16(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost.pem");
4043
- const traefikKeyFile = join16(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost-key.pem");
4215
+ const traefikCertFile = join17(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost.pem");
4216
+ const traefikKeyFile = join17(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost-key.pem");
4044
4217
  execSync8(
4045
4218
  `mkcert -cert-file "${traefikCertFile}" -key-file "${traefikKeyFile}" "*.pan.localhost" "*.localhost" localhost 127.0.0.1 ::1`,
4046
4219
  { stdio: "pipe" }
4047
4220
  );
4048
- const legacyCertFile = join16(CERTS_DIR, "localhost.pem");
4049
- const legacyKeyFile = join16(CERTS_DIR, "localhost-key.pem");
4221
+ const legacyCertFile = join17(CERTS_DIR, "localhost.pem");
4222
+ const legacyKeyFile = join17(CERTS_DIR, "localhost-key.pem");
4050
4223
  copyFileSync(traefikCertFile, legacyCertFile);
4051
4224
  copyFileSync(traefikKeyFile, legacyKeyFile);
4052
4225
  spinner.succeed("Wildcard certificates generated (*.pan.localhost, *.localhost)");
@@ -4057,13 +4230,13 @@ async function installCommand(options) {
4057
4230
  spinner.info("Skipping mkcert (not installed)");
4058
4231
  }
4059
4232
  }
4060
- const hasTtyd = checkCommand("ttyd") || existsSync17(join16(homedir5(), "bin", "ttyd"));
4233
+ const hasTtyd = checkCommand("ttyd") || existsSync18(join17(homedir6(), "bin", "ttyd"));
4061
4234
  if (!hasTtyd) {
4062
4235
  spinner.start("Installing ttyd (web terminal)...");
4063
4236
  try {
4064
- const binDir = join16(homedir5(), "bin");
4237
+ const binDir = join17(homedir6(), "bin");
4065
4238
  mkdirSync12(binDir, { recursive: true });
4066
- const ttydPath = join16(binDir, "ttyd");
4239
+ const ttydPath = join17(binDir, "ttyd");
4067
4240
  const plat2 = detectPlatform();
4068
4241
  let downloadUrl = "";
4069
4242
  if (plat2 === "darwin") {
@@ -4094,19 +4267,31 @@ async function installCommand(options) {
4094
4267
  if (!options.minimal) {
4095
4268
  spinner.start("Setting up Traefik configuration...");
4096
4269
  try {
4097
- if (!existsSync17(join16(TRAEFIK_DIR, "docker-compose.yml"))) {
4270
+ if (!existsSync18(join17(TRAEFIK_DIR, "docker-compose.yml"))) {
4098
4271
  copyDirectoryRecursive(SOURCE_TRAEFIK_TEMPLATES, TRAEFIK_DIR);
4099
4272
  spinner.succeed("Traefik configuration created from templates");
4100
4273
  } else {
4101
4274
  spinner.info("Traefik configuration already exists (skipping)");
4102
4275
  }
4276
+ const existingCompose = join17(TRAEFIK_DIR, "docker-compose.yml");
4277
+ if (existsSync18(existingCompose)) {
4278
+ const content = readFileSync15(existingCompose, "utf-8");
4279
+ if (content.includes("panopticon:") && !content.includes("external: true")) {
4280
+ const patched = content.replace(
4281
+ /networks:\s*\n\s*panopticon:\s*\n\s*name: panopticon\s*\n\s*driver: bridge/,
4282
+ "networks:\n panopticon:\n name: panopticon\n external: true # Network created by 'pan install'"
4283
+ );
4284
+ writeFileSync11(existingCompose, patched);
4285
+ spinner.info("Migrated Traefik config (added external: true to network)");
4286
+ }
4287
+ }
4103
4288
  } catch (error) {
4104
4289
  spinner.fail(`Failed to set up Traefik configuration: ${error}`);
4105
- console.log(chalk21.yellow("You can set up Traefik manually later"));
4290
+ console.log(chalk22.yellow("You can set up Traefik manually later"));
4106
4291
  }
4107
4292
  }
4108
- const configFile = join16(PANOPTICON_HOME, "config.toml");
4109
- if (!existsSync17(configFile)) {
4293
+ const configFile = join17(PANOPTICON_HOME, "config.toml");
4294
+ if (!existsSync18(configFile)) {
4110
4295
  spinner.start("Creating default config...");
4111
4296
  const config = getDefaultConfig();
4112
4297
  if (options.minimal) {
@@ -4124,30 +4309,30 @@ async function installCommand(options) {
4124
4309
  spinner.succeed("Config created");
4125
4310
  }
4126
4311
  console.log("");
4127
- console.log(chalk21.green.bold("Installation complete!"));
4312
+ console.log(chalk22.green.bold("Installation complete!"));
4128
4313
  console.log("");
4129
- console.log(chalk21.bold("Next steps:"));
4130
- console.log(` 1. Run ${chalk21.cyan("pan sync")} to sync skills to ~/.claude/`);
4314
+ console.log(chalk22.bold("Next steps:"));
4315
+ console.log(` 1. Run ${chalk22.cyan("pan sync")} to sync skills to ~/.claude/`);
4131
4316
  if (!options.minimal) {
4132
- console.log(` 2. Add to ${chalk21.yellow("/etc/hosts")}: ${chalk21.cyan("127.0.0.1 pan.localhost")}`);
4133
- console.log(` 3. Run ${chalk21.cyan("pan up")} to start Traefik and dashboard`);
4134
- console.log(` 4. Access dashboard at ${chalk21.cyan("https://pan.localhost")}`);
4317
+ console.log(` 2. Add to ${chalk22.yellow("/etc/hosts")}: ${chalk22.cyan("127.0.0.1 pan.localhost")}`);
4318
+ console.log(` 3. Run ${chalk22.cyan("pan up")} to start Traefik and dashboard`);
4319
+ console.log(` 4. Access dashboard at ${chalk22.cyan("https://pan.localhost")}`);
4135
4320
  } else {
4136
- console.log(` 2. Run ${chalk21.cyan("pan up")} to start the dashboard`);
4137
- console.log(` 3. Access dashboard at ${chalk21.cyan("http://localhost:3001")}`);
4321
+ console.log(` 2. Run ${chalk22.cyan("pan up")} to start the dashboard`);
4322
+ console.log(` 3. Access dashboard at ${chalk22.cyan("http://localhost:3001")}`);
4138
4323
  }
4139
- console.log(` ${!options.minimal ? "5" : "4"}. Create a workspace with ${chalk21.cyan("pan workspace create <issue-id>")}`);
4324
+ console.log(` ${!options.minimal ? "5" : "4"}. Create a workspace with ${chalk22.cyan("pan workspace create <issue-id>")}`);
4140
4325
  console.log("");
4141
4326
  }
4142
4327
 
4143
4328
  // src/cli/commands/cloister/status.ts
4144
- import chalk22 from "chalk";
4329
+ import chalk23 from "chalk";
4145
4330
 
4146
4331
  // src/lib/cloister/config.ts
4147
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync18, mkdirSync as mkdirSync13 } from "fs";
4332
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, existsSync as existsSync19, mkdirSync as mkdirSync13 } from "fs";
4148
4333
  import { parse, stringify } from "@iarna/toml";
4149
- import { join as join17 } from "path";
4150
- var CLOISTER_CONFIG_FILE = join17(PANOPTICON_HOME, "cloister.toml");
4334
+ import { join as join18 } from "path";
4335
+ var CLOISTER_CONFIG_FILE = join18(PANOPTICON_HOME, "cloister.toml");
4151
4336
  var DEFAULT_CLOISTER_CONFIG = {
4152
4337
  startup: {
4153
4338
  auto_start: true
@@ -4250,15 +4435,15 @@ function deepMerge(defaults, overrides) {
4250
4435
  return result;
4251
4436
  }
4252
4437
  function loadCloisterConfig() {
4253
- if (!existsSync18(PANOPTICON_HOME)) {
4438
+ if (!existsSync19(PANOPTICON_HOME)) {
4254
4439
  mkdirSync13(PANOPTICON_HOME, { recursive: true });
4255
4440
  }
4256
- if (!existsSync18(CLOISTER_CONFIG_FILE)) {
4441
+ if (!existsSync19(CLOISTER_CONFIG_FILE)) {
4257
4442
  saveCloisterConfig(DEFAULT_CLOISTER_CONFIG);
4258
4443
  return DEFAULT_CLOISTER_CONFIG;
4259
4444
  }
4260
4445
  try {
4261
- const content = readFileSync15(CLOISTER_CONFIG_FILE, "utf-8");
4446
+ const content = readFileSync16(CLOISTER_CONFIG_FILE, "utf-8");
4262
4447
  const parsed = parse(content);
4263
4448
  return deepMerge(DEFAULT_CLOISTER_CONFIG, parsed);
4264
4449
  } catch (error) {
@@ -4268,7 +4453,7 @@ function loadCloisterConfig() {
4268
4453
  }
4269
4454
  }
4270
4455
  function saveCloisterConfig(config) {
4271
- if (!existsSync18(PANOPTICON_HOME)) {
4456
+ if (!existsSync19(PANOPTICON_HOME)) {
4272
4457
  mkdirSync13(PANOPTICON_HOME, { recursive: true });
4273
4458
  }
4274
4459
  try {
@@ -4302,8 +4487,8 @@ function evaluateHealthState(timeSinceActivityMs, thresholds) {
4302
4487
  }
4303
4488
  function getAgentHealth2(agentId, runtime, thresholds) {
4304
4489
  const thresholdsMs = thresholds || getHealthThresholdsMs();
4305
- const isRunning2 = runtime.isRunning(agentId);
4306
- if (!isRunning2) {
4490
+ const isRunning3 = runtime.isRunning(agentId);
4491
+ if (!isRunning3) {
4307
4492
  return {
4308
4493
  agentId,
4309
4494
  state: "stuck",
@@ -4383,13 +4568,13 @@ function getHealthLabel(state) {
4383
4568
 
4384
4569
  // src/lib/cloister/database.ts
4385
4570
  import Database from "better-sqlite3";
4386
- import { join as join18 } from "path";
4387
- import { existsSync as existsSync19, mkdirSync as mkdirSync14 } from "fs";
4388
- var CLOISTER_DB_PATH = join18(PANOPTICON_HOME, "cloister.db");
4571
+ import { join as join19 } from "path";
4572
+ import { existsSync as existsSync20, mkdirSync as mkdirSync14 } from "fs";
4573
+ var CLOISTER_DB_PATH = join19(PANOPTICON_HOME, "cloister.db");
4389
4574
  var RETENTION_DAYS = 7;
4390
4575
  var db = null;
4391
4576
  function initHealthDatabase() {
4392
- if (!existsSync19(PANOPTICON_HOME)) {
4577
+ if (!existsSync20(PANOPTICON_HOME)) {
4393
4578
  mkdirSync14(PANOPTICON_HOME, { recursive: true });
4394
4579
  }
4395
4580
  db = new Database(CLOISTER_DB_PATH);
@@ -4455,17 +4640,17 @@ function cleanupOldEvents(database = getHealthDatabase(), retentionDays = RETENT
4455
4640
  }
4456
4641
 
4457
4642
  // src/lib/cloister/specialists.ts
4458
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync13, existsSync as existsSync21, mkdirSync as mkdirSync15, readdirSync as readdirSync13, unlinkSync as unlinkSync2 } from "fs";
4459
- import { join as join21, basename as basename4 } from "path";
4643
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync13, existsSync as existsSync22, mkdirSync as mkdirSync15, readdirSync as readdirSync13, unlinkSync as unlinkSync2, appendFileSync as appendFileSync3 } from "fs";
4644
+ import { join as join22, basename as basename4 } from "path";
4460
4645
  import { execSync as execSync9 } from "child_process";
4461
4646
 
4462
4647
  // src/lib/cost-parsers/jsonl-parser.ts
4463
- import { existsSync as existsSync20, readFileSync as readFileSync16, readdirSync as readdirSync12, statSync as statSync2 } from "fs";
4464
- import { join as join20, basename as basename3 } from "path";
4465
- import { homedir as homedir6 } from "os";
4648
+ import { existsSync as existsSync21, readFileSync as readFileSync17, readdirSync as readdirSync12, statSync as statSync2 } from "fs";
4649
+ import { join as join21, basename as basename3 } from "path";
4650
+ import { homedir as homedir7 } from "os";
4466
4651
 
4467
4652
  // src/lib/cost.ts
4468
- import { join as join19 } from "path";
4653
+ import { join as join20 } from "path";
4469
4654
  var DEFAULT_PRICING = [
4470
4655
  // Anthropic
4471
4656
  { provider: "anthropic", model: "claude-opus-4", inputPer1k: 0.015, outputPer1k: 0.075, cacheReadPer1k: 175e-5, cacheWritePer1k: 0.01875, currency: "USD" },
@@ -4502,15 +4687,15 @@ function getPricing(provider, model) {
4502
4687
  }
4503
4688
  return pricing || null;
4504
4689
  }
4505
- var BUDGETS_FILE = join19(COSTS_DIR, "budgets.json");
4690
+ var BUDGETS_FILE = join20(COSTS_DIR, "budgets.json");
4506
4691
 
4507
4692
  // src/lib/cost-parsers/jsonl-parser.ts
4508
- var CLAUDE_PROJECTS_DIR = join20(homedir6(), ".claude", "projects");
4693
+ var CLAUDE_PROJECTS_DIR = join21(homedir7(), ".claude", "projects");
4509
4694
  function getProjectDirs() {
4510
- if (!existsSync20(CLAUDE_PROJECTS_DIR)) {
4695
+ if (!existsSync21(CLAUDE_PROJECTS_DIR)) {
4511
4696
  return [];
4512
4697
  }
4513
- return readdirSync12(CLAUDE_PROJECTS_DIR).map((name) => join20(CLAUDE_PROJECTS_DIR, name)).filter((path) => {
4698
+ return readdirSync12(CLAUDE_PROJECTS_DIR).map((name) => join21(CLAUDE_PROJECTS_DIR, name)).filter((path) => {
4514
4699
  try {
4515
4700
  return statSync2(path).isDirectory();
4516
4701
  } catch {
@@ -4519,10 +4704,10 @@ function getProjectDirs() {
4519
4704
  });
4520
4705
  }
4521
4706
  function getSessionFiles(projectDir) {
4522
- if (!existsSync20(projectDir)) {
4707
+ if (!existsSync21(projectDir)) {
4523
4708
  return [];
4524
4709
  }
4525
- return readdirSync12(projectDir).filter((name) => name.endsWith(".jsonl")).map((name) => join20(projectDir, name)).sort((a, b) => {
4710
+ return readdirSync12(projectDir).filter((name) => name.endsWith(".jsonl")).map((name) => join21(projectDir, name)).sort((a, b) => {
4526
4711
  try {
4527
4712
  return statSync2(b).mtime.getTime() - statSync2(a).mtime.getTime();
4528
4713
  } catch {
@@ -4566,10 +4751,10 @@ function normalizeModelName(model) {
4566
4751
  return { provider: "anthropic", model: "claude-sonnet-4" };
4567
4752
  }
4568
4753
  function parseClaudeSession(sessionFile) {
4569
- if (!existsSync20(sessionFile)) {
4754
+ if (!existsSync21(sessionFile)) {
4570
4755
  return null;
4571
4756
  }
4572
- const content = readFileSync16(sessionFile, "utf-8");
4757
+ const content = readFileSync17(sessionFile, "utf-8");
4573
4758
  const lines = content.split("\n").filter((line) => line.trim());
4574
4759
  let sessionId = "";
4575
4760
  let startTime = "";
@@ -4636,8 +4821,8 @@ function parseClaudeSession(sessionFile) {
4636
4821
  }
4637
4822
 
4638
4823
  // src/lib/cloister/specialists.ts
4639
- var SPECIALISTS_DIR = join21(PANOPTICON_HOME, "specialists");
4640
- var REGISTRY_FILE = join21(SPECIALISTS_DIR, "registry.json");
4824
+ var SPECIALISTS_DIR = join22(PANOPTICON_HOME, "specialists");
4825
+ var REGISTRY_FILE = join22(SPECIALISTS_DIR, "registry.json");
4641
4826
  var DEFAULT_SPECIALISTS = [
4642
4827
  {
4643
4828
  name: "merge-agent",
@@ -4662,10 +4847,10 @@ var DEFAULT_SPECIALISTS = [
4662
4847
  }
4663
4848
  ];
4664
4849
  function initSpecialistsDirectory() {
4665
- if (!existsSync21(SPECIALISTS_DIR)) {
4850
+ if (!existsSync22(SPECIALISTS_DIR)) {
4666
4851
  mkdirSync15(SPECIALISTS_DIR, { recursive: true });
4667
4852
  }
4668
- if (!existsSync21(REGISTRY_FILE)) {
4853
+ if (!existsSync22(REGISTRY_FILE)) {
4669
4854
  const registry = {
4670
4855
  version: "1.0",
4671
4856
  specialists: DEFAULT_SPECIALISTS,
@@ -4677,7 +4862,7 @@ function initSpecialistsDirectory() {
4677
4862
  function loadRegistry() {
4678
4863
  initSpecialistsDirectory();
4679
4864
  try {
4680
- const content = readFileSync17(REGISTRY_FILE, "utf-8");
4865
+ const content = readFileSync18(REGISTRY_FILE, "utf-8");
4681
4866
  return JSON.parse(content);
4682
4867
  } catch (error) {
4683
4868
  console.error("Failed to load specialist registry:", error);
@@ -4689,7 +4874,7 @@ function loadRegistry() {
4689
4874
  }
4690
4875
  }
4691
4876
  function saveRegistry(registry) {
4692
- if (!existsSync21(SPECIALISTS_DIR)) {
4877
+ if (!existsSync22(SPECIALISTS_DIR)) {
4693
4878
  mkdirSync15(SPECIALISTS_DIR, { recursive: true });
4694
4879
  }
4695
4880
  registry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
@@ -4702,15 +4887,15 @@ function saveRegistry(registry) {
4702
4887
  }
4703
4888
  }
4704
4889
  function getSessionFilePath(name) {
4705
- return join21(SPECIALISTS_DIR, `${name}.session`);
4890
+ return join22(SPECIALISTS_DIR, `${name}.session`);
4706
4891
  }
4707
4892
  function getSessionId(name) {
4708
4893
  const sessionFile = getSessionFilePath(name);
4709
- if (!existsSync21(sessionFile)) {
4894
+ if (!existsSync22(sessionFile)) {
4710
4895
  return null;
4711
4896
  }
4712
4897
  try {
4713
- return readFileSync17(sessionFile, "utf-8").trim();
4898
+ return readFileSync18(sessionFile, "utf-8").trim();
4714
4899
  } catch (error) {
4715
4900
  console.error(`Failed to read session file for ${name}:`, error);
4716
4901
  return null;
@@ -4718,7 +4903,7 @@ function getSessionId(name) {
4718
4903
  }
4719
4904
  function clearSessionId(name) {
4720
4905
  const sessionFile = getSessionFilePath(name);
4721
- if (!existsSync21(sessionFile)) {
4906
+ if (!existsSync22(sessionFile)) {
4722
4907
  return false;
4723
4908
  }
4724
4909
  try {
@@ -4906,15 +5091,72 @@ async function initializeEnabledSpecialists() {
4906
5091
  }
4907
5092
  return results;
4908
5093
  }
5094
+ async function wakeSpecialist(name, taskPrompt, options = {}) {
5095
+ const { waitForReady = true, startIfNotRunning = true } = options;
5096
+ const tmuxSession = getTmuxSessionName(name);
5097
+ const sessionId = getSessionId(name);
5098
+ const wasAlreadyRunning = isRunning(name);
5099
+ if (!wasAlreadyRunning) {
5100
+ if (!startIfNotRunning) {
5101
+ return {
5102
+ success: false,
5103
+ message: `Specialist ${name} is not running`,
5104
+ wasAlreadyRunning: false,
5105
+ error: "not_running"
5106
+ };
5107
+ }
5108
+ const cwd = process.env.HOME || "/home/eltmon";
5109
+ try {
5110
+ const claudeCmd = sessionId ? `claude --resume "${sessionId}" --dangerously-skip-permissions` : `claude --dangerously-skip-permissions`;
5111
+ execSync9(
5112
+ `tmux new-session -d -s "${tmuxSession}" -c "${cwd}" "${claudeCmd}"`,
5113
+ { encoding: "utf-8" }
5114
+ );
5115
+ if (waitForReady) {
5116
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
5117
+ }
5118
+ } catch (error) {
5119
+ return {
5120
+ success: false,
5121
+ message: `Failed to start specialist ${name}: ${error.message}`,
5122
+ wasAlreadyRunning: false,
5123
+ error: error.message
5124
+ };
5125
+ }
5126
+ }
5127
+ try {
5128
+ const escapedPrompt = taskPrompt.replace(/'/g, "'\\''");
5129
+ execSync9(`tmux send-keys -t "${tmuxSession}" '${escapedPrompt}'`, { encoding: "utf-8" });
5130
+ await new Promise((resolve2) => setTimeout(resolve2, 200));
5131
+ execSync9(`tmux send-keys -t "${tmuxSession}" C-m`, { encoding: "utf-8" });
5132
+ recordWake(name, sessionId || void 0);
5133
+ return {
5134
+ success: true,
5135
+ message: wasAlreadyRunning ? `Sent task to running specialist ${name}` : `Started specialist ${name} and sent task`,
5136
+ tmuxSession,
5137
+ wasAlreadyRunning
5138
+ };
5139
+ } catch (error) {
5140
+ return {
5141
+ success: false,
5142
+ message: `Failed to send task to specialist ${name}: ${error.message}`,
5143
+ tmuxSession,
5144
+ wasAlreadyRunning,
5145
+ error: error.message
5146
+ };
5147
+ }
5148
+ }
4909
5149
  function checkSpecialistQueue(specialistName) {
4910
5150
  return checkHook(specialistName);
4911
5151
  }
5152
+ var FEEDBACK_DIR = join22(PANOPTICON_HOME, "specialists", "feedback");
5153
+ var FEEDBACK_LOG = join22(FEEDBACK_DIR, "feedback.jsonl");
4912
5154
 
4913
5155
  // src/lib/runtimes/claude-code.ts
4914
- import { existsSync as existsSync22, readFileSync as readFileSync18, statSync as statSync3, mkdirSync as mkdirSync16, writeFileSync as writeFileSync14 } from "fs";
4915
- import { join as join22 } from "path";
4916
- import { homedir as homedir7 } from "os";
4917
- var CLAUDE_PROJECTS_DIR2 = join22(homedir7(), ".claude", "projects");
5156
+ import { existsSync as existsSync23, readFileSync as readFileSync19, statSync as statSync3, mkdirSync as mkdirSync16, writeFileSync as writeFileSync14 } from "fs";
5157
+ import { join as join23 } from "path";
5158
+ import { homedir as homedir8 } from "os";
5159
+ var CLAUDE_PROJECTS_DIR2 = join23(homedir8(), ".claude", "projects");
4918
5160
  var ClaudeCodeRuntime = class {
4919
5161
  name = "claude-code";
4920
5162
  /**
@@ -4924,15 +5166,15 @@ var ClaudeCodeRuntime = class {
4924
5166
  * We need to find the project directory that contains sessions for this workspace.
4925
5167
  */
4926
5168
  getProjectDirForWorkspace(workspace) {
4927
- if (!existsSync22(CLAUDE_PROJECTS_DIR2)) {
5169
+ if (!existsSync23(CLAUDE_PROJECTS_DIR2)) {
4928
5170
  return null;
4929
5171
  }
4930
5172
  const projectDirs = getProjectDirs();
4931
5173
  for (const projectDir of projectDirs) {
4932
- const indexPath = join22(projectDir, "sessions-index.json");
4933
- if (existsSync22(indexPath)) {
5174
+ const indexPath = join23(projectDir, "sessions-index.json");
5175
+ if (existsSync23(indexPath)) {
4934
5176
  try {
4935
- const indexContent = readFileSync18(indexPath, "utf-8");
5177
+ const indexContent = readFileSync19(indexPath, "utf-8");
4936
5178
  if (indexContent.includes(workspace)) {
4937
5179
  return projectDir;
4938
5180
  }
@@ -4946,12 +5188,12 @@ var ClaudeCodeRuntime = class {
4946
5188
  * Get the active session ID for an agent from the sessions index
4947
5189
  */
4948
5190
  getActiveSessionId(projectDir) {
4949
- const indexPath = join22(projectDir, "sessions-index.json");
4950
- if (!existsSync22(indexPath)) {
5191
+ const indexPath = join23(projectDir, "sessions-index.json");
5192
+ if (!existsSync23(indexPath)) {
4951
5193
  return null;
4952
5194
  }
4953
5195
  try {
4954
- const indexContent = readFileSync18(indexPath, "utf-8");
5196
+ const indexContent = readFileSync19(indexPath, "utf-8");
4955
5197
  const index = JSON.parse(indexContent);
4956
5198
  if (index.sessions && Array.isArray(index.sessions)) {
4957
5199
  const sessions = index.sessions;
@@ -4986,8 +5228,8 @@ var ClaudeCodeRuntime = class {
4986
5228
  }
4987
5229
  const sessionId = this.getActiveSessionId(projectDir);
4988
5230
  if (sessionId) {
4989
- const sessionPath = join22(projectDir, `${sessionId}.jsonl`);
4990
- if (existsSync22(sessionPath)) {
5231
+ const sessionPath = join23(projectDir, `${sessionId}.jsonl`);
5232
+ if (existsSync23(sessionPath)) {
4991
5233
  return sessionPath;
4992
5234
  }
4993
5235
  }
@@ -5000,7 +5242,7 @@ var ClaudeCodeRuntime = class {
5000
5242
  */
5001
5243
  getLastActivity(agentId) {
5002
5244
  const sessionPath = this.getSessionPath(agentId);
5003
- if (!sessionPath || !existsSync22(sessionPath)) {
5245
+ if (!sessionPath || !existsSync23(sessionPath)) {
5004
5246
  return null;
5005
5247
  }
5006
5248
  try {
@@ -5014,12 +5256,12 @@ var ClaudeCodeRuntime = class {
5014
5256
  * Read active heartbeat file if it exists
5015
5257
  */
5016
5258
  getActiveHeartbeat(agentId) {
5017
- const heartbeatPath = join22(homedir7(), ".panopticon", "heartbeats", `${agentId}.json`);
5018
- if (!existsSync22(heartbeatPath)) {
5259
+ const heartbeatPath = join23(homedir8(), ".panopticon", "heartbeats", `${agentId}.json`);
5260
+ if (!existsSync23(heartbeatPath)) {
5019
5261
  return null;
5020
5262
  }
5021
5263
  try {
5022
- const content = readFileSync18(heartbeatPath, "utf-8");
5264
+ const content = readFileSync19(heartbeatPath, "utf-8");
5023
5265
  const data = JSON.parse(content);
5024
5266
  const timestamp = new Date(data.timestamp);
5025
5267
  const now = /* @__PURE__ */ new Date();
@@ -5123,11 +5365,11 @@ var ClaudeCodeRuntime = class {
5123
5365
  throw new Error(`Agent ${agentId} is not running`);
5124
5366
  }
5125
5367
  sendKeys(agentId, message);
5126
- const mailDir = join22(getAgentDir(agentId), "mail");
5368
+ const mailDir = join23(getAgentDir(agentId), "mail");
5127
5369
  mkdirSync16(mailDir, { recursive: true });
5128
5370
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5129
5371
  writeFileSync14(
5130
- join22(mailDir, `${timestamp}.md`),
5372
+ join23(mailDir, `${timestamp}.md`),
5131
5373
  `# Message
5132
5374
 
5133
5375
  ${message}
@@ -5293,8 +5535,8 @@ function getRuntimeForAgent(agentId) {
5293
5535
  }
5294
5536
 
5295
5537
  // src/lib/cloister/triggers.ts
5296
- import { existsSync as existsSync23 } from "fs";
5297
- import { join as join23 } from "path";
5538
+ import { existsSync as existsSync24 } from "fs";
5539
+ import { join as join24 } from "path";
5298
5540
  import { execSync as execSync10 } from "child_process";
5299
5541
  function checkStuckEscalation(health, currentModel, config) {
5300
5542
  const conf = config || loadCloisterConfig();
@@ -5384,8 +5626,8 @@ function checkPlanningComplete(agentId, workspace, issueId, config) {
5384
5626
  }
5385
5627
  } catch (error) {
5386
5628
  }
5387
- const prdPath = join23(workspace, `docs/prds/active/${issueId.toLowerCase()}-plan.md`);
5388
- if (existsSync23(prdPath)) {
5629
+ const prdPath = join24(workspace, `docs/prds/active/${issueId.toLowerCase()}-plan.md`);
5630
+ if (existsSync24(prdPath)) {
5389
5631
  signals.push("PRD file exists");
5390
5632
  signalCount++;
5391
5633
  }
@@ -5528,11 +5770,11 @@ function checkAllTriggers(agentId, workspace, issueId, currentModel, health, con
5528
5770
 
5529
5771
  // src/lib/cloister/handoff.ts
5530
5772
  import { writeFileSync as writeFileSync15, mkdirSync as mkdirSync17 } from "fs";
5531
- import { join as join25 } from "path";
5773
+ import { join as join26 } from "path";
5532
5774
 
5533
5775
  // src/lib/cloister/handoff-context.ts
5534
- import { existsSync as existsSync24, readFileSync as readFileSync19 } from "fs";
5535
- import { join as join24 } from "path";
5776
+ import { existsSync as existsSync25, readFileSync as readFileSync20 } from "fs";
5777
+ import { join as join25 } from "path";
5536
5778
  import { execSync as execSync11 } from "child_process";
5537
5779
  async function captureHandoffContext(agentState, targetModel, reason) {
5538
5780
  const context = {
@@ -5554,13 +5796,13 @@ async function captureHandoffContext(agentState, targetModel, reason) {
5554
5796
  }
5555
5797
  async function captureFiles(context, workspace) {
5556
5798
  try {
5557
- const stateFile = join24(workspace, ".planning/STATE.md");
5558
- if (existsSync24(stateFile)) {
5559
- context.stateFile = readFileSync19(stateFile, "utf-8");
5799
+ const stateFile = join25(workspace, ".planning/STATE.md");
5800
+ if (existsSync25(stateFile)) {
5801
+ context.stateFile = readFileSync20(stateFile, "utf-8");
5560
5802
  }
5561
- const claudeMd = join24(workspace, "CLAUDE.md");
5562
- if (existsSync24(claudeMd)) {
5563
- context.claudeMd = readFileSync19(claudeMd, "utf-8");
5803
+ const claudeMd = join25(workspace, "CLAUDE.md");
5804
+ if (existsSync25(claudeMd)) {
5805
+ context.claudeMd = readFileSync20(claudeMd, "utf-8");
5564
5806
  }
5565
5807
  } catch (error) {
5566
5808
  console.error("Error capturing files:", error);
@@ -5749,9 +5991,9 @@ async function performKillAndSpawn(state, options) {
5749
5991
  const context = await captureHandoffContext(state, options.targetModel, options.reason);
5750
5992
  stopAgent(state.id);
5751
5993
  const prompt = buildHandoffPrompt(context, options.additionalInstructions);
5752
- const handoffDir = join25(getAgentDir(state.id), "handoffs");
5994
+ const handoffDir = join26(getAgentDir(state.id), "handoffs");
5753
5995
  mkdirSync17(handoffDir, { recursive: true });
5754
- const handoffFile = join25(handoffDir, `handoff-${Date.now()}.md`);
5996
+ const handoffFile = join26(handoffDir, `handoff-${Date.now()}.md`);
5755
5997
  writeFileSync15(handoffFile, prompt);
5756
5998
  const newState = spawnAgent({
5757
5999
  issueId: state.issueId,
@@ -5790,8 +6032,25 @@ async function performSpecialistWake(state, options) {
5790
6032
  error: "Could not determine specialist name from agent ID"
5791
6033
  };
5792
6034
  }
5793
- console.warn(`Specialist wake not yet fully implemented, falling back to kill-spawn`);
5794
- return await performKillAndSpawn(state, options);
6035
+ const sessionId = getSessionId(specialistName);
6036
+ const tmuxSession = getTmuxSessionName(specialistName);
6037
+ console.log(`[handoff] Waking specialist ${specialistName} (session: ${sessionId || "none"})`);
6038
+ const wakeResult = await wakeSpecialist(specialistName, prompt, {
6039
+ waitForReady: true,
6040
+ startIfNotRunning: true
6041
+ });
6042
+ if (!wakeResult.success) {
6043
+ console.error(`[handoff] Failed to wake specialist: ${wakeResult.error}`);
6044
+ console.warn(`[handoff] Falling back to kill-spawn`);
6045
+ return await performKillAndSpawn(state, options);
6046
+ }
6047
+ console.log(`[handoff] Successfully woke specialist ${specialistName}`);
6048
+ return {
6049
+ success: true,
6050
+ method: "specialist-wake",
6051
+ newSessionId: sessionId || void 0,
6052
+ context
6053
+ };
5795
6054
  } catch (error) {
5796
6055
  return {
5797
6056
  success: false,
@@ -5824,19 +6083,19 @@ function sleep(ms) {
5824
6083
  }
5825
6084
 
5826
6085
  // src/lib/cloister/handoff-logger.ts
5827
- import { existsSync as existsSync26, mkdirSync as mkdirSync18, appendFileSync as appendFileSync3, readFileSync as readFileSync20 } from "fs";
5828
- import { join as join26 } from "path";
5829
- var HANDOFF_LOG_FILE = join26(PANOPTICON_HOME, "logs", "handoffs.jsonl");
6086
+ import { existsSync as existsSync27, mkdirSync as mkdirSync18, appendFileSync as appendFileSync4, readFileSync as readFileSync21, writeFileSync as writeFileSync16 } from "fs";
6087
+ import { join as join27 } from "path";
6088
+ var HANDOFF_LOG_FILE = join27(PANOPTICON_HOME, "logs", "handoffs.jsonl");
5830
6089
  function ensureLogDir() {
5831
- const logDir = join26(PANOPTICON_HOME, "logs");
5832
- if (!existsSync26(logDir)) {
6090
+ const logDir = join27(PANOPTICON_HOME, "logs");
6091
+ if (!existsSync27(logDir)) {
5833
6092
  mkdirSync18(logDir, { recursive: true });
5834
6093
  }
5835
6094
  }
5836
6095
  function logHandoffEvent(event) {
5837
6096
  ensureLogDir();
5838
6097
  const line = JSON.stringify(event) + "\n";
5839
- appendFileSync3(HANDOFF_LOG_FILE, line, "utf-8");
6098
+ appendFileSync4(HANDOFF_LOG_FILE, line, "utf-8");
5840
6099
  }
5841
6100
  function createHandoffEvent(agentId, issueId, context, trigger, success, errorMessage) {
5842
6101
  let stuckMinutes;
@@ -6262,8 +6521,8 @@ async function statusCommand2(options) {
6262
6521
  console.log(JSON.stringify(status, null, 2));
6263
6522
  return;
6264
6523
  }
6265
- console.log(chalk22.bold("\n\u{1F514} Cloister Agent Watchdog\n"));
6266
- const runningStatus = status.running ? chalk22.green("Running") : chalk22.red("Stopped");
6524
+ console.log(chalk23.bold("\n\u{1F514} Cloister Agent Watchdog\n"));
6525
+ const runningStatus = status.running ? chalk23.green("Running") : chalk23.red("Stopped");
6267
6526
  console.log(`Status: ${runningStatus}`);
6268
6527
  if (status.lastCheck) {
6269
6528
  const lastCheck = new Date(status.lastCheck);
@@ -6271,69 +6530,69 @@ async function statusCommand2(options) {
6271
6530
  console.log(`Last check: ${timeSince}s ago`);
6272
6531
  }
6273
6532
  console.log("");
6274
- console.log(chalk22.bold("Agent Health Summary:"));
6275
- console.log(` \u{1F7E2} Active: ${chalk22.green(status.summary.active)}`);
6276
- console.log(` \u{1F7E1} Stale: ${chalk22.yellow(status.summary.stale)}`);
6277
- console.log(` \u{1F7E0} Warning: ${chalk22.hex("#FFA500")(status.summary.warning)}`);
6278
- console.log(` \u{1F534} Stuck: ${chalk22.red(status.summary.stuck)}`);
6533
+ console.log(chalk23.bold("Agent Health Summary:"));
6534
+ console.log(` \u{1F7E2} Active: ${chalk23.green(status.summary.active)}`);
6535
+ console.log(` \u{1F7E1} Stale: ${chalk23.yellow(status.summary.stale)}`);
6536
+ console.log(` \u{1F7E0} Warning: ${chalk23.hex("#FFA500")(status.summary.warning)}`);
6537
+ console.log(` \u{1F534} Stuck: ${chalk23.red(status.summary.stuck)}`);
6279
6538
  console.log(` Total: ${status.summary.total}`);
6280
6539
  console.log("");
6281
6540
  if (status.agentsNeedingAttention.length > 0) {
6282
- console.log(chalk22.bold("\u26A0\uFE0F Agents Needing Attention:"));
6541
+ console.log(chalk23.bold("\u26A0\uFE0F Agents Needing Attention:"));
6283
6542
  for (const agentId of status.agentsNeedingAttention) {
6284
6543
  const health = service.getAgentHealth(agentId);
6285
6544
  if (health) {
6286
6545
  const emoji = getHealthEmoji(health.state);
6287
6546
  const label = getHealthLabel(health.state);
6288
- const color = health.state === "warning" ? chalk22.hex("#FFA500") : chalk22.red;
6547
+ const color = health.state === "warning" ? chalk23.hex("#FFA500") : chalk23.red;
6289
6548
  console.log(` ${emoji} ${color(agentId)} - ${label}`);
6290
6549
  }
6291
6550
  }
6292
6551
  console.log("");
6293
6552
  }
6294
- console.log(chalk22.bold("Configuration:"));
6553
+ console.log(chalk23.bold("Configuration:"));
6295
6554
  console.log(` Auto-start: ${status.config.startup.auto_start ? "enabled" : "disabled"}`);
6296
6555
  console.log(` Thresholds: stale=${status.config.thresholds.stale}m, warning=${status.config.thresholds.warning}m, stuck=${status.config.thresholds.stuck}m`);
6297
6556
  console.log(` Auto-actions:`);
6298
6557
  console.log(` - Poke on warning: ${status.config.auto_actions.poke_on_warning ? "enabled" : "disabled"}`);
6299
- console.log(` - Kill on stuck: ${status.config.auto_actions.kill_on_stuck ? chalk22.red("enabled") : "disabled"}`);
6558
+ console.log(` - Kill on stuck: ${status.config.auto_actions.kill_on_stuck ? chalk23.red("enabled") : "disabled"}`);
6300
6559
  console.log("");
6301
6560
  }
6302
6561
 
6303
6562
  // src/cli/commands/cloister/start.ts
6304
- import chalk23 from "chalk";
6563
+ import chalk24 from "chalk";
6305
6564
  async function startCommand() {
6306
6565
  const service = getCloisterService();
6307
6566
  if (service.isRunning()) {
6308
- console.log(chalk23.yellow("\u26A0\uFE0F Cloister is already running"));
6567
+ console.log(chalk24.yellow("\u26A0\uFE0F Cloister is already running"));
6309
6568
  return;
6310
6569
  }
6311
6570
  await service.start();
6312
- console.log(chalk23.green("\u2713 Cloister started"));
6313
- console.log(chalk23.dim(" Monitoring all running agents..."));
6571
+ console.log(chalk24.green("\u2713 Cloister started"));
6572
+ console.log(chalk24.dim(" Monitoring all running agents..."));
6314
6573
  }
6315
6574
 
6316
6575
  // src/cli/commands/cloister/stop.ts
6317
- import chalk24 from "chalk";
6576
+ import chalk25 from "chalk";
6318
6577
  async function stopCommand(options) {
6319
6578
  const service = getCloisterService();
6320
6579
  if (!service.isRunning() && !options.emergency) {
6321
- console.log(chalk24.yellow("\u26A0\uFE0F Cloister is not running"));
6580
+ console.log(chalk25.yellow("\u26A0\uFE0F Cloister is not running"));
6322
6581
  return;
6323
6582
  }
6324
6583
  if (options.emergency) {
6325
- console.log(chalk24.red.bold("\u{1F6A8} EMERGENCY STOP - Killing all agents"));
6326
- console.log(chalk24.dim(" This will terminate all running agent sessions"));
6584
+ console.log(chalk25.red.bold("\u{1F6A8} EMERGENCY STOP - Killing all agents"));
6585
+ console.log(chalk25.dim(" This will terminate all running agent sessions"));
6327
6586
  const killedAgents = service.emergencyStop();
6328
6587
  console.log("");
6329
- console.log(chalk24.green(`\u2713 Killed ${killedAgents.length} agent(s):`));
6588
+ console.log(chalk25.green(`\u2713 Killed ${killedAgents.length} agent(s):`));
6330
6589
  for (const agentId of killedAgents) {
6331
- console.log(chalk24.dim(` - ${agentId}`));
6590
+ console.log(chalk25.dim(` - ${agentId}`));
6332
6591
  }
6333
6592
  } else {
6334
6593
  service.stop();
6335
- console.log(chalk24.green("\u2713 Cloister stopped"));
6336
- console.log(chalk24.dim(" Agents are still running"));
6594
+ console.log(chalk25.green("\u2713 Cloister stopped"));
6595
+ console.log(chalk25.dim(" Agents are still running"));
6337
6596
  }
6338
6597
  }
6339
6598
 
@@ -6347,11 +6606,11 @@ function registerCloisterCommands(program2) {
6347
6606
  }
6348
6607
 
6349
6608
  // src/cli/commands/setup/hooks.ts
6350
- import chalk25 from "chalk";
6351
- import { readFileSync as readFileSync21, writeFileSync as writeFileSync16, existsSync as existsSync27, mkdirSync as mkdirSync19, copyFileSync as copyFileSync2, chmodSync as chmodSync2 } from "fs";
6352
- import { join as join27 } from "path";
6609
+ import chalk26 from "chalk";
6610
+ import { readFileSync as readFileSync22, writeFileSync as writeFileSync17, existsSync as existsSync28, mkdirSync as mkdirSync19, copyFileSync as copyFileSync2, chmodSync as chmodSync2 } from "fs";
6611
+ import { join as join28 } from "path";
6353
6612
  import { execSync as execSync12 } from "child_process";
6354
- import { homedir as homedir8 } from "os";
6613
+ import { homedir as homedir9 } from "os";
6355
6614
  function checkJqInstalled() {
6356
6615
  try {
6357
6616
  execSync12("which jq", { stdio: "pipe" });
@@ -6361,38 +6620,38 @@ function checkJqInstalled() {
6361
6620
  }
6362
6621
  }
6363
6622
  function installJq() {
6364
- console.log(chalk25.yellow("Installing jq dependency..."));
6623
+ console.log(chalk26.yellow("Installing jq dependency..."));
6365
6624
  try {
6366
6625
  const platform2 = process.platform;
6367
6626
  if (platform2 === "darwin") {
6368
6627
  try {
6369
6628
  execSync12("brew --version", { stdio: "pipe" });
6370
6629
  execSync12("brew install jq", { stdio: "inherit" });
6371
- console.log(chalk25.green("\u2713 jq installed via Homebrew"));
6630
+ console.log(chalk26.green("\u2713 jq installed via Homebrew"));
6372
6631
  return true;
6373
6632
  } catch {
6374
- console.log(chalk25.yellow("\u26A0 Homebrew not found"));
6633
+ console.log(chalk26.yellow("\u26A0 Homebrew not found"));
6375
6634
  }
6376
6635
  } else if (platform2 === "linux") {
6377
6636
  try {
6378
6637
  execSync12("apt-get --version", { stdio: "pipe" });
6379
6638
  execSync12("sudo apt-get update && sudo apt-get install -y jq", { stdio: "inherit" });
6380
- console.log(chalk25.green("\u2713 jq installed via apt"));
6639
+ console.log(chalk26.green("\u2713 jq installed via apt"));
6381
6640
  return true;
6382
6641
  } catch {
6383
6642
  try {
6384
6643
  execSync12("yum --version", { stdio: "pipe" });
6385
6644
  execSync12("sudo yum install -y jq", { stdio: "inherit" });
6386
- console.log(chalk25.green("\u2713 jq installed via yum"));
6645
+ console.log(chalk26.green("\u2713 jq installed via yum"));
6387
6646
  return true;
6388
6647
  } catch {
6389
- console.log(chalk25.yellow("\u26A0 No supported package manager found (apt/yum)"));
6648
+ console.log(chalk26.yellow("\u26A0 No supported package manager found (apt/yum)"));
6390
6649
  }
6391
6650
  }
6392
6651
  }
6393
6652
  return false;
6394
6653
  } catch (error) {
6395
- console.log(chalk25.red("\u2717 Failed to install jq automatically"));
6654
+ console.log(chalk26.red("\u2717 Failed to install jq automatically"));
6396
6655
  return false;
6397
6656
  }
6398
6657
  }
@@ -6405,73 +6664,73 @@ function hookAlreadyConfigured(settings, hookPath) {
6405
6664
  );
6406
6665
  }
6407
6666
  async function setupHooksCommand() {
6408
- console.log(chalk25.bold("Setting up Panopticon heartbeat hooks\n"));
6667
+ console.log(chalk26.bold("Setting up Panopticon heartbeat hooks\n"));
6409
6668
  if (!checkJqInstalled()) {
6410
- console.log(chalk25.yellow("\u26A0 jq is required for heartbeat hooks"));
6669
+ console.log(chalk26.yellow("\u26A0 jq is required for heartbeat hooks"));
6411
6670
  const installed = installJq();
6412
6671
  if (!installed) {
6413
- console.log(chalk25.red("\n\u2717 Setup failed: jq dependency missing"));
6414
- console.log(chalk25.dim("\nPlease install jq manually:"));
6415
- console.log(chalk25.dim(" macOS: brew install jq"));
6416
- console.log(chalk25.dim(" Ubuntu: sudo apt-get install jq"));
6417
- console.log(chalk25.dim(" CentOS: sudo yum install jq\n"));
6672
+ console.log(chalk26.red("\n\u2717 Setup failed: jq dependency missing"));
6673
+ console.log(chalk26.dim("\nPlease install jq manually:"));
6674
+ console.log(chalk26.dim(" macOS: brew install jq"));
6675
+ console.log(chalk26.dim(" Ubuntu: sudo apt-get install jq"));
6676
+ console.log(chalk26.dim(" CentOS: sudo yum install jq\n"));
6418
6677
  process.exit(1);
6419
6678
  }
6420
6679
  } else {
6421
- console.log(chalk25.green("\u2713 jq is installed"));
6680
+ console.log(chalk26.green("\u2713 jq is installed"));
6422
6681
  }
6423
- const panopticonHome = join27(homedir8(), ".panopticon");
6424
- const binDir = join27(panopticonHome, "bin");
6425
- const heartbeatsDir = join27(panopticonHome, "heartbeats");
6426
- if (!existsSync27(binDir)) {
6682
+ const panopticonHome = join28(homedir9(), ".panopticon");
6683
+ const binDir = join28(panopticonHome, "bin");
6684
+ const heartbeatsDir = join28(panopticonHome, "heartbeats");
6685
+ if (!existsSync28(binDir)) {
6427
6686
  mkdirSync19(binDir, { recursive: true });
6428
- console.log(chalk25.green("\u2713 Created ~/.panopticon/bin/"));
6687
+ console.log(chalk26.green("\u2713 Created ~/.panopticon/bin/"));
6429
6688
  }
6430
- if (!existsSync27(heartbeatsDir)) {
6689
+ if (!existsSync28(heartbeatsDir)) {
6431
6690
  mkdirSync19(heartbeatsDir, { recursive: true });
6432
- console.log(chalk25.green("\u2713 Created ~/.panopticon/heartbeats/"));
6691
+ console.log(chalk26.green("\u2713 Created ~/.panopticon/heartbeats/"));
6433
6692
  }
6434
- const scriptSource = join27(process.cwd(), "scripts", "heartbeat-hook");
6435
- const scriptDest = join27(binDir, "heartbeat-hook");
6693
+ const scriptSource = join28(process.cwd(), "scripts", "heartbeat-hook");
6694
+ const scriptDest = join28(binDir, "heartbeat-hook");
6436
6695
  let sourcePath = scriptSource;
6437
- if (!existsSync27(sourcePath)) {
6696
+ if (!existsSync28(sourcePath)) {
6438
6697
  const { fileURLToPath: fileURLToPath3 } = await import("url");
6439
6698
  const { dirname: dirname6 } = await import("path");
6440
6699
  const __dirname3 = dirname6(fileURLToPath3(import.meta.url));
6441
- const installedSource = join27(__dirname3, "..", "..", "..", "scripts", "heartbeat-hook");
6442
- if (existsSync27(installedSource)) {
6700
+ const installedSource = join28(__dirname3, "..", "..", "..", "scripts", "heartbeat-hook");
6701
+ if (existsSync28(installedSource)) {
6443
6702
  sourcePath = installedSource;
6444
6703
  } else {
6445
- console.log(chalk25.red("\u2717 Could not find heartbeat-hook script"));
6446
- console.log(chalk25.dim(` Checked: ${scriptSource}`));
6447
- console.log(chalk25.dim(` Checked: ${installedSource}`));
6704
+ console.log(chalk26.red("\u2717 Could not find heartbeat-hook script"));
6705
+ console.log(chalk26.dim(` Checked: ${scriptSource}`));
6706
+ console.log(chalk26.dim(` Checked: ${installedSource}`));
6448
6707
  process.exit(1);
6449
6708
  }
6450
6709
  }
6451
6710
  copyFileSync2(sourcePath, scriptDest);
6452
6711
  chmodSync2(scriptDest, 493);
6453
- console.log(chalk25.green("\u2713 Installed heartbeat-hook script"));
6454
- const claudeDir = join27(homedir8(), ".claude");
6455
- const settingsPath = join27(claudeDir, "settings.json");
6712
+ console.log(chalk26.green("\u2713 Installed heartbeat-hook script"));
6713
+ const claudeDir = join28(homedir9(), ".claude");
6714
+ const settingsPath = join28(claudeDir, "settings.json");
6456
6715
  let settings = {};
6457
- if (existsSync27(settingsPath)) {
6716
+ if (existsSync28(settingsPath)) {
6458
6717
  try {
6459
- const settingsContent = readFileSync21(settingsPath, "utf-8");
6718
+ const settingsContent = readFileSync22(settingsPath, "utf-8");
6460
6719
  settings = JSON.parse(settingsContent);
6461
- console.log(chalk25.green("\u2713 Read existing Claude Code settings"));
6720
+ console.log(chalk26.green("\u2713 Read existing Claude Code settings"));
6462
6721
  } catch (error) {
6463
- console.log(chalk25.yellow("\u26A0 Could not parse settings.json, creating new file"));
6722
+ console.log(chalk26.yellow("\u26A0 Could not parse settings.json, creating new file"));
6464
6723
  settings = {};
6465
6724
  }
6466
6725
  } else {
6467
- console.log(chalk25.dim("No existing settings.json found, creating new file"));
6468
- if (!existsSync27(claudeDir)) {
6726
+ console.log(chalk26.dim("No existing settings.json found, creating new file"));
6727
+ if (!existsSync28(claudeDir)) {
6469
6728
  mkdirSync19(claudeDir, { recursive: true });
6470
6729
  }
6471
6730
  }
6472
6731
  if (hookAlreadyConfigured(settings, scriptDest)) {
6473
- console.log(chalk25.cyan("\n\u2713 Panopticon heartbeat hook already configured"));
6474
- console.log(chalk25.dim(" No changes needed\n"));
6732
+ console.log(chalk26.cyan("\n\u2713 Panopticon heartbeat hook already configured"));
6733
+ console.log(chalk26.dim(" No changes needed\n"));
6475
6734
  return;
6476
6735
  }
6477
6736
  if (!settings.hooks) {
@@ -6489,12 +6748,12 @@ async function setupHooksCommand() {
6489
6748
  }
6490
6749
  ]
6491
6750
  });
6492
- writeFileSync16(settingsPath, JSON.stringify(settings, null, 2));
6493
- console.log(chalk25.green("\u2713 Updated Claude Code settings.json"));
6494
- console.log(chalk25.green.bold("\n\u2713 Setup complete!\n"));
6495
- console.log(chalk25.dim("Heartbeat hooks are now active. When you run agents via"));
6496
- console.log(chalk25.dim("`pan work issue`, they will send real-time activity updates"));
6497
- console.log(chalk25.dim("to the Panopticon dashboard.\n"));
6751
+ writeFileSync17(settingsPath, JSON.stringify(settings, null, 2));
6752
+ console.log(chalk26.green("\u2713 Updated Claude Code settings.json"));
6753
+ console.log(chalk26.green.bold("\n\u2713 Setup complete!\n"));
6754
+ console.log(chalk26.dim("Heartbeat hooks are now active. When you run agents via"));
6755
+ console.log(chalk26.dim("`pan work issue`, they will send real-time activity updates"));
6756
+ console.log(chalk26.dim("to the Panopticon dashboard.\n"));
6498
6757
  }
6499
6758
 
6500
6759
  // src/cli/commands/setup/index.ts
@@ -6504,16 +6763,16 @@ function registerSetupCommands(program2) {
6504
6763
  }
6505
6764
 
6506
6765
  // src/cli/commands/specialists/list.ts
6507
- import chalk26 from "chalk";
6766
+ import chalk27 from "chalk";
6508
6767
  function listCommand3(options) {
6509
6768
  const specialists = getAllSpecialistStatus();
6510
6769
  if (options.json) {
6511
6770
  console.log(JSON.stringify(specialists, null, 2));
6512
6771
  return;
6513
6772
  }
6514
- console.log(chalk26.bold("\nSpecialist Agents:\n"));
6773
+ console.log(chalk27.bold("\nSpecialist Agents:\n"));
6515
6774
  if (specialists.length === 0) {
6516
- console.log(chalk26.dim("No specialists configured."));
6775
+ console.log(chalk27.dim("No specialists configured."));
6517
6776
  console.log("");
6518
6777
  return;
6519
6778
  }
@@ -6525,38 +6784,38 @@ function listCommand3(options) {
6525
6784
  function displaySpecialist(specialist) {
6526
6785
  const statusIcon = getStatusIcon(specialist);
6527
6786
  const statusColor = getStatusColor(specialist);
6528
- const enabledBadge = specialist.enabled ? chalk26.green("enabled") : chalk26.dim("disabled");
6529
- console.log(`${statusIcon} ${chalk26.bold(specialist.displayName)} (${enabledBadge})`);
6530
- console.log(` ${chalk26.dim(specialist.description)}`);
6787
+ const enabledBadge = specialist.enabled ? chalk27.green("enabled") : chalk27.dim("disabled");
6788
+ console.log(`${statusIcon} ${chalk27.bold(specialist.displayName)} (${enabledBadge})`);
6789
+ console.log(` ${chalk27.dim(specialist.description)}`);
6531
6790
  console.log(` Status: ${statusColor(specialist.state)}`);
6532
6791
  if (specialist.isRunning) {
6533
- console.log(` Running: ${chalk26.cyan(specialist.tmuxSession)}`);
6792
+ console.log(` Running: ${chalk27.cyan(specialist.tmuxSession)}`);
6534
6793
  }
6535
6794
  if (specialist.sessionId) {
6536
6795
  const shortId = specialist.sessionId.substring(0, 8);
6537
- console.log(` Session: ${chalk26.dim(shortId + "...")}`);
6796
+ console.log(` Session: ${chalk27.dim(shortId + "...")}`);
6538
6797
  }
6539
6798
  if (specialist.contextTokens) {
6540
6799
  const tokensFormatted = specialist.contextTokens.toLocaleString();
6541
- console.log(` Context: ${chalk26.dim(tokensFormatted + " tokens")}`);
6800
+ console.log(` Context: ${chalk27.dim(tokensFormatted + " tokens")}`);
6542
6801
  }
6543
6802
  if (specialist.lastWake) {
6544
6803
  const lastWake = new Date(specialist.lastWake);
6545
6804
  const relative = getRelativeTime(lastWake);
6546
- console.log(` Last wake: ${chalk26.dim(relative)}`);
6805
+ console.log(` Last wake: ${chalk27.dim(relative)}`);
6547
6806
  }
6548
6807
  console.log("");
6549
6808
  }
6550
6809
  function getStatusIcon(specialist) {
6551
- if (!specialist.enabled) return chalk26.dim("\u25CB");
6552
- if (specialist.isRunning) return chalk26.green("\u25CF");
6553
- if (specialist.state === "sleeping") return chalk26.yellow("\u25CF");
6554
- return chalk26.dim("\u25CB");
6810
+ if (!specialist.enabled) return chalk27.dim("\u25CB");
6811
+ if (specialist.isRunning) return chalk27.green("\u25CF");
6812
+ if (specialist.state === "sleeping") return chalk27.yellow("\u25CF");
6813
+ return chalk27.dim("\u25CB");
6555
6814
  }
6556
6815
  function getStatusColor(specialist) {
6557
- if (specialist.isRunning) return chalk26.green;
6558
- if (specialist.state === "sleeping") return chalk26.yellow;
6559
- return chalk26.dim;
6816
+ if (specialist.isRunning) return chalk27.green;
6817
+ if (specialist.state === "sleeping") return chalk27.yellow;
6818
+ return chalk27.dim;
6560
6819
  }
6561
6820
  function getRelativeTime(date) {
6562
6821
  const now = /* @__PURE__ */ new Date();
@@ -6572,12 +6831,12 @@ function getRelativeTime(date) {
6572
6831
  }
6573
6832
 
6574
6833
  // src/cli/commands/specialists/wake.ts
6575
- import chalk27 from "chalk";
6834
+ import chalk28 from "chalk";
6576
6835
  import { execSync as execSync13 } from "child_process";
6577
6836
  function wakeCommand(name, options) {
6578
6837
  const validNames = ["merge-agent", "review-agent", "test-agent"];
6579
6838
  if (!validNames.includes(name)) {
6580
- console.log(chalk27.red(`
6839
+ console.log(chalk28.red(`
6581
6840
  Error: Unknown specialist '${name}'`));
6582
6841
  console.log(`Valid specialists: ${validNames.join(", ")}
6583
6842
  `);
@@ -6585,29 +6844,29 @@ Error: Unknown specialist '${name}'`));
6585
6844
  }
6586
6845
  const specialistName = name;
6587
6846
  const status = getSpecialistStatus(specialistName);
6588
- console.log(chalk27.bold(`
6847
+ console.log(chalk28.bold(`
6589
6848
  Waking ${status.displayName}...
6590
6849
  `));
6591
6850
  if (status.isRunning) {
6592
- console.log(chalk27.yellow(`Specialist is already running in tmux session: ${status.tmuxSession}`));
6851
+ console.log(chalk28.yellow(`Specialist is already running in tmux session: ${status.tmuxSession}`));
6593
6852
  if (options.task) {
6594
- console.log(chalk27.dim("\nSending task message..."));
6853
+ console.log(chalk28.dim("\nSending task message..."));
6595
6854
  try {
6596
6855
  const escapedTask = options.task.replace(/'/g, "'\\''");
6597
6856
  execSync13(`tmux send-keys -t "${status.tmuxSession}" '${escapedTask}' C-m`, { encoding: "utf-8" });
6598
- console.log(chalk27.green("\u2713 Task message sent"));
6857
+ console.log(chalk28.green("\u2713 Task message sent"));
6599
6858
  } catch (error) {
6600
- console.log(chalk27.red(`Failed to send message: ${error.message}`));
6859
+ console.log(chalk28.red(`Failed to send message: ${error.message}`));
6601
6860
  }
6602
6861
  } else {
6603
- console.log(chalk27.dim("Use --task to send a message to the running specialist"));
6862
+ console.log(chalk28.dim("Use --task to send a message to the running specialist"));
6604
6863
  }
6605
6864
  console.log("");
6606
6865
  return;
6607
6866
  }
6608
6867
  if (!status.enabled) {
6609
- console.log(chalk27.yellow(`Warning: Specialist '${specialistName}' is disabled in registry`));
6610
- console.log(chalk27.dim("You can still wake it manually, but it won't auto-wake\n"));
6868
+ console.log(chalk28.yellow(`Warning: Specialist '${specialistName}' is disabled in registry`));
6869
+ console.log(chalk28.dim("You can still wake it manually, but it won't auto-wake\n"));
6611
6870
  }
6612
6871
  const sessionId = getSessionId(specialistName);
6613
6872
  const tmuxSession = getTmuxSessionName(specialistName);
@@ -6616,28 +6875,28 @@ Waking ${status.displayName}...
6616
6875
  let claudeCmd = "claude --dangerously-skip-permissions";
6617
6876
  if (sessionId) {
6618
6877
  claudeCmd += ` --resume ${sessionId}`;
6619
- console.log(chalk27.dim(`Resuming session: ${sessionId.substring(0, 8)}...`));
6878
+ console.log(chalk28.dim(`Resuming session: ${sessionId.substring(0, 8)}...`));
6620
6879
  } else {
6621
- console.log(chalk27.dim("Starting fresh session (no previous session found)"));
6880
+ console.log(chalk28.dim("Starting fresh session (no previous session found)"));
6622
6881
  }
6623
- console.log(chalk27.dim(`Creating tmux session: ${tmuxSession}`));
6882
+ console.log(chalk28.dim(`Creating tmux session: ${tmuxSession}`));
6624
6883
  execSync13(
6625
6884
  `tmux new-session -d -s "${tmuxSession}" -c "${cwd}" "${claudeCmd}"`,
6626
6885
  { encoding: "utf-8" }
6627
6886
  );
6628
6887
  execSync13("sleep 2", { encoding: "utf-8" });
6629
6888
  if (options.task) {
6630
- console.log(chalk27.dim("Sending task message..."));
6889
+ console.log(chalk28.dim("Sending task message..."));
6631
6890
  const escapedTask = options.task.replace(/'/g, "'\\''");
6632
6891
  execSync13(`tmux send-keys -t "${tmuxSession}" '${escapedTask}' C-m`, { encoding: "utf-8" });
6633
6892
  }
6634
6893
  recordWake(specialistName);
6635
- console.log(chalk27.green(`\u2713 Specialist ${specialistName} woken up successfully`));
6636
- console.log(chalk27.dim(` Tmux session: ${tmuxSession}`));
6637
- console.log(chalk27.dim(` Attach with: tmux attach -t ${tmuxSession}`));
6894
+ console.log(chalk28.green(`\u2713 Specialist ${specialistName} woken up successfully`));
6895
+ console.log(chalk28.dim(` Tmux session: ${tmuxSession}`));
6896
+ console.log(chalk28.dim(` Attach with: tmux attach -t ${tmuxSession}`));
6638
6897
  console.log("");
6639
6898
  } catch (error) {
6640
- console.log(chalk27.red(`
6899
+ console.log(chalk28.red(`
6641
6900
  Failed to wake specialist: ${error.message}
6642
6901
  `));
6643
6902
  process.exit(1);
@@ -6645,11 +6904,11 @@ Failed to wake specialist: ${error.message}
6645
6904
  }
6646
6905
 
6647
6906
  // src/cli/commands/specialists/queue.ts
6648
- import chalk28 from "chalk";
6907
+ import chalk29 from "chalk";
6649
6908
  function queueCommand(name, options) {
6650
6909
  const validNames = ["merge-agent", "review-agent", "test-agent"];
6651
6910
  if (!validNames.includes(name)) {
6652
- console.log(chalk28.red(`
6911
+ console.log(chalk29.red(`
6653
6912
  Error: Unknown specialist '${name}'`));
6654
6913
  console.log(`Valid specialists: ${validNames.join(", ")}
6655
6914
  `);
@@ -6658,7 +6917,7 @@ Error: Unknown specialist '${name}'`));
6658
6917
  const specialistName = name;
6659
6918
  const metadata = getSpecialistMetadata(specialistName);
6660
6919
  if (!metadata) {
6661
- console.log(chalk28.red(`
6920
+ console.log(chalk29.red(`
6662
6921
  Error: Specialist '${specialistName}' not found in registry
6663
6922
  `));
6664
6923
  process.exit(1);
@@ -6668,17 +6927,17 @@ Error: Specialist '${specialistName}' not found in registry
6668
6927
  console.log(JSON.stringify(queueStatus, null, 2));
6669
6928
  return;
6670
6929
  }
6671
- console.log(chalk28.bold(`
6930
+ console.log(chalk29.bold(`
6672
6931
  ${metadata.displayName} Queue:
6673
6932
  `));
6674
6933
  if (!queueStatus.hasWork) {
6675
- console.log(chalk28.dim("Queue is empty - no pending work"));
6934
+ console.log(chalk29.dim("Queue is empty - no pending work"));
6676
6935
  console.log("");
6677
6936
  return;
6678
6937
  }
6679
- console.log(`Total items: ${chalk28.bold(queueStatus.items.length.toString())}`);
6938
+ console.log(`Total items: ${chalk29.bold(queueStatus.items.length.toString())}`);
6680
6939
  if (queueStatus.urgentCount > 0) {
6681
- console.log(`Urgent items: ${chalk28.red.bold(queueStatus.urgentCount.toString())}`);
6940
+ console.log(`Urgent items: ${chalk29.red.bold(queueStatus.urgentCount.toString())}`);
6682
6941
  }
6683
6942
  console.log("");
6684
6943
  for (let i = 0; i < queueStatus.items.length; i++) {
@@ -6686,45 +6945,45 @@ ${metadata.displayName} Queue:
6686
6945
  const position = i + 1;
6687
6946
  const priorityColor = getPriorityColor(item.priority);
6688
6947
  console.log(`${position}. ${priorityColor(item.priority.toUpperCase())}`);
6689
- console.log(` ID: ${chalk28.dim(item.id)}`);
6690
- console.log(` Source: ${chalk28.dim(item.source)}`);
6948
+ console.log(` ID: ${chalk29.dim(item.id)}`);
6949
+ console.log(` Source: ${chalk29.dim(item.source)}`);
6691
6950
  if (item.payload) {
6692
6951
  const payload = item.payload;
6693
6952
  if (payload.issueId) {
6694
- console.log(` Issue: ${chalk28.cyan(payload.issueId)}`);
6953
+ console.log(` Issue: ${chalk29.cyan(payload.issueId)}`);
6695
6954
  }
6696
6955
  if (payload.prUrl) {
6697
- console.log(` PR: ${chalk28.dim(payload.prUrl)}`);
6956
+ console.log(` PR: ${chalk29.dim(payload.prUrl)}`);
6698
6957
  }
6699
6958
  if (payload.workspace) {
6700
- console.log(` Workspace: ${chalk28.dim(payload.workspace)}`);
6959
+ console.log(` Workspace: ${chalk29.dim(payload.workspace)}`);
6701
6960
  }
6702
6961
  if (payload.branch) {
6703
- console.log(` Branch: ${chalk28.dim(payload.branch)}`);
6962
+ console.log(` Branch: ${chalk29.dim(payload.branch)}`);
6704
6963
  }
6705
6964
  if (payload.filesChanged && Array.isArray(payload.filesChanged)) {
6706
6965
  const fileCount = payload.filesChanged.length;
6707
- console.log(` Files: ${chalk28.dim(fileCount + " changed")}`);
6966
+ console.log(` Files: ${chalk29.dim(fileCount + " changed")}`);
6708
6967
  }
6709
6968
  }
6710
6969
  const createdAt = new Date(item.createdAt);
6711
6970
  const age = getAge(createdAt);
6712
- console.log(` Age: ${chalk28.dim(age)}`);
6971
+ console.log(` Age: ${chalk29.dim(age)}`);
6713
6972
  console.log("");
6714
6973
  }
6715
6974
  }
6716
6975
  function getPriorityColor(priority) {
6717
6976
  switch (priority.toLowerCase()) {
6718
6977
  case "urgent":
6719
- return chalk28.red.bold;
6978
+ return chalk29.red.bold;
6720
6979
  case "high":
6721
- return chalk28.yellow;
6980
+ return chalk29.yellow;
6722
6981
  case "normal":
6723
- return chalk28.white;
6982
+ return chalk29.white;
6724
6983
  case "low":
6725
- return chalk28.dim;
6984
+ return chalk29.dim;
6726
6985
  default:
6727
- return chalk28.white;
6986
+ return chalk29.white;
6728
6987
  }
6729
6988
  }
6730
6989
  function getAge(date) {
@@ -6741,13 +7000,13 @@ function getAge(date) {
6741
7000
  }
6742
7001
 
6743
7002
  // src/cli/commands/specialists/reset.ts
6744
- import chalk29 from "chalk";
7003
+ import chalk30 from "chalk";
6745
7004
  import { execSync as execSync14 } from "child_process";
6746
7005
  import * as readline from "readline";
6747
7006
  async function resetCommand(name, options) {
6748
7007
  const validNames = ["merge-agent", "review-agent", "test-agent"];
6749
7008
  if (!validNames.includes(name)) {
6750
- console.log(chalk29.red(`
7009
+ console.log(chalk30.red(`
6751
7010
  Error: Unknown specialist '${name}'`));
6752
7011
  console.log(`Valid specialists: ${validNames.join(", ")}
6753
7012
  `);
@@ -6755,13 +7014,13 @@ Error: Unknown specialist '${name}'`));
6755
7014
  }
6756
7015
  const specialistName = name;
6757
7016
  const status = getSpecialistStatus(specialistName);
6758
- console.log(chalk29.bold(`
7017
+ console.log(chalk30.bold(`
6759
7018
  Resetting ${status.displayName}...
6760
7019
  `));
6761
- console.log(chalk29.dim("Current state:"));
7020
+ console.log(chalk30.dim("Current state:"));
6762
7021
  console.log(` Status: ${status.state}`);
6763
7022
  if (status.isRunning) {
6764
- console.log(` Running: ${chalk29.yellow("yes")} (tmux: ${status.tmuxSession})`);
7023
+ console.log(` Running: ${chalk30.yellow("yes")} (tmux: ${status.tmuxSession})`);
6765
7024
  }
6766
7025
  if (status.sessionId) {
6767
7026
  console.log(` Session: ${status.sessionId.substring(0, 8)}...`);
@@ -6775,29 +7034,29 @@ Resetting ${status.displayName}...
6775
7034
  "This will clear the session and all context. Continue?"
6776
7035
  );
6777
7036
  if (!confirmed) {
6778
- console.log(chalk29.dim("Reset cancelled\n"));
7037
+ console.log(chalk30.dim("Reset cancelled\n"));
6779
7038
  return;
6780
7039
  }
6781
7040
  }
6782
7041
  if (status.isRunning) {
6783
- console.log(chalk29.dim("Stopping tmux session..."));
7042
+ console.log(chalk30.dim("Stopping tmux session..."));
6784
7043
  try {
6785
7044
  execSync14(`tmux kill-session -t "${status.tmuxSession}"`, { encoding: "utf-8", stdio: "ignore" });
6786
- console.log(chalk29.green("\u2713 Tmux session stopped"));
7045
+ console.log(chalk30.green("\u2713 Tmux session stopped"));
6787
7046
  } catch (error) {
6788
- console.log(chalk29.yellow("\u26A0 Failed to stop tmux session (may not be running)"));
7047
+ console.log(chalk30.yellow("\u26A0 Failed to stop tmux session (may not be running)"));
6789
7048
  }
6790
7049
  }
6791
- console.log(chalk29.dim("Clearing session file..."));
7050
+ console.log(chalk30.dim("Clearing session file..."));
6792
7051
  const cleared = clearSessionId(specialistName);
6793
7052
  if (cleared) {
6794
- console.log(chalk29.green("\u2713 Session file cleared"));
7053
+ console.log(chalk30.green("\u2713 Session file cleared"));
6795
7054
  } else {
6796
- console.log(chalk29.dim("No session file to clear"));
7055
+ console.log(chalk30.dim("No session file to clear"));
6797
7056
  }
6798
- console.log(chalk29.green(`
7057
+ console.log(chalk30.green(`
6799
7058
  \u2713 Specialist ${specialistName} has been reset`));
6800
- console.log(chalk29.dim("Next wake will start a fresh session\n"));
7059
+ console.log(chalk30.dim("Next wake will start a fresh session\n"));
6801
7060
  }
6802
7061
  function confirm(question) {
6803
7062
  const rl = readline.createInterface({
@@ -6805,7 +7064,7 @@ function confirm(question) {
6805
7064
  output: process.stdout
6806
7065
  });
6807
7066
  return new Promise((resolve2) => {
6808
- rl.question(chalk29.yellow(`${question} [y/N] `), (answer) => {
7067
+ rl.question(chalk30.yellow(`${question} [y/N] `), (answer) => {
6809
7068
  rl.close();
6810
7069
  resolve2(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
6811
7070
  });
@@ -6822,29 +7081,29 @@ function registerSpecialistsCommands(program2) {
6822
7081
  }
6823
7082
 
6824
7083
  // src/cli/commands/project.ts
6825
- import chalk30 from "chalk";
6826
- import { existsSync as existsSync28, readFileSync as readFileSync22 } from "fs";
6827
- import { join as join28, resolve } from "path";
7084
+ import chalk31 from "chalk";
7085
+ import { existsSync as existsSync29, readFileSync as readFileSync23 } from "fs";
7086
+ import { join as join29, resolve } from "path";
6828
7087
  async function projectAddCommand(projectPath, options = {}) {
6829
7088
  const fullPath = resolve(projectPath);
6830
- if (!existsSync28(fullPath)) {
6831
- console.log(chalk30.red(`Path does not exist: ${fullPath}`));
7089
+ if (!existsSync29(fullPath)) {
7090
+ console.log(chalk31.red(`Path does not exist: ${fullPath}`));
6832
7091
  return;
6833
7092
  }
6834
7093
  const name = options.name || fullPath.split("/").pop() || "unknown";
6835
7094
  const key = name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
6836
7095
  const existing = getProject(key);
6837
7096
  if (existing) {
6838
- console.log(chalk30.yellow(`Project already registered with key: ${key}`));
6839
- console.log(chalk30.dim(`Existing path: ${existing.path}`));
6840
- console.log(chalk30.dim(`To update, first run: pan project remove ${key}`));
7097
+ console.log(chalk31.yellow(`Project already registered with key: ${key}`));
7098
+ console.log(chalk31.dim(`Existing path: ${existing.path}`));
7099
+ console.log(chalk31.dim(`To update, first run: pan project remove ${key}`));
6841
7100
  return;
6842
7101
  }
6843
7102
  let linearTeam = options.linearTeam;
6844
7103
  if (!linearTeam) {
6845
- const projectToml = join28(fullPath, ".panopticon", "project.toml");
6846
- if (existsSync28(projectToml)) {
6847
- const content = readFileSync22(projectToml, "utf-8");
7104
+ const projectToml = join29(fullPath, ".panopticon", "project.toml");
7105
+ if (existsSync29(projectToml)) {
7106
+ const content = readFileSync23(projectToml, "utf-8");
6848
7107
  const match = content.match(/team\s*=\s*"([^"]+)"/);
6849
7108
  if (match) linearTeam = match[1];
6850
7109
  }
@@ -6857,21 +7116,21 @@ async function projectAddCommand(projectPath, options = {}) {
6857
7116
  projectConfig.linear_team = linearTeam.toUpperCase();
6858
7117
  }
6859
7118
  registerProject(key, projectConfig);
6860
- console.log(chalk30.green(`\u2713 Added project: ${name}`));
6861
- console.log(chalk30.dim(` Key: ${key}`));
6862
- console.log(chalk30.dim(` Path: ${fullPath}`));
7119
+ console.log(chalk31.green(`\u2713 Added project: ${name}`));
7120
+ console.log(chalk31.dim(` Key: ${key}`));
7121
+ console.log(chalk31.dim(` Path: ${fullPath}`));
6863
7122
  if (linearTeam) {
6864
- console.log(chalk30.dim(` Linear team: ${linearTeam}`));
7123
+ console.log(chalk31.dim(` Linear team: ${linearTeam}`));
6865
7124
  }
6866
7125
  console.log("");
6867
- console.log(chalk30.dim(`Edit ${PROJECTS_CONFIG_FILE} to add issue routing rules.`));
7126
+ console.log(chalk31.dim(`Edit ${PROJECTS_CONFIG_FILE} to add issue routing rules.`));
6868
7127
  }
6869
7128
  async function projectListCommand(options = {}) {
6870
7129
  const projects = listProjects();
6871
7130
  if (projects.length === 0) {
6872
- console.log(chalk30.dim("No projects registered."));
6873
- console.log(chalk30.dim("Add one with: pan project add <path> --linear-team <TEAM>"));
6874
- console.log(chalk30.dim(`Or edit: ${PROJECTS_CONFIG_FILE}`));
7131
+ console.log(chalk31.dim("No projects registered."));
7132
+ console.log(chalk31.dim("Add one with: pan project add <path> --linear-team <TEAM>"));
7133
+ console.log(chalk31.dim(`Or edit: ${PROJECTS_CONFIG_FILE}`));
6875
7134
  return;
6876
7135
  }
6877
7136
  if (options.json) {
@@ -6882,51 +7141,51 @@ async function projectListCommand(options = {}) {
6882
7141
  console.log(JSON.stringify(output, null, 2));
6883
7142
  return;
6884
7143
  }
6885
- console.log(chalk30.bold("\nRegistered Projects:\n"));
7144
+ console.log(chalk31.bold("\nRegistered Projects:\n"));
6886
7145
  for (const { key, config } of projects) {
6887
- const exists = existsSync28(config.path);
6888
- const statusIcon = exists ? chalk30.green("\u2713") : chalk30.red("\u2717");
6889
- console.log(`${statusIcon} ${chalk30.bold(config.name)} ${chalk30.dim(`(${key})`)}`);
6890
- console.log(` ${chalk30.dim(config.path)}`);
7146
+ const exists = existsSync29(config.path);
7147
+ const statusIcon = exists ? chalk31.green("\u2713") : chalk31.red("\u2717");
7148
+ console.log(`${statusIcon} ${chalk31.bold(config.name)} ${chalk31.dim(`(${key})`)}`);
7149
+ console.log(` ${chalk31.dim(config.path)}`);
6891
7150
  if (config.linear_team) {
6892
- console.log(` ${chalk30.cyan(`Linear: ${config.linear_team}`)}`);
7151
+ console.log(` ${chalk31.cyan(`Linear: ${config.linear_team}`)}`);
6893
7152
  }
6894
7153
  if (config.issue_routing && config.issue_routing.length > 0) {
6895
- console.log(` ${chalk30.dim(`Routes: ${config.issue_routing.length} rules`)}`);
7154
+ console.log(` ${chalk31.dim(`Routes: ${config.issue_routing.length} rules`)}`);
6896
7155
  }
6897
7156
  console.log("");
6898
7157
  }
6899
- console.log(chalk30.dim(`Config: ${PROJECTS_CONFIG_FILE}`));
7158
+ console.log(chalk31.dim(`Config: ${PROJECTS_CONFIG_FILE}`));
6900
7159
  }
6901
7160
  async function projectRemoveCommand(nameOrPath) {
6902
7161
  const projects = listProjects();
6903
7162
  if (unregisterProject(nameOrPath)) {
6904
- console.log(chalk30.green(`\u2713 Removed project: ${nameOrPath}`));
7163
+ console.log(chalk31.green(`\u2713 Removed project: ${nameOrPath}`));
6905
7164
  return;
6906
7165
  }
6907
7166
  for (const { key, config } of projects) {
6908
7167
  if (config.name === nameOrPath || config.path === resolve(nameOrPath)) {
6909
7168
  unregisterProject(key);
6910
- console.log(chalk30.green(`\u2713 Removed project: ${config.name}`));
7169
+ console.log(chalk31.green(`\u2713 Removed project: ${config.name}`));
6911
7170
  return;
6912
7171
  }
6913
7172
  }
6914
- console.log(chalk30.red(`Project not found: ${nameOrPath}`));
6915
- console.log(chalk30.dim(`Use 'pan project list' to see registered projects.`));
7173
+ console.log(chalk31.red(`Project not found: ${nameOrPath}`));
7174
+ console.log(chalk31.dim(`Use 'pan project list' to see registered projects.`));
6916
7175
  }
6917
7176
  async function projectInitCommand() {
6918
- if (existsSync28(PROJECTS_CONFIG_FILE)) {
6919
- console.log(chalk30.yellow(`Config already exists: ${PROJECTS_CONFIG_FILE}`));
7177
+ if (existsSync29(PROJECTS_CONFIG_FILE)) {
7178
+ console.log(chalk31.yellow(`Config already exists: ${PROJECTS_CONFIG_FILE}`));
6920
7179
  return;
6921
7180
  }
6922
7181
  initializeProjectsConfig();
6923
- console.log(chalk30.green("\u2713 Projects config initialized"));
7182
+ console.log(chalk31.green("\u2713 Projects config initialized"));
6924
7183
  console.log("");
6925
- console.log(chalk30.dim(`Edit ${PROJECTS_CONFIG_FILE} to add your projects.`));
7184
+ console.log(chalk31.dim(`Edit ${PROJECTS_CONFIG_FILE} to add your projects.`));
6926
7185
  console.log("");
6927
- console.log(chalk30.bold("Quick start:"));
7186
+ console.log(chalk31.bold("Quick start:"));
6928
7187
  console.log(
6929
- chalk30.dim(
7188
+ chalk31.dim(
6930
7189
  ' pan project add /path/to/project --name "My Project" --linear-team MIN'
6931
7190
  )
6932
7191
  );
@@ -6945,13 +7204,13 @@ async function projectShowCommand(keyOrName) {
6945
7204
  }
6946
7205
  }
6947
7206
  if (!found) {
6948
- console.error(chalk30.red(`Project not found: ${keyOrName}`));
6949
- console.log(chalk30.dim(`Use 'pan project list' to see registered projects.`));
7207
+ console.error(chalk31.red(`Project not found: ${keyOrName}`));
7208
+ console.log(chalk31.dim(`Use 'pan project list' to see registered projects.`));
6950
7209
  process.exit(1);
6951
7210
  }
6952
- const pathExists = existsSync28(found.path);
6953
- const pathStatus = pathExists ? chalk30.green("\u2713") : chalk30.red("\u2717");
6954
- console.log(chalk30.bold(`
7211
+ const pathExists = existsSync29(found.path);
7212
+ const pathStatus = pathExists ? chalk31.green("\u2713") : chalk31.red("\u2717");
7213
+ console.log(chalk31.bold(`
6955
7214
  Project: ${foundKey}
6956
7215
  `));
6957
7216
  console.log(` Name: ${found.name}`);
@@ -6960,7 +7219,7 @@ Project: ${foundKey}
6960
7219
  console.log(` Team: ${found.linear_team}`);
6961
7220
  }
6962
7221
  if (found.issue_routing && found.issue_routing.length > 0) {
6963
- console.log("\n " + chalk30.bold("Routing Rules:"));
7222
+ console.log("\n " + chalk31.bold("Routing Rules:"));
6964
7223
  for (const rule of found.issue_routing) {
6965
7224
  if (rule.labels) {
6966
7225
  console.log(` Labels: ${rule.labels.join(", ")}`);
@@ -6975,11 +7234,11 @@ Project: ${foundKey}
6975
7234
  }
6976
7235
 
6977
7236
  // src/cli/commands/doctor.ts
6978
- import chalk31 from "chalk";
6979
- import { existsSync as existsSync29, readdirSync as readdirSync15, readFileSync as readFileSync23 } from "fs";
7237
+ import chalk32 from "chalk";
7238
+ import { existsSync as existsSync30, readdirSync as readdirSync15, readFileSync as readFileSync24 } from "fs";
6980
7239
  import { execSync as execSync15 } from "child_process";
6981
- import { homedir as homedir9 } from "os";
6982
- import { join as join29 } from "path";
7240
+ import { homedir as homedir10 } from "os";
7241
+ import { join as join30 } from "path";
6983
7242
  function checkCommand2(cmd) {
6984
7243
  try {
6985
7244
  execSync15(`which ${cmd}`, { encoding: "utf-8", stdio: "pipe" });
@@ -6989,10 +7248,10 @@ function checkCommand2(cmd) {
6989
7248
  }
6990
7249
  }
6991
7250
  function checkDirectory(path) {
6992
- return existsSync29(path);
7251
+ return existsSync30(path);
6993
7252
  }
6994
7253
  function countItems(path) {
6995
- if (!existsSync29(path)) return 0;
7254
+ if (!existsSync30(path)) return 0;
6996
7255
  try {
6997
7256
  return readdirSync15(path).length;
6998
7257
  } catch {
@@ -7000,8 +7259,8 @@ function countItems(path) {
7000
7259
  }
7001
7260
  }
7002
7261
  async function doctorCommand() {
7003
- console.log(chalk31.bold("\nPanopticon Doctor\n"));
7004
- console.log(chalk31.dim("Checking system health...\n"));
7262
+ console.log(chalk32.bold("\nPanopticon Doctor\n"));
7263
+ console.log(chalk32.dim("Checking system health...\n"));
7005
7264
  const checks = [];
7006
7265
  const requiredCommands = [
7007
7266
  { cmd: "git", name: "Git", fix: "Install git" },
@@ -7043,8 +7302,8 @@ async function doctorCommand() {
7043
7302
  }
7044
7303
  }
7045
7304
  if (checkDirectory(CLAUDE_DIR)) {
7046
- const skillsCount = countItems(join29(CLAUDE_DIR, "skills"));
7047
- const commandsCount = countItems(join29(CLAUDE_DIR, "commands"));
7305
+ const skillsCount = countItems(join30(CLAUDE_DIR, "skills"));
7306
+ const commandsCount = countItems(join30(CLAUDE_DIR, "commands"));
7048
7307
  checks.push({
7049
7308
  name: "Claude Code Skills",
7050
7309
  status: skillsCount > 0 ? "ok" : "warn",
@@ -7065,8 +7324,8 @@ async function doctorCommand() {
7065
7324
  fix: "Install Claude Code first"
7066
7325
  });
7067
7326
  }
7068
- const envFile = join29(homedir9(), ".panopticon.env");
7069
- if (existsSync29(envFile)) {
7327
+ const envFile = join30(homedir10(), ".panopticon.env");
7328
+ if (existsSync30(envFile)) {
7070
7329
  checks.push({ name: "Config File", status: "ok", message: "~/.panopticon.env exists" });
7071
7330
  } else {
7072
7331
  checks.push({
@@ -7078,8 +7337,8 @@ async function doctorCommand() {
7078
7337
  }
7079
7338
  if (process.env.LINEAR_API_KEY) {
7080
7339
  checks.push({ name: "LINEAR_API_KEY", status: "ok", message: "Set in environment" });
7081
- } else if (existsSync29(envFile)) {
7082
- const content = readFileSync23(envFile, "utf-8");
7340
+ } else if (existsSync30(envFile)) {
7341
+ const content = readFileSync24(envFile, "utf-8");
7083
7342
  if (content.includes("LINEAR_API_KEY")) {
7084
7343
  checks.push({ name: "LINEAR_API_KEY", status: "ok", message: "Set in config file" });
7085
7344
  } else {
@@ -7114,46 +7373,46 @@ async function doctorCommand() {
7114
7373
  });
7115
7374
  }
7116
7375
  const icons = {
7117
- ok: chalk31.green("\u2713"),
7118
- warn: chalk31.yellow("\u26A0"),
7119
- error: chalk31.red("\u2717")
7376
+ ok: chalk32.green("\u2713"),
7377
+ warn: chalk32.yellow("\u26A0"),
7378
+ error: chalk32.red("\u2717")
7120
7379
  };
7121
7380
  let hasErrors = false;
7122
7381
  let hasWarnings = false;
7123
7382
  for (const check of checks) {
7124
7383
  const icon = icons[check.status];
7125
- const message = check.status === "error" ? chalk31.red(check.message) : check.status === "warn" ? chalk31.yellow(check.message) : chalk31.dim(check.message);
7384
+ const message = check.status === "error" ? chalk32.red(check.message) : check.status === "warn" ? chalk32.yellow(check.message) : chalk32.dim(check.message);
7126
7385
  console.log(`${icon} ${check.name}: ${message}`);
7127
7386
  if (check.fix && check.status !== "ok") {
7128
- console.log(chalk31.dim(` Fix: ${check.fix}`));
7387
+ console.log(chalk32.dim(` Fix: ${check.fix}`));
7129
7388
  }
7130
7389
  if (check.status === "error") hasErrors = true;
7131
7390
  if (check.status === "warn") hasWarnings = true;
7132
7391
  }
7133
7392
  console.log("");
7134
7393
  if (hasErrors) {
7135
- console.log(chalk31.red("Some required components are missing."));
7136
- console.log(chalk31.dim("Fix the errors above before using Panopticon."));
7394
+ console.log(chalk32.red("Some required components are missing."));
7395
+ console.log(chalk32.dim("Fix the errors above before using Panopticon."));
7137
7396
  } else if (hasWarnings) {
7138
- console.log(chalk31.yellow("System is functional with some optional features missing."));
7397
+ console.log(chalk32.yellow("System is functional with some optional features missing."));
7139
7398
  } else {
7140
- console.log(chalk31.green("All systems operational!"));
7399
+ console.log(chalk32.green("All systems operational!"));
7141
7400
  }
7142
7401
  console.log("");
7143
7402
  }
7144
7403
 
7145
7404
  // src/cli/commands/update.ts
7146
7405
  import { execSync as execSync16 } from "child_process";
7147
- import chalk32 from "chalk";
7148
- import { readFileSync as readFileSync24 } from "fs";
7406
+ import chalk33 from "chalk";
7407
+ import { readFileSync as readFileSync25 } from "fs";
7149
7408
  import { fileURLToPath as fileURLToPath2 } from "url";
7150
- import { dirname as dirname5, join as join30 } from "path";
7409
+ import { dirname as dirname5, join as join31 } from "path";
7151
7410
  function getCurrentVersion() {
7152
7411
  try {
7153
7412
  const __filename3 = fileURLToPath2(import.meta.url);
7154
7413
  const __dirname3 = dirname5(__filename3);
7155
- const pkgPath = join30(__dirname3, "..", "..", "..", "package.json");
7156
- const pkg = JSON.parse(readFileSync24(pkgPath, "utf-8"));
7414
+ const pkgPath = join31(__dirname3, "..", "..", "..", "package.json");
7415
+ const pkg = JSON.parse(readFileSync25(pkgPath, "utf-8"));
7157
7416
  return pkg.version;
7158
7417
  } catch {
7159
7418
  return "unknown";
@@ -7186,49 +7445,49 @@ function isNewer(latest, current) {
7186
7445
  return l.patch > c.patch;
7187
7446
  }
7188
7447
  async function updateCommand(options) {
7189
- console.log(chalk32.bold("Panopticon Update\n"));
7448
+ console.log(chalk33.bold("Panopticon Update\n"));
7190
7449
  const currentVersion = getCurrentVersion();
7191
- console.log(`Current version: ${chalk32.cyan(currentVersion)}`);
7450
+ console.log(`Current version: ${chalk33.cyan(currentVersion)}`);
7192
7451
  let latestVersion;
7193
7452
  try {
7194
- console.log(chalk32.dim("Checking npm for latest version..."));
7453
+ console.log(chalk33.dim("Checking npm for latest version..."));
7195
7454
  latestVersion = await getLatestVersion();
7196
- console.log(`Latest version: ${chalk32.cyan(latestVersion)}`);
7455
+ console.log(`Latest version: ${chalk33.cyan(latestVersion)}`);
7197
7456
  } catch (error) {
7198
- console.error(chalk32.red("Failed to check for updates"));
7199
- console.error(chalk32.dim("Make sure you have internet connectivity"));
7457
+ console.error(chalk33.red("Failed to check for updates"));
7458
+ console.error(chalk33.dim("Make sure you have internet connectivity"));
7200
7459
  process.exit(1);
7201
7460
  }
7202
7461
  const needsUpdate = isNewer(latestVersion, currentVersion);
7203
7462
  if (!needsUpdate) {
7204
- console.log(chalk32.green("\n\u2713 You are on the latest version"));
7463
+ console.log(chalk33.green("\n\u2713 You are on the latest version"));
7205
7464
  return;
7206
7465
  }
7207
7466
  console.log(
7208
- chalk32.yellow(`
7467
+ chalk33.yellow(`
7209
7468
  \u2191 Update available: ${currentVersion} \u2192 ${latestVersion}`)
7210
7469
  );
7211
7470
  if (options.check) {
7212
- console.log(chalk32.dim("\nRun `pan update` to install"));
7471
+ console.log(chalk33.dim("\nRun `pan update` to install"));
7213
7472
  return;
7214
7473
  }
7215
- console.log(chalk32.dim("\nUpdating Panopticon..."));
7474
+ console.log(chalk33.dim("\nUpdating Panopticon..."));
7216
7475
  try {
7217
7476
  execSync16("npm install -g panopticon-cli@latest", {
7218
7477
  stdio: "inherit"
7219
7478
  });
7220
- console.log(chalk32.green(`
7479
+ console.log(chalk33.green(`
7221
7480
  \u2713 Updated to ${latestVersion}`));
7222
7481
  const config = loadConfig();
7223
7482
  if (config.sync.auto_sync) {
7224
- console.log(chalk32.dim("\nRunning auto-sync..."));
7483
+ console.log(chalk33.dim("\nRunning auto-sync..."));
7225
7484
  await syncCommand({});
7226
7485
  }
7227
- console.log(chalk32.dim("\nRestart any running agents to use the new version."));
7486
+ console.log(chalk33.dim("\nRestart any running agents to use the new version."));
7228
7487
  } catch (error) {
7229
- console.error(chalk32.red("\nUpdate failed"));
7488
+ console.error(chalk33.red("\nUpdate failed"));
7230
7489
  console.error(
7231
- chalk32.dim("Try running with sudo: sudo npm install -g panopticon-cli@latest")
7490
+ chalk33.dim("Try running with sudo: sudo npm install -g panopticon-cli@latest")
7232
7491
  );
7233
7492
  process.exit(1);
7234
7493
  }
@@ -7253,55 +7512,85 @@ registerInstallCommand(program);
7253
7512
  program.command("status").description("Show running agents (shorthand for work status)").option("--json", "Output as JSON").action(statusCommand);
7254
7513
  program.command("up").description("Start dashboard (and Traefik if enabled)").option("--detach", "Run in background").option("--skip-traefik", "Skip Traefik startup").action(async (options) => {
7255
7514
  const { spawn, execSync: execSync17 } = await import("child_process");
7256
- const { join: join31, dirname: dirname6 } = await import("path");
7515
+ const { join: join32, dirname: dirname6 } = await import("path");
7257
7516
  const { fileURLToPath: fileURLToPath3 } = await import("url");
7258
- const { readFileSync: readFileSync25, existsSync: existsSync30 } = await import("fs");
7517
+ const { readFileSync: readFileSync26, existsSync: existsSync31 } = await import("fs");
7259
7518
  const { parse: parse2 } = await import("@iarna/toml");
7260
7519
  const __dirname3 = dirname6(fileURLToPath3(import.meta.url));
7261
- const dashboardDir = join31(__dirname3, "..", "dashboard");
7262
- const configFile = join31(process.env.HOME || "", ".panopticon", "config.toml");
7520
+ const bundledServer = join32(__dirname3, "..", "dashboard", "server.js");
7521
+ const srcDashboard = join32(__dirname3, "..", "..", "src", "dashboard");
7522
+ const configFile = join32(process.env.HOME || "", ".panopticon", "config.toml");
7263
7523
  let traefikEnabled = false;
7264
7524
  let traefikDomain = "pan.localhost";
7265
- if (existsSync30(configFile)) {
7525
+ if (existsSync31(configFile)) {
7266
7526
  try {
7267
- const configContent = readFileSync25(configFile, "utf-8");
7527
+ const configContent = readFileSync26(configFile, "utf-8");
7268
7528
  const config = parse2(configContent);
7269
7529
  traefikEnabled = config.traefik?.enabled === true;
7270
7530
  traefikDomain = config.traefik?.domain || "pan.localhost";
7271
7531
  } catch (error) {
7272
- console.log(chalk33.yellow("Warning: Could not read config.toml"));
7532
+ console.log(chalk34.yellow("Warning: Could not read config.toml"));
7273
7533
  }
7274
7534
  }
7275
- console.log(chalk33.bold("Starting Panopticon...\n"));
7535
+ console.log(chalk34.bold("Starting Panopticon...\n"));
7276
7536
  if (traefikEnabled && !options.skipTraefik) {
7277
- const traefikDir = join31(process.env.HOME || "", ".panopticon", "traefik");
7278
- if (existsSync30(traefikDir)) {
7537
+ const traefikDir = join32(process.env.HOME || "", ".panopticon", "traefik");
7538
+ if (existsSync31(traefikDir)) {
7279
7539
  try {
7280
- console.log(chalk33.dim("Starting Traefik..."));
7540
+ const composeFile = join32(traefikDir, "docker-compose.yml");
7541
+ if (existsSync31(composeFile)) {
7542
+ const content = readFileSync26(composeFile, "utf-8");
7543
+ if (!content.includes("external: true") && content.includes("panopticon:")) {
7544
+ const patched = content.replace(
7545
+ /networks:\s*\n\s*panopticon:\s*\n\s*name: panopticon\s*\n\s*driver: bridge/,
7546
+ "networks:\n panopticon:\n name: panopticon\n external: true # Network created by 'pan install'"
7547
+ );
7548
+ const { writeFileSync: writeFileSync18 } = await import("fs");
7549
+ writeFileSync18(composeFile, patched);
7550
+ console.log(chalk34.dim(" (migrated network config)"));
7551
+ }
7552
+ }
7553
+ console.log(chalk34.dim("Starting Traefik..."));
7281
7554
  execSync17("docker-compose up -d", {
7282
7555
  cwd: traefikDir,
7283
7556
  stdio: "pipe"
7284
7557
  });
7285
- console.log(chalk33.green("\u2713 Traefik started"));
7286
- console.log(chalk33.dim(` Dashboard: https://traefik.${traefikDomain}:8080
7558
+ console.log(chalk34.green("\u2713 Traefik started"));
7559
+ console.log(chalk34.dim(` Dashboard: https://traefik.${traefikDomain}:8080
7287
7560
  `));
7288
7561
  } catch (error) {
7289
- console.log(chalk33.yellow("\u26A0 Failed to start Traefik (continuing anyway)"));
7290
- console.log(chalk33.dim(" Run with --skip-traefik to suppress this message\n"));
7562
+ console.log(chalk34.yellow("\u26A0 Failed to start Traefik (continuing anyway)"));
7563
+ console.log(chalk34.dim(" Run with --skip-traefik to suppress this message\n"));
7291
7564
  }
7292
7565
  }
7293
7566
  }
7294
- try {
7295
- execSync17("npm --version", { stdio: "pipe" });
7296
- } catch {
7297
- console.error(chalk33.red("Error: npm not found in PATH"));
7298
- console.error(chalk33.dim("Make sure Node.js and npm are installed and in your PATH"));
7567
+ const isProduction = existsSync31(bundledServer);
7568
+ const isDevelopment = existsSync31(srcDashboard);
7569
+ if (!isProduction && !isDevelopment) {
7570
+ console.error(chalk34.red("Error: Dashboard not found"));
7571
+ console.error(chalk34.dim("This may be a corrupted installation. Try reinstalling panopticon-cli."));
7299
7572
  process.exit(1);
7300
7573
  }
7301
- console.log(chalk33.dim("Starting dashboard..."));
7574
+ if (isDevelopment && !isProduction) {
7575
+ try {
7576
+ execSync17("npm --version", { stdio: "pipe" });
7577
+ } catch {
7578
+ console.error(chalk34.red("Error: npm not found in PATH"));
7579
+ console.error(chalk34.dim("Make sure Node.js and npm are installed and in your PATH"));
7580
+ process.exit(1);
7581
+ }
7582
+ }
7583
+ if (isProduction) {
7584
+ console.log(chalk34.dim("Starting dashboard (bundled mode)..."));
7585
+ } else {
7586
+ console.log(chalk34.dim("Starting dashboard (development mode)..."));
7587
+ }
7302
7588
  if (options.detach) {
7303
- const child = spawn("npm", ["run", "dev"], {
7304
- cwd: dashboardDir,
7589
+ const child = isProduction ? spawn("node", [bundledServer], {
7590
+ detached: true,
7591
+ stdio: "ignore"
7592
+ }) : spawn("npm", ["run", "dev"], {
7593
+ cwd: srcDashboard,
7305
7594
  detached: true,
7306
7595
  stdio: "ignore",
7307
7596
  shell: true
@@ -7309,7 +7598,7 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
7309
7598
  let hasError = false;
7310
7599
  child.on("error", (err) => {
7311
7600
  hasError = true;
7312
- console.error(chalk33.red("Failed to start dashboard in background:"), err.message);
7601
+ console.error(chalk34.red("Failed to start dashboard in background:"), err.message);
7313
7602
  process.exit(1);
7314
7603
  });
7315
7604
  setTimeout(() => {
@@ -7317,70 +7606,72 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
7317
7606
  child.unref();
7318
7607
  }
7319
7608
  }, 100);
7320
- console.log(chalk33.green("\u2713 Dashboard started in background"));
7609
+ console.log(chalk34.green("\u2713 Dashboard started in background"));
7321
7610
  if (traefikEnabled) {
7322
- console.log(` Frontend: ${chalk33.cyan(`https://${traefikDomain}`)}`);
7323
- console.log(` API: ${chalk33.cyan(`https://${traefikDomain}/api`)}`);
7611
+ console.log(` Frontend: ${chalk34.cyan(`https://${traefikDomain}`)}`);
7612
+ console.log(` API: ${chalk34.cyan(`https://${traefikDomain}/api`)}`);
7324
7613
  } else {
7325
- console.log(` Frontend: ${chalk33.cyan("http://localhost:3001")}`);
7326
- console.log(` API: ${chalk33.cyan("http://localhost:3002")}`);
7614
+ console.log(` Frontend: ${chalk34.cyan("http://localhost:3001")}`);
7615
+ console.log(` API: ${chalk34.cyan("http://localhost:3002")}`);
7327
7616
  }
7328
7617
  } else {
7329
7618
  if (traefikEnabled) {
7330
- console.log(` Frontend: ${chalk33.cyan(`https://${traefikDomain}`)}`);
7331
- console.log(` API: ${chalk33.cyan(`https://${traefikDomain}/api`)}`);
7619
+ console.log(` Frontend: ${chalk34.cyan(`https://${traefikDomain}`)}`);
7620
+ console.log(` API: ${chalk34.cyan(`https://${traefikDomain}/api`)}`);
7332
7621
  } else {
7333
- console.log(` Frontend: ${chalk33.cyan("http://localhost:3001")}`);
7334
- console.log(` API: ${chalk33.cyan("http://localhost:3002")}`);
7622
+ console.log(` Frontend: ${chalk34.cyan("http://localhost:3001")}`);
7623
+ console.log(` API: ${chalk34.cyan("http://localhost:3002")}`);
7335
7624
  }
7336
- console.log(chalk33.dim("\nPress Ctrl+C to stop\n"));
7337
- const child = spawn("npm", ["run", "dev"], {
7338
- cwd: dashboardDir,
7625
+ console.log(chalk34.dim("\nPress Ctrl+C to stop\n"));
7626
+ const child = isProduction ? spawn("node", [bundledServer], {
7627
+ stdio: "inherit"
7628
+ }) : spawn("npm", ["run", "dev"], {
7629
+ cwd: srcDashboard,
7339
7630
  stdio: "inherit",
7340
7631
  shell: true
7341
7632
  });
7342
7633
  child.on("error", (err) => {
7343
- console.error(chalk33.red("Failed to start dashboard:"), err.message);
7634
+ console.error(chalk34.red("Failed to start dashboard:"), err.message);
7344
7635
  process.exit(1);
7345
7636
  });
7346
7637
  }
7347
7638
  });
7348
7639
  program.command("down").description("Stop dashboard (and Traefik if enabled)").option("--skip-traefik", "Skip Traefik shutdown").action(async (options) => {
7349
7640
  const { execSync: execSync17 } = await import("child_process");
7350
- const { join: join31 } = await import("path");
7351
- const { readFileSync: readFileSync25, existsSync: existsSync30 } = await import("fs");
7641
+ const { join: join32 } = await import("path");
7642
+ const { readFileSync: readFileSync26, existsSync: existsSync31 } = await import("fs");
7352
7643
  const { parse: parse2 } = await import("@iarna/toml");
7353
- console.log(chalk33.bold("Stopping Panopticon...\n"));
7354
- console.log(chalk33.dim("Stopping dashboard..."));
7644
+ console.log(chalk34.bold("Stopping Panopticon...\n"));
7645
+ console.log(chalk34.dim("Stopping dashboard..."));
7355
7646
  try {
7356
7647
  execSync17("lsof -ti:3001 | xargs kill -9 2>/dev/null || true", { stdio: "pipe" });
7357
7648
  execSync17("lsof -ti:3002 | xargs kill -9 2>/dev/null || true", { stdio: "pipe" });
7358
- console.log(chalk33.green("\u2713 Dashboard stopped"));
7649
+ console.log(chalk34.green("\u2713 Dashboard stopped"));
7359
7650
  } catch {
7360
- console.log(chalk33.dim(" No dashboard processes found"));
7651
+ console.log(chalk34.dim(" No dashboard processes found"));
7361
7652
  }
7362
- const configFile = join31(process.env.HOME || "", ".panopticon", "config.toml");
7653
+ const configFile = join32(process.env.HOME || "", ".panopticon", "config.toml");
7363
7654
  let traefikEnabled = false;
7364
- if (existsSync30(configFile)) {
7655
+ if (existsSync31(configFile)) {
7365
7656
  try {
7366
- const configContent = readFileSync25(configFile, "utf-8");
7657
+ const configContent = readFileSync26(configFile, "utf-8");
7367
7658
  const config = parse2(configContent);
7368
7659
  traefikEnabled = config.traefik?.enabled === true;
7369
7660
  } catch (error) {
7370
7661
  }
7371
7662
  }
7372
7663
  if (traefikEnabled && !options.skipTraefik) {
7373
- const traefikDir = join31(process.env.HOME || "", ".panopticon", "traefik");
7374
- if (existsSync30(traefikDir)) {
7375
- console.log(chalk33.dim("Stopping Traefik..."));
7664
+ const traefikDir = join32(process.env.HOME || "", ".panopticon", "traefik");
7665
+ if (existsSync31(traefikDir)) {
7666
+ console.log(chalk34.dim("Stopping Traefik..."));
7376
7667
  try {
7377
7668
  execSync17("docker-compose down", {
7378
7669
  cwd: traefikDir,
7379
7670
  stdio: "pipe"
7380
7671
  });
7381
- console.log(chalk33.green("\u2713 Traefik stopped"));
7672
+ console.log(chalk34.green("\u2713 Traefik stopped"));
7382
7673
  } catch (error) {
7383
- console.log(chalk33.yellow("\u26A0 Failed to stop Traefik"));
7674
+ console.log(chalk34.yellow("\u26A0 Failed to stop Traefik"));
7384
7675
  }
7385
7676
  }
7386
7677
  }