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