panopticon-cli 0.3.6 → 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
@@ -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);
@@ -4052,7 +4052,7 @@ async function destroyCommand(issueId, options) {
4052
4052
  import chalk22 from "chalk";
4053
4053
  import ora12 from "ora";
4054
4054
  import { execSync as execSync8 } from "child_process";
4055
- import { existsSync as existsSync18, mkdirSync as mkdirSync12, readFileSync as readFileSync15, copyFileSync, readdirSync as readdirSync11, statSync } from "fs";
4055
+ import { existsSync as existsSync18, mkdirSync as mkdirSync12, writeFileSync as writeFileSync11, readFileSync as readFileSync15, copyFileSync, readdirSync as readdirSync11, statSync } from "fs";
4056
4056
  import { join as join17 } from "path";
4057
4057
  import { homedir as homedir6, platform } from "os";
4058
4058
  function registerInstallCommand(program2) {
@@ -4273,6 +4273,18 @@ async function installCommand(options) {
4273
4273
  } else {
4274
4274
  spinner.info("Traefik configuration already exists (skipping)");
4275
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
+ }
4276
4288
  } catch (error) {
4277
4289
  spinner.fail(`Failed to set up Traefik configuration: ${error}`);
4278
4290
  console.log(chalk22.yellow("You can set up Traefik manually later"));
@@ -4475,8 +4487,8 @@ function evaluateHealthState(timeSinceActivityMs, thresholds) {
4475
4487
  }
4476
4488
  function getAgentHealth2(agentId, runtime, thresholds) {
4477
4489
  const thresholdsMs = thresholds || getHealthThresholdsMs();
4478
- const isRunning2 = runtime.isRunning(agentId);
4479
- if (!isRunning2) {
4490
+ const isRunning3 = runtime.isRunning(agentId);
4491
+ if (!isRunning3) {
4480
4492
  return {
4481
4493
  agentId,
4482
4494
  state: "stuck",
@@ -4628,7 +4640,7 @@ function cleanupOldEvents(database = getHealthDatabase(), retentionDays = RETENT
4628
4640
  }
4629
4641
 
4630
4642
  // src/lib/cloister/specialists.ts
4631
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync13, existsSync as existsSync22, mkdirSync as mkdirSync15, readdirSync as readdirSync13, unlinkSync as unlinkSync2 } from "fs";
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";
4632
4644
  import { join as join22, basename as basename4 } from "path";
4633
4645
  import { execSync as execSync9 } from "child_process";
4634
4646
 
@@ -5079,9 +5091,66 @@ async function initializeEnabledSpecialists() {
5079
5091
  }
5080
5092
  return results;
5081
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
+ }
5082
5149
  function checkSpecialistQueue(specialistName) {
5083
5150
  return checkHook(specialistName);
5084
5151
  }
5152
+ var FEEDBACK_DIR = join22(PANOPTICON_HOME, "specialists", "feedback");
5153
+ var FEEDBACK_LOG = join22(FEEDBACK_DIR, "feedback.jsonl");
5085
5154
 
5086
5155
  // src/lib/runtimes/claude-code.ts
5087
5156
  import { existsSync as existsSync23, readFileSync as readFileSync19, statSync as statSync3, mkdirSync as mkdirSync16, writeFileSync as writeFileSync14 } from "fs";
@@ -5963,8 +6032,25 @@ async function performSpecialistWake(state, options) {
5963
6032
  error: "Could not determine specialist name from agent ID"
5964
6033
  };
5965
6034
  }
5966
- console.warn(`Specialist wake not yet fully implemented, falling back to kill-spawn`);
5967
- 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
+ };
5968
6054
  } catch (error) {
5969
6055
  return {
5970
6056
  success: false,
@@ -5997,7 +6083,7 @@ function sleep(ms) {
5997
6083
  }
5998
6084
 
5999
6085
  // src/lib/cloister/handoff-logger.ts
6000
- import { existsSync as existsSync27, mkdirSync as mkdirSync18, appendFileSync as appendFileSync3, readFileSync as readFileSync21 } from "fs";
6086
+ import { existsSync as existsSync27, mkdirSync as mkdirSync18, appendFileSync as appendFileSync4, readFileSync as readFileSync21, writeFileSync as writeFileSync16 } from "fs";
6001
6087
  import { join as join27 } from "path";
6002
6088
  var HANDOFF_LOG_FILE = join27(PANOPTICON_HOME, "logs", "handoffs.jsonl");
6003
6089
  function ensureLogDir() {
@@ -6009,7 +6095,7 @@ function ensureLogDir() {
6009
6095
  function logHandoffEvent(event) {
6010
6096
  ensureLogDir();
6011
6097
  const line = JSON.stringify(event) + "\n";
6012
- appendFileSync3(HANDOFF_LOG_FILE, line, "utf-8");
6098
+ appendFileSync4(HANDOFF_LOG_FILE, line, "utf-8");
6013
6099
  }
6014
6100
  function createHandoffEvent(agentId, issueId, context, trigger, success, errorMessage) {
6015
6101
  let stuckMinutes;
@@ -6521,7 +6607,7 @@ function registerCloisterCommands(program2) {
6521
6607
 
6522
6608
  // src/cli/commands/setup/hooks.ts
6523
6609
  import chalk26 from "chalk";
6524
- import { readFileSync as readFileSync22, writeFileSync as writeFileSync16, existsSync as existsSync28, mkdirSync as mkdirSync19, copyFileSync as copyFileSync2, chmodSync as chmodSync2 } from "fs";
6610
+ import { readFileSync as readFileSync22, writeFileSync as writeFileSync17, existsSync as existsSync28, mkdirSync as mkdirSync19, copyFileSync as copyFileSync2, chmodSync as chmodSync2 } from "fs";
6525
6611
  import { join as join28 } from "path";
6526
6612
  import { execSync as execSync12 } from "child_process";
6527
6613
  import { homedir as homedir9 } from "os";
@@ -6662,7 +6748,7 @@ async function setupHooksCommand() {
6662
6748
  }
6663
6749
  ]
6664
6750
  });
6665
- writeFileSync16(settingsPath, JSON.stringify(settings, null, 2));
6751
+ writeFileSync17(settingsPath, JSON.stringify(settings, null, 2));
6666
6752
  console.log(chalk26.green("\u2713 Updated Claude Code settings.json"));
6667
6753
  console.log(chalk26.green.bold("\n\u2713 Setup complete!\n"));
6668
6754
  console.log(chalk26.dim("Heartbeat hooks are now active. When you run agents via"));
@@ -7451,6 +7537,19 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
7451
7537
  const traefikDir = join32(process.env.HOME || "", ".panopticon", "traefik");
7452
7538
  if (existsSync31(traefikDir)) {
7453
7539
  try {
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
+ }
7454
7553
  console.log(chalk34.dim("Starting Traefik..."));
7455
7554
  execSync17("docker-compose up -d", {
7456
7555
  cwd: traefikDir,