panopticon-cli 0.1.3 → 0.2.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/README.md +645 -22
- package/dist/chunk-J7JUNJGH.js +1058 -0
- package/dist/chunk-J7JUNJGH.js.map +1 -0
- package/dist/cli/index.js +1733 -717
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +368 -5
- package/dist/index.js +43 -1
- package/package.json +11 -2
- package/templates/context/CLAUDE.md.template +81 -0
- package/templates/context/STATE.md.template +106 -0
- package/templates/context/WORKSPACE.md.template +90 -0
- package/templates/docker/dotnet/Dockerfile.dev +22 -0
- package/templates/docker/dotnet/README.md +111 -0
- package/templates/docker/dotnet/docker-compose.yml +55 -0
- package/templates/docker/monorepo/Dockerfile.backend +15 -0
- package/templates/docker/monorepo/Dockerfile.frontend +17 -0
- package/templates/docker/monorepo/README.md +208 -0
- package/templates/docker/monorepo/docker-compose.yml +88 -0
- package/templates/docker/nextjs/Dockerfile.dev +20 -0
- package/templates/docker/nextjs/README.md +103 -0
- package/templates/docker/nextjs/docker-compose.yml +30 -0
- package/templates/docker/python-fastapi/Dockerfile.dev +22 -0
- package/templates/docker/python-fastapi/README.md +148 -0
- package/templates/docker/python-fastapi/docker-compose.yml +65 -0
- package/templates/docker/react-vite/Dockerfile.dev +20 -0
- package/templates/docker/react-vite/README.md +94 -0
- package/templates/docker/react-vite/docker-compose.yml +29 -0
- package/templates/docker/spring-boot/Dockerfile.dev +24 -0
- package/templates/docker/spring-boot/README.md +111 -0
- package/templates/docker/spring-boot/docker-compose.yml +71 -0
- package/templates/traefik/README.md +106 -0
- package/templates/traefik/docker-compose.yml +40 -0
- package/templates/traefik/dynamic/panopticon.yml +51 -0
- package/templates/traefik/dynamic/workspace.yml.template +34 -0
- package/templates/traefik/traefik.yml +45 -0
- package/dist/chunk-FR2P66GU.js +0 -352
- package/dist/chunk-FR2P66GU.js.map +0 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
app:
|
|
5
|
+
build:
|
|
6
|
+
context: .
|
|
7
|
+
dockerfile: Dockerfile.dev
|
|
8
|
+
ports:
|
|
9
|
+
- "${APP_PORT:-8080}:8080"
|
|
10
|
+
- "${DEBUG_PORT:-5005}:5005"
|
|
11
|
+
volumes:
|
|
12
|
+
- .:/app
|
|
13
|
+
- maven-cache:/root/.m2
|
|
14
|
+
environment:
|
|
15
|
+
- SPRING_PROFILES_ACTIVE=dev
|
|
16
|
+
- SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/${DB_NAME:-appdb}
|
|
17
|
+
- SPRING_DATASOURCE_USERNAME=${DB_USER:-postgres}
|
|
18
|
+
- SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD:-postgres}
|
|
19
|
+
- SPRING_REDIS_HOST=redis
|
|
20
|
+
- SPRING_REDIS_PORT=6379
|
|
21
|
+
depends_on:
|
|
22
|
+
postgres:
|
|
23
|
+
condition: service_healthy
|
|
24
|
+
redis:
|
|
25
|
+
condition: service_started
|
|
26
|
+
networks:
|
|
27
|
+
- app-network
|
|
28
|
+
labels:
|
|
29
|
+
- "traefik.enable=true"
|
|
30
|
+
- "traefik.http.routers.${COMPOSE_PROJECT_NAME:-app}.rule=Host(`${HOSTNAME:-app.pan.localhost}`)"
|
|
31
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME:-app}.loadbalancer.server.port=8080"
|
|
32
|
+
|
|
33
|
+
postgres:
|
|
34
|
+
image: postgres:16-alpine
|
|
35
|
+
environment:
|
|
36
|
+
- POSTGRES_DB=${DB_NAME:-appdb}
|
|
37
|
+
- POSTGRES_USER=${DB_USER:-postgres}
|
|
38
|
+
- POSTGRES_PASSWORD=${DB_PASSWORD:-postgres}
|
|
39
|
+
volumes:
|
|
40
|
+
- postgres-data:/var/lib/postgresql/data
|
|
41
|
+
ports:
|
|
42
|
+
- "${DB_PORT:-5432}:5432"
|
|
43
|
+
healthcheck:
|
|
44
|
+
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres}"]
|
|
45
|
+
interval: 5s
|
|
46
|
+
timeout: 5s
|
|
47
|
+
retries: 5
|
|
48
|
+
networks:
|
|
49
|
+
- app-network
|
|
50
|
+
|
|
51
|
+
redis:
|
|
52
|
+
image: redis:7-alpine
|
|
53
|
+
ports:
|
|
54
|
+
- "${REDIS_PORT:-6379}:6379"
|
|
55
|
+
volumes:
|
|
56
|
+
- redis-data:/data
|
|
57
|
+
networks:
|
|
58
|
+
- app-network
|
|
59
|
+
|
|
60
|
+
volumes:
|
|
61
|
+
postgres-data:
|
|
62
|
+
redis-data:
|
|
63
|
+
maven-cache:
|
|
64
|
+
|
|
65
|
+
networks:
|
|
66
|
+
app-network:
|
|
67
|
+
driver: bridge
|
|
68
|
+
# Connect to Traefik network if available
|
|
69
|
+
traefik:
|
|
70
|
+
external: true
|
|
71
|
+
name: traefik_default
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Panopticon Traefik Configuration
|
|
2
|
+
|
|
3
|
+
Traefik reverse proxy for local development with HTTPS.
|
|
4
|
+
|
|
5
|
+
## Directory Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
~/.panopticon/traefik/
|
|
9
|
+
├── docker-compose.yml # Traefik container definition
|
|
10
|
+
├── traefik.yml # Static configuration
|
|
11
|
+
├── dynamic/ # Dynamic routing configs
|
|
12
|
+
│ └── panopticon.yml # Dashboard routing
|
|
13
|
+
├── certs/ # mkcert SSL certificates
|
|
14
|
+
│ ├── _wildcard.pan.localhost.pem
|
|
15
|
+
│ └── _wildcard.pan.localhost-key.pem
|
|
16
|
+
└── README.md # This file
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## URLs
|
|
20
|
+
|
|
21
|
+
| URL | Service |
|
|
22
|
+
|-----|---------|
|
|
23
|
+
| `https://pan.localhost` | Panopticon Dashboard (Frontend) |
|
|
24
|
+
| `https://pan.localhost/api/*` | Panopticon Dashboard (API) |
|
|
25
|
+
| `http://localhost:8080` | Traefik Dashboard |
|
|
26
|
+
|
|
27
|
+
## How It Works
|
|
28
|
+
|
|
29
|
+
### Static Configuration (`traefik.yml`)
|
|
30
|
+
- Defines entry points (HTTP:80, HTTPS:443)
|
|
31
|
+
- Enables Traefik dashboard on port 8080
|
|
32
|
+
- Configures file provider for dynamic configs
|
|
33
|
+
- Sets up TLS with wildcard certificates
|
|
34
|
+
|
|
35
|
+
### Dynamic Configuration (`dynamic/panopticon.yml`)
|
|
36
|
+
- Routes `https://pan.localhost` to dashboard frontend (port 3001)
|
|
37
|
+
- Routes `https://pan.localhost/api/*` to dashboard API (port 3002)
|
|
38
|
+
- Uses `host.docker.internal` to access host-based services
|
|
39
|
+
|
|
40
|
+
### Docker Compose
|
|
41
|
+
- Runs Traefik v3.0 in a container
|
|
42
|
+
- Exposes ports 80, 443, 8080
|
|
43
|
+
- Mounts configuration files and certificates
|
|
44
|
+
- Uses `panopticon` network for container communication
|
|
45
|
+
|
|
46
|
+
## Prerequisites
|
|
47
|
+
|
|
48
|
+
1. **mkcert certificates** must be generated first:
|
|
49
|
+
```bash
|
|
50
|
+
mkcert "*.pan.localhost" "*.localhost" localhost 127.0.0.1 ::1
|
|
51
|
+
```
|
|
52
|
+
Certificates should be in `~/.panopticon/traefik/certs/`
|
|
53
|
+
|
|
54
|
+
2. **DNS/Hosts configuration**:
|
|
55
|
+
- Add to `/etc/hosts`: `127.0.0.1 pan.localhost`
|
|
56
|
+
- Wildcard `*.localhost` resolves automatically on most systems
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
Start Traefik:
|
|
61
|
+
```bash
|
|
62
|
+
cd ~/.panopticon/traefik
|
|
63
|
+
docker-compose up -d
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Stop Traefik:
|
|
67
|
+
```bash
|
|
68
|
+
cd ~/.panopticon/traefik
|
|
69
|
+
docker-compose down
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
View logs:
|
|
73
|
+
```bash
|
|
74
|
+
docker logs -f panopticon-traefik
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Managed by CLI
|
|
78
|
+
|
|
79
|
+
These commands are automated via `pan` CLI:
|
|
80
|
+
- `pan install` - Sets up Traefik (creates configs, generates certs)
|
|
81
|
+
- `pan up` - Starts Traefik container
|
|
82
|
+
- `pan down` - Stops Traefik container
|
|
83
|
+
|
|
84
|
+
## Adding Workspace Routes
|
|
85
|
+
|
|
86
|
+
Workspace-specific routes should be added as separate YAML files in `dynamic/`:
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
# dynamic/feature-pan-4.yml
|
|
90
|
+
http:
|
|
91
|
+
routers:
|
|
92
|
+
workspace-feature-pan-4-frontend:
|
|
93
|
+
rule: "Host(`feature-pan-4.myn.localhost`)"
|
|
94
|
+
service: workspace-feature-pan-4-frontend
|
|
95
|
+
entryPoints:
|
|
96
|
+
- websecure
|
|
97
|
+
tls: {}
|
|
98
|
+
|
|
99
|
+
services:
|
|
100
|
+
workspace-feature-pan-4-frontend:
|
|
101
|
+
loadBalancer:
|
|
102
|
+
servers:
|
|
103
|
+
- url: "http://workspace-feature-pan-4-frontend:3000"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
These are auto-generated by `pan workspace create` and `pan workspace start`.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
services:
|
|
2
|
+
traefik:
|
|
3
|
+
image: traefik:v3.0
|
|
4
|
+
container_name: panopticon-traefik
|
|
5
|
+
restart: unless-stopped
|
|
6
|
+
ports:
|
|
7
|
+
- "8081:80" # HTTP (redirects to HTTPS)
|
|
8
|
+
- "8443:443" # HTTPS
|
|
9
|
+
- "8082:8080" # Traefik Dashboard
|
|
10
|
+
volumes:
|
|
11
|
+
# Traefik configuration
|
|
12
|
+
- ./traefik.yml:/etc/traefik/traefik.yml:ro
|
|
13
|
+
- ./dynamic:/etc/traefik/dynamic:ro
|
|
14
|
+
|
|
15
|
+
# TLS certificates (mkcert generated)
|
|
16
|
+
- ./certs:/etc/traefik/certs:ro
|
|
17
|
+
|
|
18
|
+
# Docker socket for service discovery
|
|
19
|
+
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
20
|
+
|
|
21
|
+
networks:
|
|
22
|
+
- panopticon
|
|
23
|
+
|
|
24
|
+
labels:
|
|
25
|
+
- "traefik.enable=true"
|
|
26
|
+
|
|
27
|
+
# Dashboard routing (traefik.pan.localhost:8080)
|
|
28
|
+
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.pan.localhost`)"
|
|
29
|
+
- "traefik.http.routers.traefik-dashboard.entrypoints=websecure"
|
|
30
|
+
- "traefik.http.routers.traefik-dashboard.tls=true"
|
|
31
|
+
- "traefik.http.routers.traefik-dashboard.service=api@internal"
|
|
32
|
+
|
|
33
|
+
extra_hosts:
|
|
34
|
+
# Allow Traefik to reach host services (dashboard on ports 3001/3002)
|
|
35
|
+
- "host.docker.internal:host-gateway"
|
|
36
|
+
|
|
37
|
+
networks:
|
|
38
|
+
panopticon:
|
|
39
|
+
name: panopticon
|
|
40
|
+
driver: bridge
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Dynamic routing configuration for Panopticon Dashboard
|
|
2
|
+
# Routes traffic from pan.localhost to host-based services
|
|
3
|
+
|
|
4
|
+
http:
|
|
5
|
+
routers:
|
|
6
|
+
# Dashboard Frontend (pan.localhost)
|
|
7
|
+
panopticon-frontend:
|
|
8
|
+
rule: "Host(`pan.localhost`) && !PathPrefix(`/api`)"
|
|
9
|
+
entryPoints:
|
|
10
|
+
- websecure
|
|
11
|
+
service: panopticon-frontend
|
|
12
|
+
tls:
|
|
13
|
+
domains:
|
|
14
|
+
- main: "pan.localhost"
|
|
15
|
+
sans:
|
|
16
|
+
- "*.pan.localhost"
|
|
17
|
+
|
|
18
|
+
# Dashboard API (pan.localhost/api/*)
|
|
19
|
+
panopticon-api:
|
|
20
|
+
rule: "Host(`pan.localhost`) && PathPrefix(`/api`)"
|
|
21
|
+
entryPoints:
|
|
22
|
+
- websecure
|
|
23
|
+
service: panopticon-api
|
|
24
|
+
middlewares:
|
|
25
|
+
- api-stripprefix
|
|
26
|
+
tls: {}
|
|
27
|
+
|
|
28
|
+
services:
|
|
29
|
+
# Frontend service (running on host:3001)
|
|
30
|
+
panopticon-frontend:
|
|
31
|
+
loadBalancer:
|
|
32
|
+
servers:
|
|
33
|
+
- url: "http://host.docker.internal:3001"
|
|
34
|
+
|
|
35
|
+
# API service (running on host:3002)
|
|
36
|
+
panopticon-api:
|
|
37
|
+
loadBalancer:
|
|
38
|
+
servers:
|
|
39
|
+
- url: "http://host.docker.internal:3002"
|
|
40
|
+
|
|
41
|
+
middlewares:
|
|
42
|
+
# Strip /api prefix before forwarding to API service
|
|
43
|
+
api-stripprefix:
|
|
44
|
+
stripPrefix:
|
|
45
|
+
prefixes:
|
|
46
|
+
- "/api"
|
|
47
|
+
|
|
48
|
+
tls:
|
|
49
|
+
certificates:
|
|
50
|
+
- certFile: /etc/traefik/certs/_wildcard.pan.localhost.pem
|
|
51
|
+
keyFile: /etc/traefik/certs/_wildcard.pan.localhost-key.pem
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Workspace Routing Template
|
|
2
|
+
# This template is used to generate dynamic configs for workspaces
|
|
3
|
+
# Variables: {{WORKSPACE_NAME}}, {{PROJECT_NAME}}, {{FRONTEND_PORT}}, {{API_PORT}}
|
|
4
|
+
|
|
5
|
+
http:
|
|
6
|
+
routers:
|
|
7
|
+
# Workspace frontend
|
|
8
|
+
{{WORKSPACE_NAME}}-frontend:
|
|
9
|
+
rule: "Host(`{{WORKSPACE_NAME}}.{{PROJECT_NAME}}.localhost`)"
|
|
10
|
+
entryPoints:
|
|
11
|
+
- websecure
|
|
12
|
+
service: {{WORKSPACE_NAME}}-frontend
|
|
13
|
+
tls: {}
|
|
14
|
+
|
|
15
|
+
# Workspace API (if API port is specified)
|
|
16
|
+
{{WORKSPACE_NAME}}-api:
|
|
17
|
+
rule: "Host(`api-{{WORKSPACE_NAME}}.{{PROJECT_NAME}}.localhost`)"
|
|
18
|
+
entryPoints:
|
|
19
|
+
- websecure
|
|
20
|
+
service: {{WORKSPACE_NAME}}-api
|
|
21
|
+
tls: {}
|
|
22
|
+
|
|
23
|
+
services:
|
|
24
|
+
{{WORKSPACE_NAME}}-frontend:
|
|
25
|
+
loadBalancer:
|
|
26
|
+
servers:
|
|
27
|
+
- url: "http://host.docker.internal:{{FRONTEND_PORT}}"
|
|
28
|
+
passHostHeader: true
|
|
29
|
+
|
|
30
|
+
{{WORKSPACE_NAME}}-api:
|
|
31
|
+
loadBalancer:
|
|
32
|
+
servers:
|
|
33
|
+
- url: "http://host.docker.internal:{{API_PORT}}"
|
|
34
|
+
passHostHeader: true
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Traefik Static Configuration
|
|
2
|
+
# This file configures Traefik's core behavior
|
|
3
|
+
|
|
4
|
+
# API and Dashboard
|
|
5
|
+
api:
|
|
6
|
+
dashboard: true
|
|
7
|
+
insecure: true # Dashboard accessible without auth on localhost
|
|
8
|
+
|
|
9
|
+
# Entry Points
|
|
10
|
+
entryPoints:
|
|
11
|
+
web:
|
|
12
|
+
address: ":80"
|
|
13
|
+
# Redirect HTTP to HTTPS
|
|
14
|
+
http:
|
|
15
|
+
redirections:
|
|
16
|
+
entryPoint:
|
|
17
|
+
to: websecure
|
|
18
|
+
scheme: https
|
|
19
|
+
|
|
20
|
+
websecure:
|
|
21
|
+
address: ":443"
|
|
22
|
+
http:
|
|
23
|
+
tls: {}
|
|
24
|
+
|
|
25
|
+
# Providers
|
|
26
|
+
providers:
|
|
27
|
+
# File provider for dynamic configuration
|
|
28
|
+
file:
|
|
29
|
+
directory: /etc/traefik/dynamic
|
|
30
|
+
watch: true
|
|
31
|
+
|
|
32
|
+
# Docker provider (for future workspace containers)
|
|
33
|
+
docker:
|
|
34
|
+
endpoint: "unix:///var/run/docker.sock"
|
|
35
|
+
exposedByDefault: false
|
|
36
|
+
network: panopticon
|
|
37
|
+
|
|
38
|
+
# Logging
|
|
39
|
+
log:
|
|
40
|
+
level: INFO
|
|
41
|
+
format: common
|
|
42
|
+
|
|
43
|
+
# Access Logs
|
|
44
|
+
accessLog:
|
|
45
|
+
format: common
|
package/dist/chunk-FR2P66GU.js
DELETED
|
@@ -1,352 +0,0 @@
|
|
|
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
|