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/README.md
CHANGED
|
@@ -340,6 +340,37 @@ Cloister manages specialized agents that handle specific phases of the developme
|
|
|
340
340
|
| **review-agent** | Code review before merge | After tests pass (manual trigger) |
|
|
341
341
|
| **merge-agent** | Handles git merge and conflict resolution | "Approve & Merge" button |
|
|
342
342
|
|
|
343
|
+
#### Merge Agent Workflow
|
|
344
|
+
|
|
345
|
+
The merge-agent is a specialist that handles the entire PR merge process:
|
|
346
|
+
|
|
347
|
+
1. **Pull latest main** - Ensures local main is up-to-date
|
|
348
|
+
2. **Merge main into feature branch** - Brings in any changes from main
|
|
349
|
+
3. **Resolve conflicts** - Uses AI to resolve merge conflicts intelligently
|
|
350
|
+
4. **Run tests** - Verifies the merge didn't break anything
|
|
351
|
+
5. **Push changes** - Pushes the resolved merge
|
|
352
|
+
6. **Create/Update PR** - Creates a PR if one doesn't exist
|
|
353
|
+
7. **Merge PR** - Merges the PR using `gh pr merge`
|
|
354
|
+
|
|
355
|
+
**Triggering merge-agent:**
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
# Via dashboard - click "Approve & Merge" on an issue card
|
|
359
|
+
|
|
360
|
+
# Via CLI
|
|
361
|
+
pan specialists wake merge-agent --issue MIN-123
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
The merge-agent uses a specialized prompt template that instructs it to:
|
|
365
|
+
- Never force-push
|
|
366
|
+
- Always run tests before merging
|
|
367
|
+
- Document conflict resolution decisions
|
|
368
|
+
- Update the issue status on success
|
|
369
|
+
|
|
370
|
+
#### Specialist Auto-Initialization
|
|
371
|
+
|
|
372
|
+
When Cloister starts, it automatically initializes specialists that don't exist yet. This ensures the test-agent, review-agent, and merge-agent are ready to receive wake signals without manual setup.
|
|
373
|
+
|
|
343
374
|
### Automatic Handoffs
|
|
344
375
|
|
|
345
376
|
Cloister detects situations that require intervention:
|
|
@@ -349,8 +380,55 @@ Cloister detects situations that require intervention:
|
|
|
349
380
|
| **stuck_escalation** | No activity for 30+ minutes | Escalate to more capable model |
|
|
350
381
|
| **complexity_upgrade** | Task complexity exceeds model capability | Route to Opus |
|
|
351
382
|
| **implementation_complete** | Agent signals work is done | Wake test-agent |
|
|
383
|
+
| **test_failure** | Tests fail repeatedly | Escalate model or request help |
|
|
384
|
+
| **planning_complete** | Planning session finishes | Transition to implementation |
|
|
352
385
|
| **merge_requested** | User clicks "Approve & Merge" | Wake merge-agent |
|
|
353
386
|
|
|
387
|
+
### Handoff Methods
|
|
388
|
+
|
|
389
|
+
Cloister supports two handoff methods, automatically selected based on agent type:
|
|
390
|
+
|
|
391
|
+
| Method | When Used | How It Works |
|
|
392
|
+
|--------|-----------|--------------|
|
|
393
|
+
| **Kill & Spawn** | General agents (agent-min-123, etc.) | 1. Captures full context (STATE.md, beads, git state)<br>2. Kills tmux session<br>3. Spawns new agent with handoff prompt<br>4. New agent continues work with preserved context |
|
|
394
|
+
| **Specialist Wake** | Permanent specialists (merge-agent, test-agent) | 1. Captures handoff context<br>2. Sends wake message to existing session<br>3. Specialist resumes with context injection |
|
|
395
|
+
|
|
396
|
+
**Kill & Spawn** is used for temporary agents that work on specific issues. It creates a clean handoff by:
|
|
397
|
+
- Capturing the agent's current understanding (from STATE.md)
|
|
398
|
+
- Preserving beads task progress and open items
|
|
399
|
+
- Including relevant git diff and file context
|
|
400
|
+
- Building a comprehensive handoff prompt for the new model
|
|
401
|
+
|
|
402
|
+
**Specialist Wake** is used for permanent specialists that persist across multiple issues. It avoids the overhead of killing/respawning by injecting context into the existing session.
|
|
403
|
+
|
|
404
|
+
### Handoff Context Capture
|
|
405
|
+
|
|
406
|
+
When a handoff occurs, Cloister captures:
|
|
407
|
+
|
|
408
|
+
```json
|
|
409
|
+
{
|
|
410
|
+
"agentId": "agent-min-123",
|
|
411
|
+
"issueId": "MIN-123",
|
|
412
|
+
"currentModel": "sonnet",
|
|
413
|
+
"targetModel": "opus",
|
|
414
|
+
"reason": "stuck_escalation",
|
|
415
|
+
"handoffCount": 1,
|
|
416
|
+
"state": {
|
|
417
|
+
"phase": "implementation",
|
|
418
|
+
"complexity": "complex",
|
|
419
|
+
"lastActivity": "2024-01-22T10:30:00-08:00"
|
|
420
|
+
},
|
|
421
|
+
"beadsTasks": [...],
|
|
422
|
+
"gitContext": {
|
|
423
|
+
"branch": "feature/min-123",
|
|
424
|
+
"uncommittedChanges": ["src/auth.ts", "src/tests/auth.test.ts"],
|
|
425
|
+
"recentCommits": [...]
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Handoff prompts are saved to `~/.panopticon/agents/{agent-id}/handoffs/` for debugging.
|
|
431
|
+
|
|
354
432
|
### Heartbeat Monitoring
|
|
355
433
|
|
|
356
434
|
Agents send heartbeats via Claude Code hooks. Cloister tracks:
|
|
@@ -373,6 +451,41 @@ Heartbeat files are stored in `~/.panopticon/heartbeats/`:
|
|
|
373
451
|
}
|
|
374
452
|
```
|
|
375
453
|
|
|
454
|
+
### Heartbeat Hook Installation
|
|
455
|
+
|
|
456
|
+
The heartbeat hook is automatically synced to `~/.panopticon/bin/heartbeat-hook` via `pan sync`. It's also installed automatically when you install or upgrade Panopticon via npm.
|
|
457
|
+
|
|
458
|
+
**Manual installation:**
|
|
459
|
+
```bash
|
|
460
|
+
pan sync # Syncs all skills, agents, AND hooks
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**Hook configuration in `~/.claude/settings.json`:**
|
|
464
|
+
```json
|
|
465
|
+
{
|
|
466
|
+
"hooks": {
|
|
467
|
+
"PostToolUse": [
|
|
468
|
+
{
|
|
469
|
+
"matcher": "*",
|
|
470
|
+
"hooks": [
|
|
471
|
+
{
|
|
472
|
+
"type": "command",
|
|
473
|
+
"command": "~/.panopticon/bin/heartbeat-hook"
|
|
474
|
+
}
|
|
475
|
+
]
|
|
476
|
+
}
|
|
477
|
+
]
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**Hook resilience:** The heartbeat hook is designed to fail silently if:
|
|
483
|
+
- The heartbeats directory doesn't exist
|
|
484
|
+
- Write permissions are missing
|
|
485
|
+
- The hook script has errors
|
|
486
|
+
|
|
487
|
+
This prevents hook failures from interrupting agent work.
|
|
488
|
+
|
|
376
489
|
### Configuration
|
|
377
490
|
|
|
378
491
|
Cloister configuration lives in `~/.panopticon/cloister/config.json`:
|
|
@@ -398,6 +511,77 @@ Cloister configuration lives in `~/.panopticon/cloister/config.json`:
|
|
|
398
511
|
|
|
399
512
|
---
|
|
400
513
|
|
|
514
|
+
## Model Routing & Complexity Detection
|
|
515
|
+
|
|
516
|
+
Cloister automatically routes tasks to the appropriate model based on detected complexity, optimizing for cost while ensuring quality.
|
|
517
|
+
|
|
518
|
+
### Complexity Levels
|
|
519
|
+
|
|
520
|
+
| Level | Model | Use Case |
|
|
521
|
+
|-------|-------|----------|
|
|
522
|
+
| **trivial** | Haiku | Typos, comments, documentation updates |
|
|
523
|
+
| **simple** | Haiku | Small fixes, test additions, minor changes |
|
|
524
|
+
| **medium** | Sonnet | Features, components, integrations |
|
|
525
|
+
| **complex** | Sonnet/Opus | Refactors, migrations, redesigns |
|
|
526
|
+
| **expert** | Opus | Architecture, security, performance optimization |
|
|
527
|
+
|
|
528
|
+
### Complexity Detection Signals
|
|
529
|
+
|
|
530
|
+
Complexity is detected from multiple signals (in priority order):
|
|
531
|
+
|
|
532
|
+
1. **Explicit field** - Task has a `complexity` field set (e.g., in beads)
|
|
533
|
+
2. **Labels/tags** - Issue labels like `architecture`, `security`, `refactor`
|
|
534
|
+
3. **Keywords** - Title/description contains keywords like "migration", "overhaul"
|
|
535
|
+
4. **File count** - Number of files changed (>20 files = complex)
|
|
536
|
+
5. **Time estimate** - If estimate exceeds thresholds
|
|
537
|
+
|
|
538
|
+
**Keyword patterns:**
|
|
539
|
+
```javascript
|
|
540
|
+
{
|
|
541
|
+
trivial: ['typo', 'rename', 'comment', 'documentation', 'readme'],
|
|
542
|
+
simple: ['add comment', 'update docs', 'fix typo', 'small fix'],
|
|
543
|
+
medium: ['feature', 'endpoint', 'component', 'service'],
|
|
544
|
+
complex: ['refactor', 'migration', 'redesign', 'overhaul'],
|
|
545
|
+
expert: ['architecture', 'security', 'performance optimization']
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Configuring Model Routing
|
|
550
|
+
|
|
551
|
+
Edit `~/.panopticon/cloister/config.json`:
|
|
552
|
+
|
|
553
|
+
```json
|
|
554
|
+
{
|
|
555
|
+
"model_selection": {
|
|
556
|
+
"default_model": "sonnet",
|
|
557
|
+
"complexity_routing": {
|
|
558
|
+
"trivial": "haiku",
|
|
559
|
+
"simple": "haiku",
|
|
560
|
+
"medium": "sonnet",
|
|
561
|
+
"complex": "sonnet",
|
|
562
|
+
"expert": "opus"
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Cost Optimization
|
|
569
|
+
|
|
570
|
+
Model routing helps optimize costs:
|
|
571
|
+
|
|
572
|
+
| Model | Relative Cost | Best For |
|
|
573
|
+
|-------|---------------|----------|
|
|
574
|
+
| Haiku | 1x (cheapest) | Simple tasks, bulk operations |
|
|
575
|
+
| Sonnet | 3x | Most development work |
|
|
576
|
+
| Opus | 15x | Complex architecture, critical fixes |
|
|
577
|
+
|
|
578
|
+
A typical agent run might:
|
|
579
|
+
1. Start on Haiku for initial exploration
|
|
580
|
+
2. Escalate to Sonnet for implementation
|
|
581
|
+
3. Escalate to Opus only if stuck or complexity detected
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
401
585
|
## Multi-Project Support
|
|
402
586
|
|
|
403
587
|
Panopticon supports managing multiple projects with intelligent issue routing.
|
|
@@ -462,9 +646,22 @@ pan sync --dry-run # Preview what will be synced
|
|
|
462
646
|
pan doctor # Check system health
|
|
463
647
|
pan skills # List available skills
|
|
464
648
|
pan status # Show running agents
|
|
649
|
+
pan up # Start dashboard (Docker or minimal)
|
|
650
|
+
pan down # Stop dashboard and services
|
|
465
651
|
```
|
|
466
652
|
|
|
467
|
-
|
|
653
|
+
#### What `pan sync` Does
|
|
654
|
+
|
|
655
|
+
`pan sync` synchronizes Panopticon assets to all supported AI tools:
|
|
656
|
+
|
|
657
|
+
| Asset Type | Source | Destinations |
|
|
658
|
+
|------------|--------|--------------|
|
|
659
|
+
| **Skills** | `~/.panopticon/skills/` | `~/.claude/skills/`, `~/.codex/skills/`, `~/.gemini/skills/` |
|
|
660
|
+
| **Agents** | `~/.panopticon/agents/*.md` | `~/.claude/agents/` |
|
|
661
|
+
| **Commands** | `~/.panopticon/commands/` | `~/.claude/commands/` |
|
|
662
|
+
| **Hooks** | `src/hooks/` (in package) | `~/.panopticon/bin/` |
|
|
663
|
+
|
|
664
|
+
**Automatic sync:** Hooks are also synced automatically when you install or upgrade Panopticon via npm (`postinstall` hook).
|
|
468
665
|
|
|
469
666
|
### Agent Management
|
|
470
667
|
|
|
@@ -1029,6 +1226,7 @@ This ensures every Panopticon-managed project has a well-defined canonical PRD t
|
|
|
1029
1226
|
hook.json # GUPP work queue
|
|
1030
1227
|
cv.json # Work history
|
|
1031
1228
|
mail/ # Incoming messages
|
|
1229
|
+
handoffs/ # Handoff prompts (for debugging)
|
|
1032
1230
|
|
|
1033
1231
|
cloister/ # Cloister AI lifecycle manager
|
|
1034
1232
|
config.json # Cloister settings
|
|
@@ -1038,6 +1236,9 @@ This ensures every Panopticon-managed project has a well-defined canonical PRD t
|
|
|
1038
1236
|
heartbeats/ # Real-time agent activity
|
|
1039
1237
|
agent-min-123.json # Last heartbeat from agent
|
|
1040
1238
|
|
|
1239
|
+
logs/ # Log files
|
|
1240
|
+
handoffs.jsonl # All handoff events (for analytics)
|
|
1241
|
+
|
|
1041
1242
|
costs/ # Raw cost logs (JSONL)
|
|
1042
1243
|
backups/ # Sync backups
|
|
1043
1244
|
traefik/ # Traefik reverse proxy config
|
|
@@ -1045,6 +1246,36 @@ This ensures every Panopticon-managed project has a well-defined canonical PRD t
|
|
|
1045
1246
|
certs/ # TLS certificates
|
|
1046
1247
|
```
|
|
1047
1248
|
|
|
1249
|
+
### Agent State Management
|
|
1250
|
+
|
|
1251
|
+
Each agent's state is tracked in `~/.panopticon/agents/{agent-id}/state.json`:
|
|
1252
|
+
|
|
1253
|
+
```json
|
|
1254
|
+
{
|
|
1255
|
+
"id": "agent-min-123",
|
|
1256
|
+
"issueId": "MIN-123",
|
|
1257
|
+
"workspace": "/home/user/projects/myapp/workspaces/feature-min-123",
|
|
1258
|
+
"branch": "feature/min-123",
|
|
1259
|
+
"phase": "implementation",
|
|
1260
|
+
"model": "sonnet",
|
|
1261
|
+
"complexity": "medium",
|
|
1262
|
+
"handoffCount": 0,
|
|
1263
|
+
"sessionId": "abc123",
|
|
1264
|
+
"createdAt": "2024-01-22T10:00:00-08:00",
|
|
1265
|
+
"updatedAt": "2024-01-22T10:30:00-08:00"
|
|
1266
|
+
}
|
|
1267
|
+
```
|
|
1268
|
+
|
|
1269
|
+
| Field | Description |
|
|
1270
|
+
|-------|-------------|
|
|
1271
|
+
| `phase` | Current work phase: `planning`, `implementation`, `testing`, `review`, `merging` |
|
|
1272
|
+
| `model` | Current model: `haiku`, `sonnet`, `opus` |
|
|
1273
|
+
| `complexity` | Detected complexity: `trivial`, `simple`, `medium`, `complex`, `expert` |
|
|
1274
|
+
| `handoffCount` | Number of times the agent has been handed off to a different model |
|
|
1275
|
+
| `sessionId` | Claude Code session ID (for resuming after handoff) |
|
|
1276
|
+
|
|
1277
|
+
**State Cleanup:** When an agent is killed or aborted (`pan work kill`), Panopticon automatically cleans up its state files to prevent stale data from affecting future runs.
|
|
1278
|
+
|
|
1048
1279
|
## Health Monitoring (Deacon Pattern)
|
|
1049
1280
|
|
|
1050
1281
|
Panopticon implements the Deacon pattern for stuck agent detection:
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// src/lib/paths.ts
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { dirname } from "path";
|
|
6
|
+
var PANOPTICON_HOME = join(homedir(), ".panopticon");
|
|
7
|
+
var CONFIG_DIR = PANOPTICON_HOME;
|
|
8
|
+
var SKILLS_DIR = join(PANOPTICON_HOME, "skills");
|
|
9
|
+
var COMMANDS_DIR = join(PANOPTICON_HOME, "commands");
|
|
10
|
+
var AGENTS_DIR = join(PANOPTICON_HOME, "agents");
|
|
11
|
+
var BIN_DIR = join(PANOPTICON_HOME, "bin");
|
|
12
|
+
var BACKUPS_DIR = join(PANOPTICON_HOME, "backups");
|
|
13
|
+
var COSTS_DIR = join(PANOPTICON_HOME, "costs");
|
|
14
|
+
var HEARTBEATS_DIR = join(PANOPTICON_HOME, "heartbeats");
|
|
15
|
+
var TRAEFIK_DIR = join(PANOPTICON_HOME, "traefik");
|
|
16
|
+
var TRAEFIK_DYNAMIC_DIR = join(TRAEFIK_DIR, "dynamic");
|
|
17
|
+
var TRAEFIK_CERTS_DIR = join(TRAEFIK_DIR, "certs");
|
|
18
|
+
var CERTS_DIR = join(PANOPTICON_HOME, "certs");
|
|
19
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config.toml");
|
|
20
|
+
var CLAUDE_DIR = join(homedir(), ".claude");
|
|
21
|
+
var CODEX_DIR = join(homedir(), ".codex");
|
|
22
|
+
var CURSOR_DIR = join(homedir(), ".cursor");
|
|
23
|
+
var GEMINI_DIR = join(homedir(), ".gemini");
|
|
24
|
+
var SYNC_TARGETS = {
|
|
25
|
+
claude: {
|
|
26
|
+
skills: join(CLAUDE_DIR, "skills"),
|
|
27
|
+
commands: join(CLAUDE_DIR, "commands"),
|
|
28
|
+
agents: join(CLAUDE_DIR, "agents")
|
|
29
|
+
},
|
|
30
|
+
codex: {
|
|
31
|
+
skills: join(CODEX_DIR, "skills"),
|
|
32
|
+
commands: join(CODEX_DIR, "commands"),
|
|
33
|
+
agents: join(CODEX_DIR, "agents")
|
|
34
|
+
},
|
|
35
|
+
cursor: {
|
|
36
|
+
skills: join(CURSOR_DIR, "skills"),
|
|
37
|
+
commands: join(CURSOR_DIR, "commands"),
|
|
38
|
+
agents: join(CURSOR_DIR, "agents")
|
|
39
|
+
},
|
|
40
|
+
gemini: {
|
|
41
|
+
skills: join(GEMINI_DIR, "skills"),
|
|
42
|
+
commands: join(GEMINI_DIR, "commands"),
|
|
43
|
+
agents: join(GEMINI_DIR, "agents")
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var TEMPLATES_DIR = join(PANOPTICON_HOME, "templates");
|
|
47
|
+
var CLAUDE_MD_TEMPLATES = join(TEMPLATES_DIR, "claude-md", "sections");
|
|
48
|
+
var currentFile = fileURLToPath(import.meta.url);
|
|
49
|
+
var currentDir = dirname(currentFile);
|
|
50
|
+
var packageRoot;
|
|
51
|
+
if (currentDir.includes("/src/")) {
|
|
52
|
+
packageRoot = dirname(dirname(currentDir));
|
|
53
|
+
} else {
|
|
54
|
+
packageRoot = currentDir.endsWith("/lib") ? dirname(dirname(currentDir)) : dirname(currentDir);
|
|
55
|
+
}
|
|
56
|
+
var SOURCE_TEMPLATES_DIR = join(packageRoot, "templates");
|
|
57
|
+
var SOURCE_TRAEFIK_TEMPLATES = join(SOURCE_TEMPLATES_DIR, "traefik");
|
|
58
|
+
var SOURCE_SCRIPTS_DIR = join(packageRoot, "scripts");
|
|
59
|
+
var INIT_DIRS = [
|
|
60
|
+
PANOPTICON_HOME,
|
|
61
|
+
SKILLS_DIR,
|
|
62
|
+
COMMANDS_DIR,
|
|
63
|
+
AGENTS_DIR,
|
|
64
|
+
BIN_DIR,
|
|
65
|
+
BACKUPS_DIR,
|
|
66
|
+
COSTS_DIR,
|
|
67
|
+
HEARTBEATS_DIR,
|
|
68
|
+
TEMPLATES_DIR,
|
|
69
|
+
CLAUDE_MD_TEMPLATES,
|
|
70
|
+
CERTS_DIR,
|
|
71
|
+
TRAEFIK_DIR,
|
|
72
|
+
TRAEFIK_DYNAMIC_DIR,
|
|
73
|
+
TRAEFIK_CERTS_DIR
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
PANOPTICON_HOME,
|
|
78
|
+
CONFIG_DIR,
|
|
79
|
+
SKILLS_DIR,
|
|
80
|
+
COMMANDS_DIR,
|
|
81
|
+
AGENTS_DIR,
|
|
82
|
+
BIN_DIR,
|
|
83
|
+
BACKUPS_DIR,
|
|
84
|
+
COSTS_DIR,
|
|
85
|
+
HEARTBEATS_DIR,
|
|
86
|
+
TRAEFIK_DIR,
|
|
87
|
+
TRAEFIK_DYNAMIC_DIR,
|
|
88
|
+
TRAEFIK_CERTS_DIR,
|
|
89
|
+
CERTS_DIR,
|
|
90
|
+
CONFIG_FILE,
|
|
91
|
+
CLAUDE_DIR,
|
|
92
|
+
CODEX_DIR,
|
|
93
|
+
CURSOR_DIR,
|
|
94
|
+
GEMINI_DIR,
|
|
95
|
+
SYNC_TARGETS,
|
|
96
|
+
TEMPLATES_DIR,
|
|
97
|
+
CLAUDE_MD_TEMPLATES,
|
|
98
|
+
SOURCE_TEMPLATES_DIR,
|
|
99
|
+
SOURCE_TRAEFIK_TEMPLATES,
|
|
100
|
+
SOURCE_SCRIPTS_DIR,
|
|
101
|
+
INIT_DIRS
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=chunk-3SI436SZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/paths.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 BIN_DIR = join(PANOPTICON_HOME, 'bin');\nexport const BACKUPS_DIR = join(PANOPTICON_HOME, 'backups');\nexport const COSTS_DIR = join(PANOPTICON_HOME, 'costs');\nexport const HEARTBEATS_DIR = join(PANOPTICON_HOME, 'heartbeats');\n\n// Traefik directories\nexport const TRAEFIK_DIR = join(PANOPTICON_HOME, 'traefik');\nexport const TRAEFIK_DYNAMIC_DIR = join(TRAEFIK_DIR, 'dynamic');\nexport const TRAEFIK_CERTS_DIR = join(TRAEFIK_DIR, 'certs');\n\n// Legacy certs directory (for backwards compatibility)\nexport const CERTS_DIR = join(PANOPTICON_HOME, 'certs');\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 agents: join(CLAUDE_DIR, 'agents'),\n },\n codex: {\n skills: join(CODEX_DIR, 'skills'),\n commands: join(CODEX_DIR, 'commands'),\n agents: join(CODEX_DIR, 'agents'),\n },\n cursor: {\n skills: join(CURSOR_DIR, 'skills'),\n commands: join(CURSOR_DIR, 'commands'),\n agents: join(CURSOR_DIR, 'agents'),\n },\n gemini: {\n skills: join(GEMINI_DIR, 'skills'),\n commands: join(GEMINI_DIR, 'commands'),\n agents: join(GEMINI_DIR, 'agents'),\n },\n} as const;\n\nexport type Runtime = keyof typeof SYNC_TARGETS;\n\n// Templates directory (in user's ~/.panopticon)\nexport const TEMPLATES_DIR = join(PANOPTICON_HOME, 'templates');\nexport const CLAUDE_MD_TEMPLATES = join(TEMPLATES_DIR, 'claude-md', 'sections');\n\n// Source templates directory (bundled with the package)\n// This is resolved at runtime from the package root\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\n\nconst currentFile = fileURLToPath(import.meta.url);\nconst currentDir = dirname(currentFile);\n\n// Handle both development (src/lib/) and production (dist/) modes\n// In dev: /path/to/panopticon/src/lib/paths.ts -> /path/to/panopticon\n// In prod: /path/to/panopticon/dist/lib/paths.js -> /path/to/panopticon\nlet packageRoot: string;\nif (currentDir.includes('/src/')) {\n // Development mode - go up from src/lib to package root\n packageRoot = dirname(dirname(currentDir));\n} else {\n // Production mode - go up from dist (or dist/lib) to package root\n packageRoot = currentDir.endsWith('/lib')\n ? dirname(dirname(currentDir))\n : dirname(currentDir);\n}\n\nexport const SOURCE_TEMPLATES_DIR = join(packageRoot, 'templates');\nexport const SOURCE_TRAEFIK_TEMPLATES = join(SOURCE_TEMPLATES_DIR, 'traefik');\nexport const SOURCE_SCRIPTS_DIR = join(packageRoot, 'scripts');\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 BIN_DIR,\n BACKUPS_DIR,\n COSTS_DIR,\n HEARTBEATS_DIR,\n TEMPLATES_DIR,\n CLAUDE_MD_TEMPLATES,\n CERTS_DIR,\n TRAEFIK_DIR,\n TRAEFIK_DYNAMIC_DIR,\n TRAEFIK_CERTS_DIR,\n];\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAS,YAAY;AAgErB,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AA9DjB,IAAM,kBAAkB,KAAK,QAAQ,GAAG,aAAa;AAGrD,IAAM,aAAa;AACnB,IAAM,aAAa,KAAK,iBAAiB,QAAQ;AACjD,IAAM,eAAe,KAAK,iBAAiB,UAAU;AACrD,IAAM,aAAa,KAAK,iBAAiB,QAAQ;AACjD,IAAM,UAAU,KAAK,iBAAiB,KAAK;AAC3C,IAAM,cAAc,KAAK,iBAAiB,SAAS;AACnD,IAAM,YAAY,KAAK,iBAAiB,OAAO;AAC/C,IAAM,iBAAiB,KAAK,iBAAiB,YAAY;AAGzD,IAAM,cAAc,KAAK,iBAAiB,SAAS;AACnD,IAAM,sBAAsB,KAAK,aAAa,SAAS;AACvD,IAAM,oBAAoB,KAAK,aAAa,OAAO;AAGnD,IAAM,YAAY,KAAK,iBAAiB,OAAO;AAG/C,IAAM,cAAc,KAAK,YAAY,aAAa;AAGlD,IAAM,aAAa,KAAK,QAAQ,GAAG,SAAS;AAC5C,IAAM,YAAY,KAAK,QAAQ,GAAG,QAAQ;AAC1C,IAAM,aAAa,KAAK,QAAQ,GAAG,SAAS;AAC5C,IAAM,aAAa,KAAK,QAAQ,GAAG,SAAS;AAG5C,IAAM,eAAe;AAAA,EAC1B,QAAQ;AAAA,IACN,QAAQ,KAAK,YAAY,QAAQ;AAAA,IACjC,UAAU,KAAK,YAAY,UAAU;AAAA,IACrC,QAAQ,KAAK,YAAY,QAAQ;AAAA,EACnC;AAAA,EACA,OAAO;AAAA,IACL,QAAQ,KAAK,WAAW,QAAQ;AAAA,IAChC,UAAU,KAAK,WAAW,UAAU;AAAA,IACpC,QAAQ,KAAK,WAAW,QAAQ;AAAA,EAClC;AAAA,EACA,QAAQ;AAAA,IACN,QAAQ,KAAK,YAAY,QAAQ;AAAA,IACjC,UAAU,KAAK,YAAY,UAAU;AAAA,IACrC,QAAQ,KAAK,YAAY,QAAQ;AAAA,EACnC;AAAA,EACA,QAAQ;AAAA,IACN,QAAQ,KAAK,YAAY,QAAQ;AAAA,IACjC,UAAU,KAAK,YAAY,UAAU;AAAA,IACrC,QAAQ,KAAK,YAAY,QAAQ;AAAA,EACnC;AACF;AAKO,IAAM,gBAAgB,KAAK,iBAAiB,WAAW;AACvD,IAAM,sBAAsB,KAAK,eAAe,aAAa,UAAU;AAO9E,IAAM,cAAc,cAAc,YAAY,GAAG;AACjD,IAAM,aAAa,QAAQ,WAAW;AAKtC,IAAI;AACJ,IAAI,WAAW,SAAS,OAAO,GAAG;AAEhC,gBAAc,QAAQ,QAAQ,UAAU,CAAC;AAC3C,OAAO;AAEL,gBAAc,WAAW,SAAS,MAAM,IACpC,QAAQ,QAAQ,UAAU,CAAC,IAC3B,QAAQ,UAAU;AACxB;AAEO,IAAM,uBAAuB,KAAK,aAAa,WAAW;AAC1D,IAAM,2BAA2B,KAAK,sBAAsB,SAAS;AACrE,IAAM,qBAAqB,KAAK,aAAa,SAAS;AAGtD,IAAM,YAAY;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":[]}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
PANOPTICON_HOME
|
|
3
|
-
|
|
4
|
-
init_paths
|
|
5
|
-
} from "./chunk-SG7O6I7R.js";
|
|
2
|
+
PANOPTICON_HOME
|
|
3
|
+
} from "./chunk-3SI436SZ.js";
|
|
6
4
|
|
|
7
5
|
// src/lib/projects.ts
|
|
8
|
-
init_esm_shims();
|
|
9
|
-
init_paths();
|
|
10
6
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
11
7
|
import { join } from "path";
|
|
12
8
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
@@ -174,4 +170,4 @@ export {
|
|
|
174
170
|
createDefaultProjectsConfig,
|
|
175
171
|
initializeProjectsConfig
|
|
176
172
|
};
|
|
177
|
-
//# sourceMappingURL=chunk-
|
|
173
|
+
//# sourceMappingURL=chunk-IVAFJ6DS.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/projects.ts"],"sourcesContent":["/**\n * Project Registry - Multi-project support for Panopticon\n *\n * Maps Linear team prefixes and labels to project paths for workspace creation.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { PANOPTICON_HOME } from './paths.js';\n\nexport const PROJECTS_CONFIG_FILE = join(PANOPTICON_HOME, 'projects.yaml');\n\n/**\n * Issue routing rule - routes issues with certain labels to specific paths\n */\nexport interface IssueRoutingRule {\n labels?: string[];\n default?: boolean;\n path: string;\n}\n\n/**\n * Project configuration\n */\nexport interface ProjectConfig {\n name: string;\n path: string;\n linear_team?: string;\n issue_routing?: IssueRoutingRule[];\n}\n\n/**\n * Full projects configuration file\n */\nexport interface ProjectsConfig {\n projects: Record<string, ProjectConfig>;\n}\n\n/**\n * Resolved project info for workspace creation\n */\nexport interface ResolvedProject {\n projectKey: string;\n projectName: string;\n projectPath: string;\n linearTeam?: string;\n}\n\n/**\n * Load projects configuration from ~/.panopticon/projects.yaml\n */\nexport function loadProjectsConfig(): ProjectsConfig {\n if (!existsSync(PROJECTS_CONFIG_FILE)) {\n return { projects: {} };\n }\n\n try {\n const content = readFileSync(PROJECTS_CONFIG_FILE, 'utf-8');\n const config = parseYaml(content) as ProjectsConfig;\n return config || { projects: {} };\n } catch (error: any) {\n console.error(`Failed to parse projects.yaml: ${error.message}`);\n return { projects: {} };\n }\n}\n\n/**\n * Save projects configuration\n */\nexport function saveProjectsConfig(config: ProjectsConfig): void {\n const dir = PANOPTICON_HOME;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const yaml = stringifyYaml(config, { indent: 2 });\n writeFileSync(PROJECTS_CONFIG_FILE, yaml, 'utf-8');\n}\n\n/**\n * Get a list of all registered projects\n */\nexport function listProjects(): Array<{ key: string; config: ProjectConfig }> {\n const config = loadProjectsConfig();\n return Object.entries(config.projects).map(([key, projectConfig]) => ({\n key,\n config: projectConfig,\n }));\n}\n\n/**\n * Add or update a project in the registry\n */\nexport function registerProject(key: string, projectConfig: ProjectConfig): void {\n const config = loadProjectsConfig();\n config.projects[key] = projectConfig;\n saveProjectsConfig(config);\n}\n\n/**\n * Remove a project from the registry\n */\nexport function unregisterProject(key: string): boolean {\n const config = loadProjectsConfig();\n if (config.projects[key]) {\n delete config.projects[key];\n saveProjectsConfig(config);\n return true;\n }\n return false;\n}\n\n/**\n * Extract Linear team prefix from an issue ID\n * E.g., \"MIN-123\" -> \"MIN\", \"PAN-456\" -> \"PAN\"\n */\nexport function extractTeamPrefix(issueId: string): string | null {\n const match = issueId.match(/^([A-Z]+)-\\d+$/i);\n return match ? match[1].toUpperCase() : null;\n}\n\n/**\n * Find project by Linear team prefix\n */\nexport function findProjectByTeam(teamPrefix: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n\n for (const [, projectConfig] of Object.entries(config.projects)) {\n if (projectConfig.linear_team?.toUpperCase() === teamPrefix.toUpperCase()) {\n return projectConfig;\n }\n }\n\n return null;\n}\n\n/**\n * Resolve the correct project path for an issue based on labels\n *\n * @param project - The project config\n * @param labels - Array of label names from the Linear issue\n * @returns The resolved path (may differ from project.path based on routing rules)\n */\nexport function resolveProjectPath(project: ProjectConfig, labels: string[] = []): string {\n if (!project.issue_routing || project.issue_routing.length === 0) {\n return project.path;\n }\n\n // Normalize labels to lowercase for comparison\n const normalizedLabels = labels.map(l => l.toLowerCase());\n\n // First, check label-based routing rules\n for (const rule of project.issue_routing) {\n if (rule.labels && rule.labels.length > 0) {\n const ruleLabels = rule.labels.map(l => l.toLowerCase());\n const hasMatch = ruleLabels.some(label => normalizedLabels.includes(label));\n if (hasMatch) {\n return rule.path;\n }\n }\n }\n\n // Then, find default rule\n for (const rule of project.issue_routing) {\n if (rule.default) {\n return rule.path;\n }\n }\n\n // Fall back to project path\n return project.path;\n}\n\n/**\n * Resolve project from an issue ID (and optional labels)\n *\n * @param issueId - Linear issue ID (e.g., \"MIN-123\")\n * @param labels - Optional array of label names\n * @returns Resolved project info or null if not found\n */\nexport function resolveProjectFromIssue(\n issueId: string,\n labels: string[] = []\n): ResolvedProject | null {\n const teamPrefix = extractTeamPrefix(issueId);\n if (!teamPrefix) {\n return null;\n }\n\n const config = loadProjectsConfig();\n\n // Find project by team prefix\n for (const [key, projectConfig] of Object.entries(config.projects)) {\n if (projectConfig.linear_team?.toUpperCase() === teamPrefix) {\n const resolvedPath = resolveProjectPath(projectConfig, labels);\n return {\n projectKey: key,\n projectName: projectConfig.name,\n projectPath: resolvedPath,\n linearTeam: projectConfig.linear_team,\n };\n }\n }\n\n return null;\n}\n\n/**\n * Get a project by key\n */\nexport function getProject(key: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n return config.projects[key] || null;\n}\n\n/**\n * Check if projects.yaml exists and has any projects\n */\nexport function hasProjects(): boolean {\n const config = loadProjectsConfig();\n return Object.keys(config.projects).length > 0;\n}\n\n/**\n * Create a default projects.yaml with example structure\n */\nexport function createDefaultProjectsConfig(): ProjectsConfig {\n const defaultConfig: ProjectsConfig = {\n projects: {\n // Example project - commented out in actual file\n },\n };\n\n return defaultConfig;\n}\n\n/**\n * Initialize projects.yaml with example configuration\n */\nexport function initializeProjectsConfig(): void {\n if (existsSync(PROJECTS_CONFIG_FILE)) {\n console.log(`Projects config already exists at ${PROJECTS_CONFIG_FILE}`);\n return;\n }\n\n const exampleYaml = `# Panopticon Project Registry\n# Maps Linear teams to project paths for workspace creation\n\nprojects:\n # Example: Mind Your Now project\n # myn:\n # name: \"Mind Your Now\"\n # path: /home/user/projects/myn\n # linear_team: MIN\n # issue_routing:\n # # Route docs/marketing issues to docs repo\n # - labels: [docs, marketing, seo, landing-pages]\n # path: /home/user/projects/myn/docs\n # # Default: main repo\n # - default: true\n # path: /home/user/projects/myn\n\n # Example: Panopticon itself\n # panopticon:\n # name: \"Panopticon\"\n # path: /home/user/projects/panopticon\n # linear_team: PAN\n`;\n\n const dir = PANOPTICON_HOME;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(PROJECTS_CONFIG_FILE, exampleYaml, 'utf-8');\n console.log(`Created example projects config at ${PROJECTS_CONFIG_FILE}`);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/lib/projects.ts"],"sourcesContent":["/**\n * Project Registry - Multi-project support for Panopticon\n *\n * Maps Linear team prefixes and labels to project paths for workspace creation.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { PANOPTICON_HOME } from './paths.js';\n\nexport const PROJECTS_CONFIG_FILE = join(PANOPTICON_HOME, 'projects.yaml');\n\n/**\n * Issue routing rule - routes issues with certain labels to specific paths\n */\nexport interface IssueRoutingRule {\n labels?: string[];\n default?: boolean;\n path: string;\n}\n\n/**\n * Project configuration\n */\nexport interface ProjectConfig {\n name: string;\n path: string;\n linear_team?: string;\n issue_routing?: IssueRoutingRule[];\n}\n\n/**\n * Full projects configuration file\n */\nexport interface ProjectsConfig {\n projects: Record<string, ProjectConfig>;\n}\n\n/**\n * Resolved project info for workspace creation\n */\nexport interface ResolvedProject {\n projectKey: string;\n projectName: string;\n projectPath: string;\n linearTeam?: string;\n}\n\n/**\n * Load projects configuration from ~/.panopticon/projects.yaml\n */\nexport function loadProjectsConfig(): ProjectsConfig {\n if (!existsSync(PROJECTS_CONFIG_FILE)) {\n return { projects: {} };\n }\n\n try {\n const content = readFileSync(PROJECTS_CONFIG_FILE, 'utf-8');\n const config = parseYaml(content) as ProjectsConfig;\n return config || { projects: {} };\n } catch (error: any) {\n console.error(`Failed to parse projects.yaml: ${error.message}`);\n return { projects: {} };\n }\n}\n\n/**\n * Save projects configuration\n */\nexport function saveProjectsConfig(config: ProjectsConfig): void {\n const dir = PANOPTICON_HOME;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const yaml = stringifyYaml(config, { indent: 2 });\n writeFileSync(PROJECTS_CONFIG_FILE, yaml, 'utf-8');\n}\n\n/**\n * Get a list of all registered projects\n */\nexport function listProjects(): Array<{ key: string; config: ProjectConfig }> {\n const config = loadProjectsConfig();\n return Object.entries(config.projects).map(([key, projectConfig]) => ({\n key,\n config: projectConfig,\n }));\n}\n\n/**\n * Add or update a project in the registry\n */\nexport function registerProject(key: string, projectConfig: ProjectConfig): void {\n const config = loadProjectsConfig();\n config.projects[key] = projectConfig;\n saveProjectsConfig(config);\n}\n\n/**\n * Remove a project from the registry\n */\nexport function unregisterProject(key: string): boolean {\n const config = loadProjectsConfig();\n if (config.projects[key]) {\n delete config.projects[key];\n saveProjectsConfig(config);\n return true;\n }\n return false;\n}\n\n/**\n * Extract Linear team prefix from an issue ID\n * E.g., \"MIN-123\" -> \"MIN\", \"PAN-456\" -> \"PAN\"\n */\nexport function extractTeamPrefix(issueId: string): string | null {\n const match = issueId.match(/^([A-Z]+)-\\d+$/i);\n return match ? match[1].toUpperCase() : null;\n}\n\n/**\n * Find project by Linear team prefix\n */\nexport function findProjectByTeam(teamPrefix: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n\n for (const [, projectConfig] of Object.entries(config.projects)) {\n if (projectConfig.linear_team?.toUpperCase() === teamPrefix.toUpperCase()) {\n return projectConfig;\n }\n }\n\n return null;\n}\n\n/**\n * Resolve the correct project path for an issue based on labels\n *\n * @param project - The project config\n * @param labels - Array of label names from the Linear issue\n * @returns The resolved path (may differ from project.path based on routing rules)\n */\nexport function resolveProjectPath(project: ProjectConfig, labels: string[] = []): string {\n if (!project.issue_routing || project.issue_routing.length === 0) {\n return project.path;\n }\n\n // Normalize labels to lowercase for comparison\n const normalizedLabels = labels.map(l => l.toLowerCase());\n\n // First, check label-based routing rules\n for (const rule of project.issue_routing) {\n if (rule.labels && rule.labels.length > 0) {\n const ruleLabels = rule.labels.map(l => l.toLowerCase());\n const hasMatch = ruleLabels.some(label => normalizedLabels.includes(label));\n if (hasMatch) {\n return rule.path;\n }\n }\n }\n\n // Then, find default rule\n for (const rule of project.issue_routing) {\n if (rule.default) {\n return rule.path;\n }\n }\n\n // Fall back to project path\n return project.path;\n}\n\n/**\n * Resolve project from an issue ID (and optional labels)\n *\n * @param issueId - Linear issue ID (e.g., \"MIN-123\")\n * @param labels - Optional array of label names\n * @returns Resolved project info or null if not found\n */\nexport function resolveProjectFromIssue(\n issueId: string,\n labels: string[] = []\n): ResolvedProject | null {\n const teamPrefix = extractTeamPrefix(issueId);\n if (!teamPrefix) {\n return null;\n }\n\n const config = loadProjectsConfig();\n\n // Find project by team prefix\n for (const [key, projectConfig] of Object.entries(config.projects)) {\n if (projectConfig.linear_team?.toUpperCase() === teamPrefix) {\n const resolvedPath = resolveProjectPath(projectConfig, labels);\n return {\n projectKey: key,\n projectName: projectConfig.name,\n projectPath: resolvedPath,\n linearTeam: projectConfig.linear_team,\n };\n }\n }\n\n return null;\n}\n\n/**\n * Get a project by key\n */\nexport function getProject(key: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n return config.projects[key] || null;\n}\n\n/**\n * Check if projects.yaml exists and has any projects\n */\nexport function hasProjects(): boolean {\n const config = loadProjectsConfig();\n return Object.keys(config.projects).length > 0;\n}\n\n/**\n * Create a default projects.yaml with example structure\n */\nexport function createDefaultProjectsConfig(): ProjectsConfig {\n const defaultConfig: ProjectsConfig = {\n projects: {\n // Example project - commented out in actual file\n },\n };\n\n return defaultConfig;\n}\n\n/**\n * Initialize projects.yaml with example configuration\n */\nexport function initializeProjectsConfig(): void {\n if (existsSync(PROJECTS_CONFIG_FILE)) {\n console.log(`Projects config already exists at ${PROJECTS_CONFIG_FILE}`);\n return;\n }\n\n const exampleYaml = `# Panopticon Project Registry\n# Maps Linear teams to project paths for workspace creation\n\nprojects:\n # Example: Mind Your Now project\n # myn:\n # name: \"Mind Your Now\"\n # path: /home/user/projects/myn\n # linear_team: MIN\n # issue_routing:\n # # Route docs/marketing issues to docs repo\n # - labels: [docs, marketing, seo, landing-pages]\n # path: /home/user/projects/myn/docs\n # # Default: main repo\n # - default: true\n # path: /home/user/projects/myn\n\n # Example: Panopticon itself\n # panopticon:\n # name: \"Panopticon\"\n # path: /home/user/projects/panopticon\n # linear_team: PAN\n`;\n\n const dir = PANOPTICON_HOME;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(PROJECTS_CONFIG_FILE, exampleYaml, 'utf-8');\n console.log(`Created example projects config at ${PROJECTS_CONFIG_FILE}`);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -6,14 +6,10 @@ import {
|
|
|
6
6
|
CONFIG_FILE,
|
|
7
7
|
SKILLS_DIR,
|
|
8
8
|
SOURCE_SCRIPTS_DIR,
|
|
9
|
-
SYNC_TARGETS
|
|
10
|
-
|
|
11
|
-
init_paths
|
|
12
|
-
} from "./chunk-SG7O6I7R.js";
|
|
9
|
+
SYNC_TARGETS
|
|
10
|
+
} from "./chunk-3SI436SZ.js";
|
|
13
11
|
|
|
14
12
|
// src/lib/config.ts
|
|
15
|
-
init_esm_shims();
|
|
16
|
-
init_paths();
|
|
17
13
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
18
14
|
import { parse, stringify } from "@iarna/toml";
|
|
19
15
|
var DEFAULT_CONFIG = {
|
|
@@ -74,7 +70,6 @@ function getDefaultConfig() {
|
|
|
74
70
|
}
|
|
75
71
|
|
|
76
72
|
// src/lib/shell.ts
|
|
77
|
-
init_esm_shims();
|
|
78
73
|
import { existsSync as existsSync2, readFileSync as readFileSync2, appendFileSync } from "fs";
|
|
79
74
|
import { homedir } from "os";
|
|
80
75
|
import { join } from "path";
|
|
@@ -126,8 +121,6 @@ function getAliasInstructions(shell) {
|
|
|
126
121
|
}
|
|
127
122
|
|
|
128
123
|
// src/lib/backup.ts
|
|
129
|
-
init_esm_shims();
|
|
130
|
-
init_paths();
|
|
131
124
|
import { existsSync as existsSync3, mkdirSync, readdirSync, cpSync, rmSync } from "fs";
|
|
132
125
|
import { join as join2, basename } from "path";
|
|
133
126
|
function createBackupTimestamp() {
|
|
@@ -194,8 +187,6 @@ function cleanOldBackups(keepCount = 10) {
|
|
|
194
187
|
}
|
|
195
188
|
|
|
196
189
|
// src/lib/sync.ts
|
|
197
|
-
init_esm_shims();
|
|
198
|
-
init_paths();
|
|
199
190
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, symlinkSync, unlinkSync, lstatSync, readlinkSync, rmSync as rmSync2, copyFileSync, chmodSync } from "fs";
|
|
200
191
|
import { join as join3 } from "path";
|
|
201
192
|
function removeTarget(targetPath) {
|
|
@@ -379,7 +370,6 @@ function syncHooks() {
|
|
|
379
370
|
}
|
|
380
371
|
|
|
381
372
|
// src/lib/tracker/interface.ts
|
|
382
|
-
init_esm_shims();
|
|
383
373
|
var NotImplementedError = class extends Error {
|
|
384
374
|
constructor(feature) {
|
|
385
375
|
super(`Not implemented: ${feature}`);
|
|
@@ -400,7 +390,6 @@ var TrackerAuthError = class extends Error {
|
|
|
400
390
|
};
|
|
401
391
|
|
|
402
392
|
// src/lib/tracker/linear.ts
|
|
403
|
-
init_esm_shims();
|
|
404
393
|
import { LinearClient } from "@linear/sdk";
|
|
405
394
|
var STATE_MAP = {
|
|
406
395
|
backlog: "open",
|
|
@@ -610,7 +599,6 @@ var LinearTracker = class {
|
|
|
610
599
|
};
|
|
611
600
|
|
|
612
601
|
// src/lib/tracker/github.ts
|
|
613
|
-
init_esm_shims();
|
|
614
602
|
import { Octokit } from "@octokit/rest";
|
|
615
603
|
var GitHubTracker = class {
|
|
616
604
|
name = "github";
|
|
@@ -771,7 +759,6 @@ var GitHubTracker = class {
|
|
|
771
759
|
};
|
|
772
760
|
|
|
773
761
|
// src/lib/tracker/gitlab.ts
|
|
774
|
-
init_esm_shims();
|
|
775
762
|
var GitLabTracker = class {
|
|
776
763
|
constructor(token, projectId) {
|
|
777
764
|
this.token = token;
|
|
@@ -820,11 +807,7 @@ var GitLabTracker = class {
|
|
|
820
807
|
}
|
|
821
808
|
};
|
|
822
809
|
|
|
823
|
-
// src/lib/tracker/factory.ts
|
|
824
|
-
init_esm_shims();
|
|
825
|
-
|
|
826
810
|
// src/lib/tracker/rally.ts
|
|
827
|
-
init_esm_shims();
|
|
828
811
|
import rally from "rally";
|
|
829
812
|
var STATE_MAP2 = {
|
|
830
813
|
Defined: "open",
|
|
@@ -1287,7 +1270,6 @@ function getAllTrackers(trackersConfig) {
|
|
|
1287
1270
|
}
|
|
1288
1271
|
|
|
1289
1272
|
// src/lib/tracker/linking.ts
|
|
1290
|
-
init_esm_shims();
|
|
1291
1273
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
1292
1274
|
import { join as join4 } from "path";
|
|
1293
1275
|
import { homedir as homedir2 } from "os";
|
|
@@ -1428,9 +1410,6 @@ function getLinkManager() {
|
|
|
1428
1410
|
return _linkManager;
|
|
1429
1411
|
}
|
|
1430
1412
|
|
|
1431
|
-
// src/lib/tracker/index.ts
|
|
1432
|
-
init_esm_shims();
|
|
1433
|
-
|
|
1434
1413
|
export {
|
|
1435
1414
|
loadConfig,
|
|
1436
1415
|
saveConfig,
|
|
@@ -1466,4 +1445,4 @@ export {
|
|
|
1466
1445
|
LinkManager,
|
|
1467
1446
|
getLinkManager
|
|
1468
1447
|
};
|
|
1469
|
-
//# sourceMappingURL=chunk-
|
|
1448
|
+
//# sourceMappingURL=chunk-ZT55DPAC.js.map
|