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 +21 -0
- package/README.md +242 -0
- package/dist/chunk-FR2P66GU.js +352 -0
- package/dist/chunk-FR2P66GU.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +3439 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +108 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
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
|