panopticon-cli 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Edward Becker <edward.becker@mindyournow.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,242 @@
1
+ # Panopticon CLI
2
+
3
+ Multi-agent orchestration for AI coding assistants.
4
+
5
+ > *"The Panopticon had six sides, one for each of the Founders of Gallifrey..."*
6
+
7
+ ## Overview
8
+
9
+ Panopticon is a unified orchestration layer for AI coding assistants. It works with:
10
+
11
+ | Tool | Support |
12
+ |------|---------|
13
+ | **Claude Code** | Full support |
14
+ | **Codex** | Skills sync |
15
+ | **Cursor** | Skills sync |
16
+ | **Gemini CLI** | Skills sync |
17
+ | **Google Antigravity** | Skills sync |
18
+
19
+ ### Features
20
+
21
+ - **Multi-agent orchestration** - Spawn and manage multiple AI agents in tmux sessions
22
+ - **Universal skills** - One SKILL.md format works across all supported tools
23
+ - **GUPP Hooks** - Self-propelling agents that auto-resume work
24
+ - **Health Monitoring** - Deacon-style stuck detection with auto-recovery
25
+ - **Context Engineering** - Structured state management (STATE.md, WORKSPACE.md)
26
+ - **Agent CVs** - Work history tracking for capability-based routing
27
+
28
+ ## Quick Start
29
+
30
+ ```bash
31
+ # Install Panopticon
32
+ npm install -g panopticon-cli
33
+
34
+ # Initialize configuration
35
+ pan init
36
+
37
+ # Sync skills to all AI tools
38
+ pan sync
39
+
40
+ # Check system health
41
+ pan doctor
42
+ ```
43
+
44
+ ## Requirements
45
+
46
+ - Node.js 18+
47
+ - tmux (for agent sessions)
48
+ - Git (for worktree-based workspaces)
49
+ - Linear API key (for issue tracking)
50
+
51
+ ## Configuration
52
+
53
+ Create `~/.panopticon.env`:
54
+
55
+ ```bash
56
+ LINEAR_API_KEY=lin_api_xxxxx
57
+ GITHUB_TOKEN=ghp_xxxxx # Optional: for secondary tracker
58
+ ```
59
+
60
+ ## Commands
61
+
62
+ ### Core Commands
63
+
64
+ ```bash
65
+ pan init # Initialize ~/.panopticon/
66
+ pan sync # Sync skills to all AI tools
67
+ pan doctor # Check system health
68
+ pan skills # List available skills
69
+ pan status # Show running agents
70
+ ```
71
+
72
+ ### Agent Management
73
+
74
+ ```bash
75
+ # Spawn an agent for a Linear issue
76
+ pan work issue MIN-123
77
+
78
+ # List all running agents
79
+ pan work status
80
+
81
+ # Send a message to an agent
82
+ pan work tell min-123 "Please also add tests"
83
+
84
+ # Kill an agent
85
+ pan work kill min-123
86
+ ```
87
+
88
+ ### Health Monitoring
89
+
90
+ ```bash
91
+ # Run a health check
92
+ pan work health check
93
+
94
+ # Show health status of all agents
95
+ pan work health status
96
+
97
+ # Start the health daemon (background monitoring)
98
+ pan work health daemon --interval 30
99
+ ```
100
+
101
+ ### GUPP Hooks
102
+
103
+ ```bash
104
+ # Check for pending work on hook
105
+ pan work hook check
106
+
107
+ # Push work to an agent's hook
108
+ pan work hook push agent-min-123 "Continue with tests"
109
+
110
+ # Send mail to an agent
111
+ pan work hook mail agent-min-123 "Review feedback received"
112
+ ```
113
+
114
+ ### Project Management
115
+
116
+ ```bash
117
+ # Register a project
118
+ pan project add /path/to/project --name myproject
119
+
120
+ # List managed projects
121
+ pan project list
122
+
123
+ # Remove a project
124
+ pan project remove myproject
125
+ ```
126
+
127
+ ### Context Management
128
+
129
+ ```bash
130
+ # Show agent state
131
+ pan work context state agent-min-123
132
+
133
+ # Set a checkpoint
134
+ pan work context checkpoint "Completed auth module"
135
+
136
+ # Search history
137
+ pan work context history "test"
138
+ ```
139
+
140
+ ### Agent CVs
141
+
142
+ ```bash
143
+ # View an agent's CV (work history)
144
+ pan work cv agent-min-123
145
+
146
+ # Show agent rankings by success rate
147
+ pan work cv --rankings
148
+ ```
149
+
150
+ ### Crash Recovery
151
+
152
+ ```bash
153
+ # Recover a specific crashed agent
154
+ pan work recover min-123
155
+
156
+ # Auto-recover all crashed agents
157
+ pan work recover --all
158
+ ```
159
+
160
+ ## Dashboard
161
+
162
+ Start the monitoring dashboard:
163
+
164
+ ```bash
165
+ pan up
166
+ ```
167
+
168
+ - Frontend: http://localhost:3001
169
+ - API: http://localhost:3002
170
+
171
+ Stop with `pan down`.
172
+
173
+ ## Skills
174
+
175
+ Panopticon ships with 10+ high-value skills:
176
+
177
+ | Skill | Description |
178
+ |-------|-------------|
179
+ | `feature-work` | Standard feature development workflow |
180
+ | `bug-fix` | Systematic bug investigation and fix |
181
+ | `code-review` | Comprehensive code review checklist |
182
+ | `code-review-security` | OWASP Top 10 security analysis |
183
+ | `code-review-performance` | Algorithm and resource optimization |
184
+ | `refactor` | Safe refactoring with test coverage |
185
+ | `release` | Step-by-step release process |
186
+ | `incident-response` | Production incident handling |
187
+ | `dependency-update` | Safe dependency updates |
188
+ | `onboard-codebase` | Understanding new codebases |
189
+
190
+ Skills are synced to all supported AI tools via symlinks:
191
+
192
+ ```bash
193
+ ~/.panopticon/skills/ # Canonical source
194
+ ↓ pan sync
195
+ ~/.claude/skills/ # Claude Code + Cursor
196
+ ~/.codex/skills/ # Codex
197
+ ~/.gemini/skills/ # Gemini CLI
198
+ ```
199
+
200
+ ## Architecture
201
+
202
+ ```
203
+ ~/.panopticon/
204
+ skills/ # Shared skills (SKILL.md format)
205
+ commands/ # Slash commands
206
+ agents/ # Per-agent state
207
+ agent-min-123/
208
+ state.json # Agent state
209
+ health.json # Health status
210
+ hook.json # GUPP work queue
211
+ cv.json # Work history
212
+ mail/ # Incoming messages
213
+ projects.json # Managed projects
214
+ backups/ # Sync backups
215
+ ```
216
+
217
+ ## Health Monitoring (Deacon Pattern)
218
+
219
+ Panopticon implements the Deacon pattern for stuck agent detection:
220
+
221
+ - **Ping timeout**: 30 seconds
222
+ - **Consecutive failures**: 3 before recovery
223
+ - **Cooldown**: 5 minutes between force-kills
224
+
225
+ When an agent is stuck (no activity for 30+ minutes), Panopticon will:
226
+ 1. Force kill the tmux session
227
+ 2. Record the kill in health.json
228
+ 3. Respawn with crash recovery context
229
+
230
+ ## GUPP (Give Up Push Pop)
231
+
232
+ > "If there is work on your Hook, YOU MUST RUN IT."
233
+
234
+ GUPP ensures agents are self-propelling:
235
+ 1. Work items are pushed to the agent's hook
236
+ 2. On spawn/recovery, the hook is checked
237
+ 3. Pending work is injected into the agent's prompt
238
+ 4. Completed work is popped from the hook
239
+
240
+ ## License
241
+
242
+ MIT
@@ -0,0 +1,352 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/lib/paths.ts
9
+ import { homedir } from "os";
10
+ import { join } from "path";
11
+ var PANOPTICON_HOME = join(homedir(), ".panopticon");
12
+ var CONFIG_DIR = PANOPTICON_HOME;
13
+ var SKILLS_DIR = join(PANOPTICON_HOME, "skills");
14
+ var COMMANDS_DIR = join(PANOPTICON_HOME, "commands");
15
+ var AGENTS_DIR = join(PANOPTICON_HOME, "agents");
16
+ var BACKUPS_DIR = join(PANOPTICON_HOME, "backups");
17
+ var COSTS_DIR = join(PANOPTICON_HOME, "costs");
18
+ var CONFIG_FILE = join(CONFIG_DIR, "config.toml");
19
+ var CLAUDE_DIR = join(homedir(), ".claude");
20
+ var CODEX_DIR = join(homedir(), ".codex");
21
+ var CURSOR_DIR = join(homedir(), ".cursor");
22
+ var GEMINI_DIR = join(homedir(), ".gemini");
23
+ var SYNC_TARGETS = {
24
+ claude: {
25
+ skills: join(CLAUDE_DIR, "skills"),
26
+ commands: join(CLAUDE_DIR, "commands")
27
+ },
28
+ codex: {
29
+ skills: join(CODEX_DIR, "skills"),
30
+ commands: join(CODEX_DIR, "commands")
31
+ },
32
+ cursor: {
33
+ skills: join(CURSOR_DIR, "skills"),
34
+ commands: join(CURSOR_DIR, "commands")
35
+ },
36
+ gemini: {
37
+ skills: join(GEMINI_DIR, "skills"),
38
+ commands: join(GEMINI_DIR, "commands")
39
+ }
40
+ };
41
+ var TEMPLATES_DIR = join(PANOPTICON_HOME, "templates");
42
+ var CLAUDE_MD_TEMPLATES = join(TEMPLATES_DIR, "claude-md", "sections");
43
+ var INIT_DIRS = [
44
+ PANOPTICON_HOME,
45
+ SKILLS_DIR,
46
+ COMMANDS_DIR,
47
+ AGENTS_DIR,
48
+ BACKUPS_DIR,
49
+ COSTS_DIR,
50
+ TEMPLATES_DIR,
51
+ CLAUDE_MD_TEMPLATES
52
+ ];
53
+
54
+ // src/lib/config.ts
55
+ import { readFileSync, writeFileSync, existsSync } from "fs";
56
+ import { parse, stringify } from "@iarna/toml";
57
+ var DEFAULT_CONFIG = {
58
+ panopticon: {
59
+ version: "1.0.0"
60
+ },
61
+ sync: {
62
+ targets: ["claude"],
63
+ backup_before_sync: true
64
+ },
65
+ trackers: {
66
+ primary: "linear"
67
+ },
68
+ dashboard: {
69
+ port: 3001,
70
+ api_port: 3002
71
+ }
72
+ };
73
+ function loadConfig() {
74
+ if (!existsSync(CONFIG_FILE)) {
75
+ return DEFAULT_CONFIG;
76
+ }
77
+ try {
78
+ const content = readFileSync(CONFIG_FILE, "utf8");
79
+ const parsed = parse(content);
80
+ return { ...DEFAULT_CONFIG, ...parsed };
81
+ } catch (error) {
82
+ console.error("Warning: Failed to parse config, using defaults");
83
+ return DEFAULT_CONFIG;
84
+ }
85
+ }
86
+ function saveConfig(config) {
87
+ const content = stringify(config);
88
+ writeFileSync(CONFIG_FILE, content, "utf8");
89
+ }
90
+ function getDefaultConfig() {
91
+ return { ...DEFAULT_CONFIG };
92
+ }
93
+
94
+ // src/lib/shell.ts
95
+ import { existsSync as existsSync2, readFileSync as readFileSync2, appendFileSync } from "fs";
96
+ import { homedir as homedir2 } from "os";
97
+ import { join as join2 } from "path";
98
+ function detectShell() {
99
+ const shell = process.env.SHELL || "";
100
+ if (shell.includes("zsh")) return "zsh";
101
+ if (shell.includes("bash")) return "bash";
102
+ if (shell.includes("fish")) return "fish";
103
+ return "unknown";
104
+ }
105
+ function getShellRcFile(shell) {
106
+ const home = homedir2();
107
+ switch (shell) {
108
+ case "zsh":
109
+ return join2(home, ".zshrc");
110
+ case "bash":
111
+ const bashrc = join2(home, ".bashrc");
112
+ if (existsSync2(bashrc)) return bashrc;
113
+ return join2(home, ".bash_profile");
114
+ case "fish":
115
+ return join2(home, ".config", "fish", "config.fish");
116
+ default:
117
+ return null;
118
+ }
119
+ }
120
+ var ALIAS_LINE = 'alias pan="panopticon"';
121
+ var ALIAS_MARKER = "# Panopticon CLI alias";
122
+ function hasAlias(rcFile) {
123
+ if (!existsSync2(rcFile)) return false;
124
+ const content = readFileSync2(rcFile, "utf8");
125
+ return content.includes(ALIAS_MARKER) || content.includes(ALIAS_LINE);
126
+ }
127
+ function addAlias(rcFile) {
128
+ if (hasAlias(rcFile)) return;
129
+ const aliasBlock = `
130
+ ${ALIAS_MARKER}
131
+ ${ALIAS_LINE}
132
+ `;
133
+ appendFileSync(rcFile, aliasBlock, "utf8");
134
+ }
135
+ function getAliasInstructions(shell) {
136
+ const rcFile = getShellRcFile(shell);
137
+ if (!rcFile) {
138
+ return `Add this to your shell config:
139
+ ${ALIAS_LINE}`;
140
+ }
141
+ return `Alias added to ${rcFile}. Run:
142
+ source ${rcFile}`;
143
+ }
144
+
145
+ // src/lib/backup.ts
146
+ import { existsSync as existsSync3, mkdirSync, readdirSync, cpSync, rmSync } from "fs";
147
+ import { join as join3, basename } from "path";
148
+ function createBackupTimestamp() {
149
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
150
+ }
151
+ function createBackup(sourceDirs) {
152
+ const timestamp = createBackupTimestamp();
153
+ const backupPath = join3(BACKUPS_DIR, timestamp);
154
+ mkdirSync(backupPath, { recursive: true });
155
+ const targets = [];
156
+ for (const sourceDir of sourceDirs) {
157
+ if (!existsSync3(sourceDir)) continue;
158
+ const targetName = basename(sourceDir);
159
+ const targetPath = join3(backupPath, targetName);
160
+ cpSync(sourceDir, targetPath, { recursive: true });
161
+ targets.push(targetName);
162
+ }
163
+ return {
164
+ timestamp,
165
+ path: backupPath,
166
+ targets
167
+ };
168
+ }
169
+ function listBackups() {
170
+ if (!existsSync3(BACKUPS_DIR)) return [];
171
+ const entries = readdirSync(BACKUPS_DIR, { withFileTypes: true });
172
+ return entries.filter((e) => e.isDirectory()).map((e) => {
173
+ const backupPath = join3(BACKUPS_DIR, e.name);
174
+ const contents = readdirSync(backupPath);
175
+ return {
176
+ timestamp: e.name,
177
+ path: backupPath,
178
+ targets: contents
179
+ };
180
+ }).sort((a, b) => b.timestamp.localeCompare(a.timestamp));
181
+ }
182
+ function restoreBackup(timestamp, targetDirs) {
183
+ const backupPath = join3(BACKUPS_DIR, timestamp);
184
+ if (!existsSync3(backupPath)) {
185
+ throw new Error(`Backup not found: ${timestamp}`);
186
+ }
187
+ const contents = readdirSync(backupPath, { withFileTypes: true });
188
+ for (const entry of contents) {
189
+ if (!entry.isDirectory()) continue;
190
+ const sourcePath = join3(backupPath, entry.name);
191
+ const targetPath = targetDirs[entry.name];
192
+ if (!targetPath) continue;
193
+ if (existsSync3(targetPath)) {
194
+ rmSync(targetPath, { recursive: true });
195
+ }
196
+ cpSync(sourcePath, targetPath, { recursive: true });
197
+ }
198
+ }
199
+ function cleanOldBackups(keepCount = 10) {
200
+ const backups = listBackups();
201
+ if (backups.length <= keepCount) return 0;
202
+ const toRemove = backups.slice(keepCount);
203
+ let removed = 0;
204
+ for (const backup of toRemove) {
205
+ rmSync(backup.path, { recursive: true });
206
+ removed++;
207
+ }
208
+ return removed;
209
+ }
210
+
211
+ // src/lib/sync.ts
212
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, symlinkSync, unlinkSync, lstatSync, readlinkSync } from "fs";
213
+ import { join as join4 } from "path";
214
+ function isPanopticonSymlink(targetPath) {
215
+ if (!existsSync4(targetPath)) return false;
216
+ try {
217
+ const stats = lstatSync(targetPath);
218
+ if (!stats.isSymbolicLink()) return false;
219
+ const linkTarget = readlinkSync(targetPath);
220
+ return linkTarget.includes(".panopticon");
221
+ } catch {
222
+ return false;
223
+ }
224
+ }
225
+ function planSync(runtime) {
226
+ const targets = SYNC_TARGETS[runtime];
227
+ const plan = {
228
+ runtime,
229
+ skills: [],
230
+ commands: []
231
+ };
232
+ if (existsSync4(SKILLS_DIR)) {
233
+ const skills = readdirSync2(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
234
+ for (const skill of skills) {
235
+ const sourcePath = join4(SKILLS_DIR, skill.name);
236
+ const targetPath = join4(targets.skills, skill.name);
237
+ let status = "new";
238
+ if (existsSync4(targetPath)) {
239
+ if (isPanopticonSymlink(targetPath)) {
240
+ status = "symlink";
241
+ } else {
242
+ status = "conflict";
243
+ }
244
+ }
245
+ plan.skills.push({ name: skill.name, sourcePath, targetPath, status });
246
+ }
247
+ }
248
+ if (existsSync4(COMMANDS_DIR)) {
249
+ const commands = readdirSync2(COMMANDS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
250
+ for (const cmd of commands) {
251
+ const sourcePath = join4(COMMANDS_DIR, cmd.name);
252
+ const targetPath = join4(targets.commands, cmd.name);
253
+ let status = "new";
254
+ if (existsSync4(targetPath)) {
255
+ if (isPanopticonSymlink(targetPath)) {
256
+ status = "symlink";
257
+ } else {
258
+ status = "conflict";
259
+ }
260
+ }
261
+ plan.commands.push({ name: cmd.name, sourcePath, targetPath, status });
262
+ }
263
+ }
264
+ return plan;
265
+ }
266
+ function executeSync(runtime, options = {}) {
267
+ const plan = planSync(runtime);
268
+ const result = {
269
+ created: [],
270
+ skipped: [],
271
+ conflicts: []
272
+ };
273
+ const targets = SYNC_TARGETS[runtime];
274
+ mkdirSync2(targets.skills, { recursive: true });
275
+ mkdirSync2(targets.commands, { recursive: true });
276
+ for (const item of plan.skills) {
277
+ if (options.dryRun) {
278
+ if (item.status === "new" || item.status === "symlink") {
279
+ result.created.push(item.name);
280
+ } else {
281
+ result.conflicts.push(item.name);
282
+ }
283
+ continue;
284
+ }
285
+ if (item.status === "conflict" && !options.force) {
286
+ result.conflicts.push(item.name);
287
+ continue;
288
+ }
289
+ if (existsSync4(item.targetPath)) {
290
+ unlinkSync(item.targetPath);
291
+ }
292
+ symlinkSync(item.sourcePath, item.targetPath);
293
+ result.created.push(item.name);
294
+ }
295
+ for (const item of plan.commands) {
296
+ if (options.dryRun) {
297
+ if (item.status === "new" || item.status === "symlink") {
298
+ result.created.push(item.name);
299
+ } else {
300
+ result.conflicts.push(item.name);
301
+ }
302
+ continue;
303
+ }
304
+ if (item.status === "conflict" && !options.force) {
305
+ result.conflicts.push(item.name);
306
+ continue;
307
+ }
308
+ if (existsSync4(item.targetPath)) {
309
+ unlinkSync(item.targetPath);
310
+ }
311
+ symlinkSync(item.sourcePath, item.targetPath);
312
+ result.created.push(item.name);
313
+ }
314
+ return result;
315
+ }
316
+
317
+ export {
318
+ __require,
319
+ PANOPTICON_HOME,
320
+ CONFIG_DIR,
321
+ SKILLS_DIR,
322
+ COMMANDS_DIR,
323
+ AGENTS_DIR,
324
+ BACKUPS_DIR,
325
+ COSTS_DIR,
326
+ CONFIG_FILE,
327
+ CLAUDE_DIR,
328
+ CODEX_DIR,
329
+ CURSOR_DIR,
330
+ GEMINI_DIR,
331
+ SYNC_TARGETS,
332
+ TEMPLATES_DIR,
333
+ CLAUDE_MD_TEMPLATES,
334
+ INIT_DIRS,
335
+ loadConfig,
336
+ saveConfig,
337
+ getDefaultConfig,
338
+ detectShell,
339
+ getShellRcFile,
340
+ hasAlias,
341
+ addAlias,
342
+ getAliasInstructions,
343
+ createBackupTimestamp,
344
+ createBackup,
345
+ listBackups,
346
+ restoreBackup,
347
+ cleanOldBackups,
348
+ isPanopticonSymlink,
349
+ planSync,
350
+ executeSync
351
+ };
352
+ //# sourceMappingURL=chunk-FR2P66GU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/paths.ts","../src/lib/config.ts","../src/lib/shell.ts","../src/lib/backup.ts","../src/lib/sync.ts"],"sourcesContent":["import { homedir } from 'os';\nimport { join } from 'path';\n\n// Panopticon home directory\nexport const PANOPTICON_HOME = join(homedir(), '.panopticon');\n\n// Subdirectories\nexport const CONFIG_DIR = PANOPTICON_HOME;\nexport const SKILLS_DIR = join(PANOPTICON_HOME, 'skills');\nexport const COMMANDS_DIR = join(PANOPTICON_HOME, 'commands');\nexport const AGENTS_DIR = join(PANOPTICON_HOME, 'agents');\nexport const BACKUPS_DIR = join(PANOPTICON_HOME, 'backups');\nexport const COSTS_DIR = join(PANOPTICON_HOME, 'costs');\n\n// Config files\nexport const CONFIG_FILE = join(CONFIG_DIR, 'config.toml');\n\n// AI tool directories\nexport const CLAUDE_DIR = join(homedir(), '.claude');\nexport const CODEX_DIR = join(homedir(), '.codex');\nexport const CURSOR_DIR = join(homedir(), '.cursor');\nexport const GEMINI_DIR = join(homedir(), '.gemini');\n\n// Target sync locations\nexport const SYNC_TARGETS = {\n claude: {\n skills: join(CLAUDE_DIR, 'skills'),\n commands: join(CLAUDE_DIR, 'commands'),\n },\n codex: {\n skills: join(CODEX_DIR, 'skills'),\n commands: join(CODEX_DIR, 'commands'),\n },\n cursor: {\n skills: join(CURSOR_DIR, 'skills'),\n commands: join(CURSOR_DIR, 'commands'),\n },\n gemini: {\n skills: join(GEMINI_DIR, 'skills'),\n commands: join(GEMINI_DIR, 'commands'),\n },\n} as const;\n\nexport type Runtime = keyof typeof SYNC_TARGETS;\n\n// Templates directory\nexport const TEMPLATES_DIR = join(PANOPTICON_HOME, 'templates');\nexport const CLAUDE_MD_TEMPLATES = join(TEMPLATES_DIR, 'claude-md', 'sections');\n\n// All directories to create on init\nexport const INIT_DIRS = [\n PANOPTICON_HOME,\n SKILLS_DIR,\n COMMANDS_DIR,\n AGENTS_DIR,\n BACKUPS_DIR,\n COSTS_DIR,\n TEMPLATES_DIR,\n CLAUDE_MD_TEMPLATES,\n];\n","import { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { parse, stringify } from '@iarna/toml';\nimport { CONFIG_FILE } from './paths.js';\n\nexport interface PanopticonConfig {\n panopticon: {\n version: string;\n };\n sync: {\n targets: string[]; // 'claude', 'codex', 'cursor', 'gemini'\n backup_before_sync: boolean;\n };\n trackers: {\n primary: string; // 'linear' or 'github'\n secondary?: string;\n };\n dashboard: {\n port: number;\n api_port: number;\n };\n}\n\nconst DEFAULT_CONFIG: PanopticonConfig = {\n panopticon: {\n version: '1.0.0',\n },\n sync: {\n targets: ['claude'],\n backup_before_sync: true,\n },\n trackers: {\n primary: 'linear',\n },\n dashboard: {\n port: 3001,\n api_port: 3002,\n },\n};\n\nexport function loadConfig(): PanopticonConfig {\n if (!existsSync(CONFIG_FILE)) {\n return DEFAULT_CONFIG;\n }\n\n try {\n const content = readFileSync(CONFIG_FILE, 'utf8');\n const parsed = parse(content) as unknown as PanopticonConfig;\n return { ...DEFAULT_CONFIG, ...parsed };\n } catch (error) {\n console.error('Warning: Failed to parse config, using defaults');\n return DEFAULT_CONFIG;\n }\n}\n\nexport function saveConfig(config: PanopticonConfig): void {\n const content = stringify(config as any);\n writeFileSync(CONFIG_FILE, content, 'utf8');\n}\n\nexport function getDefaultConfig(): PanopticonConfig {\n return { ...DEFAULT_CONFIG };\n}\n","import { existsSync, readFileSync, appendFileSync } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport type Shell = 'bash' | 'zsh' | 'fish' | 'unknown';\n\nexport function detectShell(): Shell {\n const shell = process.env.SHELL || '';\n\n if (shell.includes('zsh')) return 'zsh';\n if (shell.includes('bash')) return 'bash';\n if (shell.includes('fish')) return 'fish';\n\n return 'unknown';\n}\n\nexport function getShellRcFile(shell: Shell): string | null {\n const home = homedir();\n\n switch (shell) {\n case 'zsh':\n return join(home, '.zshrc');\n case 'bash':\n // Prefer .bashrc, fall back to .bash_profile\n const bashrc = join(home, '.bashrc');\n if (existsSync(bashrc)) return bashrc;\n return join(home, '.bash_profile');\n case 'fish':\n return join(home, '.config', 'fish', 'config.fish');\n default:\n return null;\n }\n}\n\nconst ALIAS_LINE = 'alias pan=\"panopticon\"';\nconst ALIAS_MARKER = '# Panopticon CLI alias';\n\nexport function hasAlias(rcFile: string): boolean {\n if (!existsSync(rcFile)) return false;\n\n const content = readFileSync(rcFile, 'utf8');\n return content.includes(ALIAS_MARKER) || content.includes(ALIAS_LINE);\n}\n\nexport function addAlias(rcFile: string): void {\n if (hasAlias(rcFile)) return;\n\n const aliasBlock = `\n${ALIAS_MARKER}\n${ALIAS_LINE}\n`;\n\n appendFileSync(rcFile, aliasBlock, 'utf8');\n}\n\nexport function getAliasInstructions(shell: Shell): string {\n const rcFile = getShellRcFile(shell);\n\n if (!rcFile) {\n return `Add this to your shell config:\\n ${ALIAS_LINE}`;\n }\n\n return `Alias added to ${rcFile}. Run:\\n source ${rcFile}`;\n}\n","import { existsSync, mkdirSync, readdirSync, cpSync, rmSync } from 'fs';\nimport { join, basename } from 'path';\nimport { BACKUPS_DIR } from './paths.js';\n\nexport interface BackupInfo {\n timestamp: string;\n path: string;\n targets: string[];\n}\n\nexport function createBackupTimestamp(): string {\n return new Date().toISOString().replace(/[:.]/g, '-');\n}\n\nexport function createBackup(sourceDirs: string[]): BackupInfo {\n const timestamp = createBackupTimestamp();\n const backupPath = join(BACKUPS_DIR, timestamp);\n\n mkdirSync(backupPath, { recursive: true });\n\n const targets: string[] = [];\n\n for (const sourceDir of sourceDirs) {\n if (!existsSync(sourceDir)) continue;\n\n const targetName = basename(sourceDir);\n const targetPath = join(backupPath, targetName);\n\n cpSync(sourceDir, targetPath, { recursive: true });\n targets.push(targetName);\n }\n\n return {\n timestamp,\n path: backupPath,\n targets,\n };\n}\n\nexport function listBackups(): BackupInfo[] {\n if (!existsSync(BACKUPS_DIR)) return [];\n\n const entries = readdirSync(BACKUPS_DIR, { withFileTypes: true });\n\n return entries\n .filter((e) => e.isDirectory())\n .map((e) => {\n const backupPath = join(BACKUPS_DIR, e.name);\n const contents = readdirSync(backupPath);\n\n return {\n timestamp: e.name,\n path: backupPath,\n targets: contents,\n };\n })\n .sort((a, b) => b.timestamp.localeCompare(a.timestamp));\n}\n\nexport function restoreBackup(timestamp: string, targetDirs: Record<string, string>): void {\n const backupPath = join(BACKUPS_DIR, timestamp);\n\n if (!existsSync(backupPath)) {\n throw new Error(`Backup not found: ${timestamp}`);\n }\n\n const contents = readdirSync(backupPath, { withFileTypes: true });\n\n for (const entry of contents) {\n if (!entry.isDirectory()) continue;\n\n const sourcePath = join(backupPath, entry.name);\n const targetPath = targetDirs[entry.name];\n\n if (!targetPath) continue;\n\n // Remove existing and restore from backup\n if (existsSync(targetPath)) {\n rmSync(targetPath, { recursive: true });\n }\n\n cpSync(sourcePath, targetPath, { recursive: true });\n }\n}\n\nexport function cleanOldBackups(keepCount: number = 10): number {\n const backups = listBackups();\n\n if (backups.length <= keepCount) return 0;\n\n const toRemove = backups.slice(keepCount);\n let removed = 0;\n\n for (const backup of toRemove) {\n rmSync(backup.path, { recursive: true });\n removed++;\n }\n\n return removed;\n}\n","import { existsSync, mkdirSync, readdirSync, symlinkSync, unlinkSync, lstatSync, readlinkSync } from 'fs';\nimport { join, basename } from 'path';\nimport { SKILLS_DIR, COMMANDS_DIR, SYNC_TARGETS, type Runtime } from './paths.js';\n\nexport interface SyncItem {\n name: string;\n sourcePath: string;\n targetPath: string;\n status: 'new' | 'exists' | 'conflict' | 'symlink';\n}\n\nexport interface SyncPlan {\n runtime: Runtime;\n skills: SyncItem[];\n commands: SyncItem[];\n}\n\n/**\n * Check if a path is a Panopticon-managed symlink\n */\nexport function isPanopticonSymlink(targetPath: string): boolean {\n if (!existsSync(targetPath)) return false;\n\n try {\n const stats = lstatSync(targetPath);\n if (!stats.isSymbolicLink()) return false;\n\n const linkTarget = readlinkSync(targetPath);\n // It's ours if it points to our skills/commands dir\n return linkTarget.includes('.panopticon');\n } catch {\n return false;\n }\n}\n\n/**\n * Plan what would be synced (dry run)\n */\nexport function planSync(runtime: Runtime): SyncPlan {\n const targets = SYNC_TARGETS[runtime];\n const plan: SyncPlan = {\n runtime,\n skills: [],\n commands: [],\n };\n\n // Plan skills sync\n if (existsSync(SKILLS_DIR)) {\n const skills = readdirSync(SKILLS_DIR, { withFileTypes: true })\n .filter((d) => d.isDirectory());\n\n for (const skill of skills) {\n const sourcePath = join(SKILLS_DIR, skill.name);\n const targetPath = join(targets.skills, skill.name);\n\n let status: SyncItem['status'] = 'new';\n\n if (existsSync(targetPath)) {\n if (isPanopticonSymlink(targetPath)) {\n status = 'symlink'; // Already managed by us\n } else {\n status = 'conflict'; // User content exists\n }\n }\n\n plan.skills.push({ name: skill.name, sourcePath, targetPath, status });\n }\n }\n\n // Plan commands sync\n if (existsSync(COMMANDS_DIR)) {\n const commands = readdirSync(COMMANDS_DIR, { withFileTypes: true })\n .filter((d) => d.isDirectory());\n\n for (const cmd of commands) {\n const sourcePath = join(COMMANDS_DIR, cmd.name);\n const targetPath = join(targets.commands, cmd.name);\n\n let status: SyncItem['status'] = 'new';\n\n if (existsSync(targetPath)) {\n if (isPanopticonSymlink(targetPath)) {\n status = 'symlink';\n } else {\n status = 'conflict';\n }\n }\n\n plan.commands.push({ name: cmd.name, sourcePath, targetPath, status });\n }\n }\n\n return plan;\n}\n\nexport interface SyncOptions {\n force?: boolean;\n dryRun?: boolean;\n}\n\nexport interface SyncResult {\n created: string[];\n skipped: string[];\n conflicts: string[];\n}\n\n/**\n * Execute sync for a runtime\n */\nexport function executeSync(runtime: Runtime, options: SyncOptions = {}): SyncResult {\n const plan = planSync(runtime);\n const result: SyncResult = {\n created: [],\n skipped: [],\n conflicts: [],\n };\n\n const targets = SYNC_TARGETS[runtime];\n\n // Ensure target directories exist\n mkdirSync(targets.skills, { recursive: true });\n mkdirSync(targets.commands, { recursive: true });\n\n // Process skills\n for (const item of plan.skills) {\n if (options.dryRun) {\n if (item.status === 'new' || item.status === 'symlink') {\n result.created.push(item.name);\n } else {\n result.conflicts.push(item.name);\n }\n continue;\n }\n\n if (item.status === 'conflict' && !options.force) {\n result.conflicts.push(item.name);\n continue;\n }\n\n // Remove existing if force or if it's our symlink\n if (existsSync(item.targetPath)) {\n unlinkSync(item.targetPath);\n }\n\n // Create symlink\n symlinkSync(item.sourcePath, item.targetPath);\n result.created.push(item.name);\n }\n\n // Process commands\n for (const item of plan.commands) {\n if (options.dryRun) {\n if (item.status === 'new' || item.status === 'symlink') {\n result.created.push(item.name);\n } else {\n result.conflicts.push(item.name);\n }\n continue;\n }\n\n if (item.status === 'conflict' && !options.force) {\n result.conflicts.push(item.name);\n continue;\n }\n\n if (existsSync(item.targetPath)) {\n unlinkSync(item.targetPath);\n }\n\n symlinkSync(item.sourcePath, item.targetPath);\n result.created.push(item.name);\n }\n\n return result;\n}\n"],"mappings":"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","names":["existsSync","readFileSync","homedir","join","existsSync","join","join","existsSync","existsSync","mkdirSync","readdirSync","join","existsSync","readdirSync","join","mkdirSync"]}
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node