panopticon-cli 0.4.4 → 0.4.6
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 +84 -2695
- package/dist/{agents-B5NRTVHK.js → agents-54LDKMHR.js} +8 -3
- package/dist/chunk-44EOY2ZL.js +58 -0
- package/dist/chunk-44EOY2ZL.js.map +1 -0
- package/dist/chunk-BWGFN44T.js +224 -0
- package/dist/chunk-BWGFN44T.js.map +1 -0
- package/dist/chunk-F7NQZD6H.js +49 -0
- package/dist/chunk-F7NQZD6H.js.map +1 -0
- package/dist/chunk-HCTJFIJJ.js +159 -0
- package/dist/chunk-HCTJFIJJ.js.map +1 -0
- package/dist/chunk-JM6V62LT.js +650 -0
- package/dist/chunk-JM6V62LT.js.map +1 -0
- package/dist/chunk-K45YD6A3.js +254 -0
- package/dist/chunk-K45YD6A3.js.map +1 -0
- package/dist/chunk-KGPRXDMX.js +137 -0
- package/dist/chunk-KGPRXDMX.js.map +1 -0
- package/dist/chunk-KQAEUOML.js +278 -0
- package/dist/chunk-KQAEUOML.js.map +1 -0
- package/dist/chunk-NYVQC3D7.js +90 -0
- package/dist/chunk-NYVQC3D7.js.map +1 -0
- package/dist/chunk-PUR532O7.js +1556 -0
- package/dist/chunk-PUR532O7.js.map +1 -0
- package/dist/chunk-VTDDVLCK.js +1977 -0
- package/dist/chunk-VTDDVLCK.js.map +1 -0
- package/dist/chunk-Z24TY3XN.js +916 -0
- package/dist/chunk-Z24TY3XN.js.map +1 -0
- package/dist/chunk-ZHC57RCV.js +44 -0
- package/dist/chunk-ZHC57RCV.js.map +1 -0
- package/dist/{chunk-ITI4IC5A.js → chunk-ZZ3477GY.js} +69 -100
- package/dist/chunk-ZZ3477GY.js.map +1 -0
- package/dist/cli/index.js +4664 -2912
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/public/assets/index-CRqsEkmn.css +32 -0
- package/dist/dashboard/public/assets/index-DPSUbu4A.js +645 -0
- package/dist/dashboard/public/index.html +15 -3
- package/dist/dashboard/server.js +45663 -17860
- package/dist/dns-L3L2BB27.js +30 -0
- package/dist/dns-L3L2BB27.js.map +1 -0
- package/dist/index.d.ts +63 -3
- package/dist/index.js +42 -18
- package/dist/index.js.map +1 -1
- package/dist/projects-ESIB34QQ.js +43 -0
- package/dist/projects-ESIB34QQ.js.map +1 -0
- package/dist/remote-agents-Z3R2A5BN.js +25 -0
- package/dist/remote-agents-Z3R2A5BN.js.map +1 -0
- package/dist/remote-workspace-HI4VML6H.js +179 -0
- package/dist/remote-workspace-HI4VML6H.js.map +1 -0
- package/dist/specialist-context-SNCJ7O7G.js +256 -0
- package/dist/specialist-context-SNCJ7O7G.js.map +1 -0
- package/dist/specialist-logs-A7ODEK2T.js +43 -0
- package/dist/specialist-logs-A7ODEK2T.js.map +1 -0
- package/dist/specialists-C7XLNSXQ.js +121 -0
- package/dist/specialists-C7XLNSXQ.js.map +1 -0
- package/dist/traefik-WI3KSRGG.js +12 -0
- package/dist/traefik-WI3KSRGG.js.map +1 -0
- package/package.json +1 -1
- package/templates/traefik/docker-compose.yml +1 -1
- package/templates/traefik/dynamic/panopticon.yml.template +41 -0
- package/templates/traefik/traefik.yml +8 -0
- package/dist/chunk-7HHDVXBM.js +0 -349
- package/dist/chunk-7HHDVXBM.js.map +0 -1
- package/dist/chunk-H45CLB7E.js +0 -2044
- package/dist/chunk-H45CLB7E.js.map +0 -1
- package/dist/chunk-ITI4IC5A.js.map +0 -1
- package/dist/dashboard/public/assets/index-BDd8hGYb.css +0 -32
- package/dist/dashboard/public/assets/index-sFwLPko-.js +0 -556
- package/templates/traefik/dynamic/panopticon.yml +0 -51
- /package/dist/{agents-B5NRTVHK.js.map → agents-54LDKMHR.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/remote/exe-provider.ts"],"sourcesContent":["/**\n * exe.dev Remote Provider\n *\n * Implements the RemoteProvider interface for exe.dev cloud VMs.\n * exe.dev provides affordable dev VMs with persistent storage.\n *\n * exe.dev uses an SSH-based API:\n * - `ssh exe.dev` - Access the CLI\n * - `ssh exe.dev new` - Create a new VM\n * - `ssh exe.dev ls` - List VMs\n * - `ssh vmname.exe.xyz` - SSH into a VM\n *\n * Pricing (as of 2025):\n * - Individual: $20/month, 8GB RAM\n * - Team: $25/month/user, 8GB RAM\n * - Enterprise: $30/month/user, 16GB RAM\n *\n * @see https://exe.dev/docs\n */\n\nimport { exec, spawn } from 'child_process';\nimport { promisify } from 'util';\nimport { existsSync, readFileSync, readdirSync, statSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type {\n RemoteProvider,\n VmInfo,\n VmStatus,\n ExecResult,\n} from './interface.js';\n\nconst execAsync = promisify(exec);\n\nexport interface ExeProviderConfig {\n /** Shared infrastructure VM name (for postgres, redis, traefik) */\n infraVm?: string;\n}\n\n/**\n * Parse exe.dev ls output for VM list\n * Format appears to be: vmname (status info)\n */\nfunction parseVmList(output: string): VmInfo[] {\n const vms: VmInfo[] = [];\n const lines = output.trim().split('\\n');\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('Your VMs')) continue;\n\n // exe.dev format: \" • pan-pan-81-ws.exe.xyz - running (boldsoftware/exeuntu)\"\n // Extract VM name (without .exe.xyz suffix) and status\n const match = trimmed.match(/[•\\-]\\s*([a-z0-9-]+)\\.exe\\.xyz\\s*-\\s*(\\w+)/i);\n if (match) {\n const name = match[1];\n const statusText = match[2].toLowerCase();\n const status: VmStatus = statusText === 'running' ? 'running' :\n statusText === 'stopped' ? 'stopped' : 'unknown';\n vms.push({ name, status });\n }\n }\n\n return vms;\n}\n\n/**\n * Check if SSH key is configured for exe.dev\n */\nasync function canSshToExeDev(): Promise<boolean> {\n try {\n // Try a quick command - ssh exe.dev ls should work if authenticated\n const { stdout } = await execAsync('ssh -o BatchMode=yes -o ConnectTimeout=5 exe.dev ls 2>&1', {\n timeout: 10000,\n });\n // If we get output without permission denied, we're good\n return !stdout.includes('Permission denied') && !stdout.includes('Host key verification failed');\n } catch (error: any) {\n // Check if it's just an empty list (which is fine)\n if (error.stdout && !error.stdout.includes('Permission denied')) {\n return true;\n }\n return false;\n }\n}\n\nexport class ExeProvider implements RemoteProvider {\n readonly name = 'exe';\n private config: ExeProviderConfig;\n\n constructor(config: ExeProviderConfig = {}) {\n this.config = config;\n }\n\n /**\n * Check if user is authenticated with exe.dev\n * This checks if SSH key is configured and can connect\n */\n async isAuthenticated(): Promise<boolean> {\n return canSshToExeDev();\n }\n\n /**\n * Execute a command on the exe.dev CLI (via ssh exe.dev)\n */\n private async exeCmd(command: string): Promise<ExecResult> {\n try {\n const { stdout, stderr } = await execAsync(`ssh exe.dev ${command}`, {\n timeout: 60000,\n maxBuffer: 10 * 1024 * 1024,\n });\n return { stdout, stderr, exitCode: 0 };\n } catch (error: any) {\n return {\n stdout: error.stdout || '',\n stderr: error.stderr || error.message,\n exitCode: error.code || 1,\n };\n }\n }\n\n /**\n * Create a new VM on exe.dev\n */\n async createVm(name: string): Promise<VmInfo> {\n try {\n // exe.dev uses --name flag, and names must be valid hostnames\n const result = await this.exeCmd(`new --name=${name}`);\n\n if (result.exitCode !== 0) {\n throw new Error(`Failed to create VM: ${result.stderr}`);\n }\n\n // Wait a moment for VM to be ready\n await new Promise(resolve => setTimeout(resolve, 5000));\n\n return { name, status: 'running' };\n } catch (error: any) {\n throw new Error(`Failed to create VM ${name}: ${error.message}`);\n }\n }\n\n /**\n * Delete a VM on exe.dev\n */\n async deleteVm(name: string): Promise<void> {\n try {\n const result = await this.exeCmd(`rm ${name}`);\n\n if (result.exitCode !== 0 && !result.stderr.includes('not found')) {\n throw new Error(result.stderr);\n }\n } catch (error: any) {\n throw new Error(`Failed to delete VM ${name}: ${error.message}`);\n }\n }\n\n /**\n * List all VMs\n */\n async listVms(): Promise<VmInfo[]> {\n try {\n const result = await this.exeCmd('ls');\n\n if (result.exitCode !== 0) {\n throw new Error(result.stderr);\n }\n\n return parseVmList(result.stdout);\n } catch (error: any) {\n throw new Error(`Failed to list VMs: ${error.message}`);\n }\n }\n\n /**\n * Get VM status\n */\n async getStatus(name: string): Promise<VmStatus> {\n try {\n const vms = await this.listVms();\n const vm = vms.find(v => v.name === name);\n return vm?.status || 'unknown';\n } catch {\n return 'unknown';\n }\n }\n\n /**\n * Get detailed VM info\n */\n async getVmInfo(name: string): Promise<VmInfo | null> {\n try {\n const vms = await this.listVms();\n return vms.find(v => v.name === name) || null;\n } catch {\n return null;\n }\n }\n\n /**\n * Start a stopped VM\n * Note: exe.dev VMs are persistent and always running\n */\n async startVm(name: string): Promise<void> {\n // exe.dev VMs don't have start/stop - they're always running\n // Check if VM exists\n const status = await this.getStatus(name);\n if (status === 'unknown') {\n throw new Error(`VM ${name} not found`);\n }\n }\n\n /**\n * Stop a running VM\n * Note: exe.dev VMs are persistent - use rm to delete\n */\n async stopVm(name: string): Promise<void> {\n // exe.dev doesn't have stop - VMs are always running or deleted\n // We can implement \"stop\" as a no-op or throw\n console.warn(`exe.dev VMs cannot be stopped, only deleted. VM ${name} continues running.`);\n }\n\n /**\n * Execute a command on VM via SSH\n * SSH to vmname.exe.xyz\n */\n async ssh(vm: string, command: string): Promise<ExecResult> {\n try {\n const sshHost = `${vm}.exe.xyz`;\n const escapedCmd = command.replace(/\"/g, '\\\\\"');\n\n // Use -A for agent forwarding so the VM can access GitHub with user's SSH keys\n const { stdout, stderr } = await execAsync(`ssh -A ${sshHost} \"${escapedCmd}\"`, {\n timeout: 300000, // 5 minutes\n maxBuffer: 50 * 1024 * 1024, // 50MB\n });\n\n return { stdout, stderr, exitCode: 0 };\n } catch (error: any) {\n return {\n stdout: error.stdout || '',\n stderr: error.stderr || error.message,\n exitCode: error.code || 1,\n };\n }\n }\n\n /**\n * Execute a command and stream output\n */\n async *sshStream(vm: string, command: string): AsyncIterable<string> {\n const sshHost = `${vm}.exe.xyz`;\n // Use -A for agent forwarding\n const child = spawn('ssh', ['-A', sshHost, command], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n for await (const chunk of child.stdout) {\n yield chunk.toString();\n }\n\n for await (const chunk of child.stderr) {\n yield chunk.toString();\n }\n }\n\n /**\n * Copy file to VM\n */\n async copyToVm(vm: string, localPath: string, remotePath: string): Promise<void> {\n try {\n const sshHost = `${vm}.exe.xyz`;\n await execAsync(`scp \"${localPath}\" ${sshHost}:${remotePath}`, { timeout: 300000 });\n } catch (error: any) {\n throw new Error(`Failed to copy ${localPath} to ${vm}:${remotePath}: ${error.message}`);\n }\n }\n\n /**\n * Copy file from VM\n */\n async copyFromVm(vm: string, remotePath: string, localPath: string): Promise<void> {\n try {\n const sshHost = `${vm}.exe.xyz`;\n await execAsync(`scp ${sshHost}:${remotePath} \"${localPath}\"`, { timeout: 300000 });\n } catch (error: any) {\n throw new Error(`Failed to copy ${vm}:${remotePath} to ${localPath}: ${error.message}`);\n }\n }\n\n /**\n * Expose a port on VM (returns public URL)\n *\n * exe.dev provides automatic HTTPS URLs for services:\n * https://vmname.exe.xyz:PORT\n */\n async exposePort(vm: string, port: number): Promise<string> {\n // exe.dev automatically exposes all ports via HTTPS\n // The URL format is: https://vmname.exe.xyz:PORT\n // Or for default HTTP (80/443): https://vmname.exe.xyz\n if (port === 80 || port === 443) {\n return `https://${vm}.exe.xyz`;\n }\n return `https://${vm}.exe.xyz:${port}`;\n }\n\n /**\n * Create SSH tunnel to VM\n */\n async tunnel(vm: string, remotePort: number, localPort: number): Promise<{ close: () => void }> {\n const sshHost = `${vm}.exe.xyz`;\n const child = spawn('ssh', ['-N', '-L', `${localPort}:localhost:${remotePort}`, sshHost], {\n stdio: 'ignore',\n detached: true,\n });\n\n child.unref();\n\n return {\n close: () => {\n child.kill();\n },\n };\n }\n\n /**\n * Get the configured infrastructure VM name\n */\n getInfraVm(): string | undefined {\n return this.config.infraVm;\n }\n\n /**\n * Initialize the shared infrastructure VM\n *\n * Sets up postgres, redis, and traefik on a dedicated VM.\n */\n async initInfrastructure(vmName: string): Promise<void> {\n // Check if VM exists\n let status = await this.getStatus(vmName);\n\n if (status === 'unknown') {\n // Create the VM\n await this.createVm(vmName);\n // Wait for it to be ready\n await new Promise(resolve => setTimeout(resolve, 10000));\n }\n\n // Install Docker if not present\n const dockerCheck = await this.ssh(vmName, 'which docker');\n if (dockerCheck.exitCode !== 0) {\n await this.ssh(vmName, 'curl -fsSL https://get.docker.com | sh');\n await this.ssh(vmName, 'sudo usermod -aG docker $USER');\n }\n\n // Create docker-compose.yml for shared services\n const composeContent = `\nversion: '3.8'\nservices:\n postgres:\n image: postgres:16\n restart: unless-stopped\n environment:\n POSTGRES_PASSWORD: \\${PAN_POSTGRES_PASSWORD:-panopticon}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n ports:\n - \"5432:5432\"\n\n redis:\n image: redis:7\n restart: unless-stopped\n volumes:\n - redis_data:/data\n ports:\n - \"6379:6379\"\n\n traefik:\n image: traefik:v3.0\n restart: unless-stopped\n ports:\n - \"80:80\"\n - \"443:443\"\n - \"8080:8080\"\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n command:\n - --api.dashboard=true\n - --providers.docker=true\n - --providers.docker.exposedbydefault=false\n - --entrypoints.web.address=:80\n - --entrypoints.websecure.address=:443\n\nvolumes:\n postgres_data:\n redis_data:\n`;\n\n // Write compose file and start services\n await this.ssh(vmName, `mkdir -p /opt/panopticon && cat > /opt/panopticon/docker-compose.yml << 'COMPOSE_EOF'\n${composeContent}\nCOMPOSE_EOF`);\n\n await this.ssh(vmName, 'cd /opt/panopticon && docker compose up -d');\n\n // Store as infra VM\n this.config.infraVm = vmName;\n }\n\n /**\n * Sync Claude Code credentials from local macOS Keychain to remote VM\n *\n * This should be called before spawning agents to ensure fresh credentials.\n * Credentials can expire, and re-running this ensures the VM has valid auth.\n *\n * @returns true if credentials were synced, false if not available\n */\n async syncClaudeCredentials(vmName: string): Promise<boolean> {\n try {\n // Get credentials from macOS Keychain\n const { stdout: credentials } = await execAsync(\n 'security find-generic-password -s \"Claude Code-credentials\" -w 2>/dev/null',\n { encoding: 'utf-8' }\n );\n\n if (!credentials || !credentials.trim()) {\n return false;\n }\n\n // Ensure ~/.claude directory exists\n await this.ssh(vmName, 'mkdir -p ~/.claude');\n\n // Send credentials to VM (base64 encode to handle special characters)\n const credsBase64 = Buffer.from(credentials.trim()).toString('base64');\n const result = await this.ssh(vmName, `echo '${credsBase64}' | base64 -d > ~/.claude/.credentials.json`);\n\n return result.exitCode === 0;\n } catch (error: any) {\n // Credentials not found in Keychain or other error\n return false;\n }\n }\n\n /**\n * Sync GitHub CLI authentication from local macOS to remote VM\n *\n * GitHub CLI on macOS stores tokens in Keychain. On Linux VMs, it stores\n * them directly in ~/.config/gh/hosts.yml. This extracts from Keychain\n * and writes to the VM's config file.\n *\n * @returns true if auth was synced, false if not available\n */\n async syncGitHubAuth(vmName: string): Promise<boolean> {\n try {\n // Get GitHub token from macOS Keychain\n const { stdout: tokenRaw } = await execAsync(\n 'security find-generic-password -s \"gh:github.com\" -w 2>/dev/null',\n { encoding: 'utf-8' }\n );\n\n if (!tokenRaw || !tokenRaw.trim()) {\n return false;\n }\n\n // The token may be base64-encoded with go-keyring prefix\n let token = tokenRaw.trim();\n if (token.startsWith('go-keyring-base64:')) {\n token = Buffer.from(token.replace('go-keyring-base64:', ''), 'base64').toString('utf-8');\n }\n\n // Get GitHub username from local config\n let username = 'user';\n try {\n const { stdout: configOutput } = await execAsync('cat ~/.config/gh/hosts.yml 2>/dev/null', { encoding: 'utf-8' });\n const userMatch = configOutput.match(/user:\\s*(\\S+)/);\n if (userMatch) {\n username = userMatch[1];\n }\n } catch {\n // Use default\n }\n\n // Create hosts.yml content for Linux (token stored directly in file)\n const hostsYml = `github.com:\n oauth_token: ${token}\n git_protocol: ssh\n user: ${username}\n`;\n\n // Ensure ~/.config/gh directory exists and write hosts.yml\n await this.ssh(vmName, 'mkdir -p ~/.config/gh');\n const hostsBase64 = Buffer.from(hostsYml).toString('base64');\n const result = await this.ssh(vmName, `echo '${hostsBase64}' | base64 -d > ~/.config/gh/hosts.yml && chmod 600 ~/.config/gh/hosts.yml`);\n\n return result.exitCode === 0;\n } catch (error: any) {\n // Token not found in Keychain or other error\n return false;\n }\n }\n\n /**\n * Sync GitLab CLI (glab) authentication to a remote VM\n *\n * Copies the glab config from local machine to the remote VM.\n * This allows the remote agent to use glab commands for MRs, etc.\n */\n async syncGitLabAuth(vmName: string): Promise<boolean> {\n try {\n // glab stores config in ~/.config/glab-cli/config.yml\n const glabConfigPath = join(homedir(), '.config', 'glab-cli', 'config.yml');\n\n if (!existsSync(glabConfigPath)) {\n console.log(`[exe-provider] No glab config found at ${glabConfigPath}`);\n return false;\n }\n\n // Read local glab config\n const glabConfig = readFileSync(glabConfigPath, 'utf-8');\n\n // Ensure ~/.config/glab-cli directory exists on remote\n await this.ssh(vmName, 'mkdir -p ~/.config/glab-cli');\n\n // Write config to remote\n const configBase64 = Buffer.from(glabConfig).toString('base64');\n const result = await this.ssh(vmName, `echo '${configBase64}' | base64 -d > ~/.config/glab-cli/config.yml && chmod 600 ~/.config/glab-cli/config.yml`);\n\n if (result.exitCode === 0) {\n console.log(`[exe-provider] GitLab auth synced to ${vmName}`);\n return true;\n }\n\n return false;\n } catch (error: any) {\n console.error(`[exe-provider] Failed to sync GitLab auth: ${error.message}`);\n return false;\n }\n }\n\n /**\n * Sync all credentials needed for remote workspace operation\n *\n * This syncs:\n * - Claude Code OAuth credentials\n * - GitHub CLI authentication\n *\n * Call this before spawning agents to ensure fresh credentials.\n *\n * @returns object with sync status for each credential type\n */\n async syncAllCredentials(vmName: string): Promise<{\n claude: boolean;\n github: boolean;\n }> {\n const [claude, github] = await Promise.all([\n this.syncClaudeCredentials(vmName),\n this.syncGitHubAuth(vmName),\n ]);\n\n return { claude, github };\n }\n\n /**\n * Install beads CLI (bd) on a remote VM\n *\n * Beads is required for planning agents to create task breakdowns.\n * Downloads the binary from GitHub releases since npm/node are often\n * not available on exe.dev VMs.\n *\n * @returns true if bd is available (already installed or just installed)\n */\n async installBeads(vmName: string): Promise<boolean> {\n const FALLBACK_BEADS_VERSION = '0.49.4';\n\n try {\n // Check if bd is already installed\n const checkResult = await this.ssh(vmName, 'which bd');\n if (checkResult.exitCode === 0 && checkResult.stdout.trim()) {\n console.log(`[exe-provider] bd already installed on ${vmName}`);\n return true;\n }\n\n console.log(`[exe-provider] Installing bd (beads CLI) on ${vmName}...`);\n\n // Download from GitHub releases - exe.dev VMs typically don't have npm/node\n // but do have curl and sudo access.\n // Resolve the latest version dynamically with fallback to known-good version.\n const installCmd = `\n cd /tmp && \\\n BEADS_VERSION=$(curl -sI \"https://github.com/steveyegge/beads/releases/latest\" 2>/dev/null | grep -i '^location:' | sed 's|.*/v||' | tr -d '\\\\r\\\\n') && \\\n if [ -z \"$BEADS_VERSION\" ]; then BEADS_VERSION=\"${FALLBACK_BEADS_VERSION}\"; echo \"Using fallback version: $BEADS_VERSION\"; else echo \"Detected version: $BEADS_VERSION\"; fi && \\\n echo \"Downloading beads v$BEADS_VERSION...\" && \\\n curl -sL \"https://github.com/steveyegge/beads/releases/download/v\\${BEADS_VERSION}/beads_\\${BEADS_VERSION}_linux_amd64.tar.gz\" -o beads.tar.gz && \\\n tar -xzf beads.tar.gz && \\\n sudo mv bd /usr/local/bin/ && \\\n rm -f beads.tar.gz CHANGELOG.md LICENSE README.md\n `.trim().replace(/\\n\\s+/g, ' ');\n\n const installResult = await this.ssh(vmName, installCmd);\n if (installResult.stderr) {\n console.log(`[exe-provider] bd install stderr on ${vmName}: ${installResult.stderr}`);\n }\n if (installResult.stdout) {\n console.log(`[exe-provider] bd install output on ${vmName}: ${installResult.stdout.trim()}`);\n }\n\n // Verify installation\n const verifyResult = await this.ssh(vmName, 'which bd && bd --version');\n if (verifyResult.exitCode === 0 && verifyResult.stdout.includes('bd version')) {\n console.log(`[exe-provider] bd installed successfully on ${vmName}: ${verifyResult.stdout.trim()}`);\n return true;\n }\n\n console.warn(`[exe-provider] Failed to install bd on ${vmName} - verify exitCode: ${verifyResult.exitCode}, stdout: ${verifyResult.stdout}, stderr: ${verifyResult.stderr}`);\n return false;\n } catch (error: any) {\n console.error(`[exe-provider] Error installing bd on ${vmName}:`, error.message);\n return false;\n }\n }\n\n /**\n * Initialize beads in a workspace on a remote VM\n *\n * Creates .beads/ directory and initializes the database if needed.\n */\n async initBeads(vmName: string, workspacePath: string = '/workspace'): Promise<boolean> {\n try {\n // Check if .beads already exists\n const checkResult = await this.ssh(vmName, `test -d ${workspacePath}/.beads && echo \"exists\"`);\n if (checkResult.stdout.includes('exists')) {\n console.log(`[exe-provider] .beads already initialized on ${vmName}`);\n return true;\n }\n\n console.log(`[exe-provider] Initializing beads on ${vmName}...`);\n\n // Initialize beads in the workspace\n const initResult = await this.ssh(vmName, `cd ${workspacePath} && bd init 2>/dev/null || true`);\n\n // Verify\n const verifyResult = await this.ssh(vmName, `test -d ${workspacePath}/.beads && echo \"ok\"`);\n if (verifyResult.stdout.includes('ok')) {\n console.log(`[exe-provider] beads initialized on ${vmName}`);\n return true;\n }\n\n // If bd init failed, create the directory manually\n await this.ssh(vmName, `mkdir -p ${workspacePath}/.beads`);\n return true;\n } catch (error: any) {\n console.error(`[exe-provider] Error initializing beads on ${vmName}:`, error.message);\n return false;\n }\n }\n\n /**\n * Sync beads from remote VM to git\n *\n * Exports beads database to JSONL, commits, and pushes.\n * This should be called after planning completes to persist tasks.\n */\n async syncBeadsToGit(vmName: string, workspacePath: string = '/workspace', commitMessage?: string): Promise<boolean> {\n try {\n console.log(`[exe-provider] Syncing beads to git on ${vmName}...`);\n\n // Export beads to JSONL\n const syncResult = await this.ssh(vmName, `cd ${workspacePath} && bd sync 2>/dev/null || true`);\n\n // Check if there are changes to commit\n const statusResult = await this.ssh(vmName, `cd ${workspacePath} && git status --porcelain .beads/ .planning/ 2>/dev/null`);\n\n if (!statusResult.stdout.trim()) {\n console.log(`[exe-provider] No beads changes to commit on ${vmName}`);\n return true;\n }\n\n // Add and commit\n const msg = commitMessage || 'Sync beads from planning session';\n await this.ssh(vmName, `cd ${workspacePath} && git add .beads/ .planning/ 2>/dev/null || true`);\n await this.ssh(vmName, `cd ${workspacePath} && git commit -m \"${msg}\" 2>/dev/null || true`);\n\n // Push - use -u to set upstream if not already set\n const branchResult = await this.ssh(vmName, `cd ${workspacePath} && git branch --show-current`);\n const branch = branchResult.stdout.trim() || 'main';\n const pushResult = await this.ssh(vmName, `cd ${workspacePath} && git push -u origin ${branch} 2>&1`);\n if (pushResult.exitCode !== 0) {\n console.warn(`[exe-provider] Git push failed on ${vmName}: ${pushResult.stderr || pushResult.stdout}`);\n return false;\n }\n\n console.log(`[exe-provider] Beads synced and pushed from ${vmName}`);\n return true;\n } catch (error: any) {\n console.error(`[exe-provider] Error syncing beads on ${vmName}:`, error.message);\n return false;\n }\n }\n\n /**\n * Query beads on a remote VM\n *\n * Runs bd search on the remote and returns results.\n */\n async queryBeads(vmName: string, searchTerm: string, workspacePath: string = '/workspace'): Promise<any[]> {\n try {\n const result = await this.ssh(vmName, `cd ${workspacePath} && bd search \"${searchTerm}\" --json 2>/dev/null || echo \"[]\"`);\n\n if (result.exitCode !== 0 || !result.stdout.trim()) {\n return [];\n }\n\n try {\n return JSON.parse(result.stdout.trim());\n } catch {\n return [];\n }\n } catch (error: any) {\n console.error(`[exe-provider] Error querying beads on ${vmName}:`, error.message);\n return [];\n }\n }\n\n /**\n * Configure Claude Code on a VM for autonomous operation\n *\n * Sets up:\n * - ~/.claude.json with bypass permissions and onboarding settings\n * - ~/.bashrc with proper terminal environment (TERM, LANG, etc.)\n *\n * This is required for remote agents to run without human interaction.\n */\n async configureClaudeCode(vmName: string): Promise<void> {\n // Configure Claude settings\n const setupScript = `\nimport json, os\npath = os.path.expanduser(\"~/.claude.json\")\ndata = {}\nif os.path.exists(path):\n with open(path, \"r\") as f:\n data = json.load(f)\ndata[\"bypassPermissionsModeAccepted\"] = True\ndata[\"hasCompletedOnboarding\"] = True\nwith open(path, \"w\") as f:\n json.dump(data, f, indent=2)\n`;\n const scriptBase64 = Buffer.from(setupScript).toString('base64');\n const result = await this.ssh(vmName, `echo '${scriptBase64}' | base64 -d | python3`);\n if (result.exitCode !== 0) {\n throw new Error(`Failed to configure Claude Code on ${vmName}: ${result.stderr}`);\n }\n\n // Configure terminal environment in .bashrc for proper rendering\n const termEnv = `\n# Panopticon terminal settings\nexport TERM=xterm-256color\nexport LANG=C.UTF-8\nexport LC_ALL=C.UTF-8\nexport COLORTERM=truecolor\n`;\n const termEnvBase64 = Buffer.from(termEnv).toString('base64');\n await this.ssh(vmName, `grep -q \"Panopticon terminal settings\" ~/.bashrc 2>/dev/null || echo '${termEnvBase64}' | base64 -d >> ~/.bashrc`);\n }\n\n /**\n * Copy essential skills from local ~/.panopticon/skills/ to remote VM ~/.claude/skills/\n *\n * Skills are read locally and written to the VM via base64-encoded SSH.\n * Only copies SKILL.md and resource files (not symlinks or deep directories).\n */\n async copySkillsToVm(vmName: string): Promise<void> {\n const essentialSkills = [\n 'beads',\n 'beads-completion-check',\n 'beads-panopticon-guide',\n 'work-complete',\n 'session-health',\n ];\n\n const skillsSourceDir = join(homedir(), '.panopticon', 'skills');\n if (!existsSync(skillsSourceDir)) {\n console.log(`[exe-provider] No skills source directory at ${skillsSourceDir}`);\n return;\n }\n\n // Create skills directory on remote\n await this.ssh(vmName, 'mkdir -p ~/.claude/skills');\n\n for (const skillName of essentialSkills) {\n const skillDir = join(skillsSourceDir, skillName);\n if (!existsSync(skillDir)) {\n continue;\n }\n\n try {\n // Create skill directory on remote\n await this.ssh(vmName, `mkdir -p ~/.claude/skills/${skillName}`);\n\n // Copy SKILL.md\n const skillMdPath = join(skillDir, 'SKILL.md');\n if (existsSync(skillMdPath)) {\n const content = readFileSync(skillMdPath, 'utf-8');\n const contentBase64 = Buffer.from(content).toString('base64');\n await this.ssh(vmName, `echo '${contentBase64}' | base64 -d > ~/.claude/skills/${skillName}/SKILL.md`);\n }\n\n // Copy resources/ directory if it exists\n const resourcesDir = join(skillDir, 'resources');\n if (existsSync(resourcesDir) && statSync(resourcesDir).isDirectory()) {\n await this.ssh(vmName, `mkdir -p ~/.claude/skills/${skillName}/resources`);\n const resourceFiles = readdirSync(resourcesDir).filter(f => f.endsWith('.md'));\n for (const resourceFile of resourceFiles) {\n const resourcePath = join(resourcesDir, resourceFile);\n if (statSync(resourcePath).isFile()) {\n const resourceContent = readFileSync(resourcePath, 'utf-8');\n const resourceBase64 = Buffer.from(resourceContent).toString('base64');\n await this.ssh(vmName, `echo '${resourceBase64}' | base64 -d > ~/.claude/skills/${skillName}/resources/${resourceFile}`);\n }\n }\n }\n\n console.log(`[exe-provider] Copied skill ${skillName} to ${vmName}`);\n } catch (error: any) {\n console.warn(`[exe-provider] Failed to copy skill ${skillName} to ${vmName}: ${error.message}`);\n }\n }\n }\n}\n\n/**\n * Factory function to create an ExeProvider with config from Panopticon settings\n */\nexport function createExeProvider(config?: ExeProviderConfig): ExeProvider {\n return new ExeProvider(config);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PANOPTICON_HOME,
|
|
3
|
+
init_paths
|
|
4
|
+
} from "./chunk-KGPRXDMX.js";
|
|
5
|
+
import {
|
|
6
|
+
__esm,
|
|
7
|
+
__export,
|
|
8
|
+
init_esm_shims
|
|
9
|
+
} from "./chunk-ZHC57RCV.js";
|
|
10
|
+
|
|
11
|
+
// src/lib/projects.ts
|
|
12
|
+
var projects_exports = {};
|
|
13
|
+
__export(projects_exports, {
|
|
14
|
+
PROJECTS_CONFIG_FILE: () => PROJECTS_CONFIG_FILE,
|
|
15
|
+
createDefaultProjectsConfig: () => createDefaultProjectsConfig,
|
|
16
|
+
extractTeamPrefix: () => extractTeamPrefix,
|
|
17
|
+
findProjectByTeam: () => findProjectByTeam,
|
|
18
|
+
getProject: () => getProject,
|
|
19
|
+
getSpecialistConfig: () => getSpecialistConfig,
|
|
20
|
+
getSpecialistPromptOverride: () => getSpecialistPromptOverride,
|
|
21
|
+
getSpecialistRetention: () => getSpecialistRetention,
|
|
22
|
+
hasProjects: () => hasProjects,
|
|
23
|
+
initializeProjectsConfig: () => initializeProjectsConfig,
|
|
24
|
+
listProjects: () => listProjects,
|
|
25
|
+
loadProjectsConfig: () => loadProjectsConfig,
|
|
26
|
+
registerProject: () => registerProject,
|
|
27
|
+
resolveProjectFromIssue: () => resolveProjectFromIssue,
|
|
28
|
+
resolveProjectPath: () => resolveProjectPath,
|
|
29
|
+
saveProjectsConfig: () => saveProjectsConfig,
|
|
30
|
+
unregisterProject: () => unregisterProject
|
|
31
|
+
});
|
|
32
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
33
|
+
import { join } from "path";
|
|
34
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
35
|
+
function loadProjectsConfig() {
|
|
36
|
+
if (!existsSync(PROJECTS_CONFIG_FILE)) {
|
|
37
|
+
return { projects: {} };
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const content = readFileSync(PROJECTS_CONFIG_FILE, "utf-8");
|
|
41
|
+
const config = parseYaml(content);
|
|
42
|
+
return config || { projects: {} };
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(`Failed to parse projects.yaml: ${error.message}`);
|
|
45
|
+
return { projects: {} };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function saveProjectsConfig(config) {
|
|
49
|
+
const dir = PANOPTICON_HOME;
|
|
50
|
+
if (!existsSync(dir)) {
|
|
51
|
+
mkdirSync(dir, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
const yaml = stringifyYaml(config, { indent: 2 });
|
|
54
|
+
writeFileSync(PROJECTS_CONFIG_FILE, yaml, "utf-8");
|
|
55
|
+
}
|
|
56
|
+
function listProjects() {
|
|
57
|
+
const config = loadProjectsConfig();
|
|
58
|
+
return Object.entries(config.projects).map(([key, projectConfig]) => ({
|
|
59
|
+
key,
|
|
60
|
+
config: projectConfig
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
function registerProject(key, projectConfig) {
|
|
64
|
+
const config = loadProjectsConfig();
|
|
65
|
+
config.projects[key] = projectConfig;
|
|
66
|
+
saveProjectsConfig(config);
|
|
67
|
+
}
|
|
68
|
+
function unregisterProject(key) {
|
|
69
|
+
const config = loadProjectsConfig();
|
|
70
|
+
if (config.projects[key]) {
|
|
71
|
+
delete config.projects[key];
|
|
72
|
+
saveProjectsConfig(config);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
function extractTeamPrefix(issueId) {
|
|
78
|
+
const match = issueId.match(/^([A-Z]+)-\d+$/i);
|
|
79
|
+
return match ? match[1].toUpperCase() : null;
|
|
80
|
+
}
|
|
81
|
+
function findProjectByTeam(teamPrefix) {
|
|
82
|
+
const config = loadProjectsConfig();
|
|
83
|
+
for (const [, projectConfig] of Object.entries(config.projects)) {
|
|
84
|
+
if (projectConfig.linear_team?.toUpperCase() === teamPrefix.toUpperCase()) {
|
|
85
|
+
return projectConfig;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function resolveProjectPath(project, labels = []) {
|
|
91
|
+
if (!project.issue_routing || project.issue_routing.length === 0) {
|
|
92
|
+
return project.path;
|
|
93
|
+
}
|
|
94
|
+
const normalizedLabels = labels.map((l) => l.toLowerCase());
|
|
95
|
+
for (const rule of project.issue_routing) {
|
|
96
|
+
if (rule.labels && rule.labels.length > 0) {
|
|
97
|
+
const ruleLabels = rule.labels.map((l) => l.toLowerCase());
|
|
98
|
+
const hasMatch = ruleLabels.some((label) => normalizedLabels.includes(label));
|
|
99
|
+
if (hasMatch) {
|
|
100
|
+
return rule.path;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const rule of project.issue_routing) {
|
|
105
|
+
if (rule.default) {
|
|
106
|
+
return rule.path;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return project.path;
|
|
110
|
+
}
|
|
111
|
+
function resolveProjectFromIssue(issueId, labels = []) {
|
|
112
|
+
const teamPrefix = extractTeamPrefix(issueId);
|
|
113
|
+
if (!teamPrefix) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const config = loadProjectsConfig();
|
|
117
|
+
for (const [key, projectConfig] of Object.entries(config.projects)) {
|
|
118
|
+
if (projectConfig.linear_team?.toUpperCase() === teamPrefix) {
|
|
119
|
+
const resolvedPath = resolveProjectPath(projectConfig, labels);
|
|
120
|
+
return {
|
|
121
|
+
projectKey: key,
|
|
122
|
+
projectName: projectConfig.name,
|
|
123
|
+
projectPath: resolvedPath,
|
|
124
|
+
linearTeam: projectConfig.linear_team
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
function getProject(key) {
|
|
131
|
+
const config = loadProjectsConfig();
|
|
132
|
+
return config.projects[key] || null;
|
|
133
|
+
}
|
|
134
|
+
function hasProjects() {
|
|
135
|
+
const config = loadProjectsConfig();
|
|
136
|
+
return Object.keys(config.projects).length > 0;
|
|
137
|
+
}
|
|
138
|
+
function createDefaultProjectsConfig() {
|
|
139
|
+
const defaultConfig = {
|
|
140
|
+
projects: {
|
|
141
|
+
// Example project - commented out in actual file
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
return defaultConfig;
|
|
145
|
+
}
|
|
146
|
+
function initializeProjectsConfig() {
|
|
147
|
+
if (existsSync(PROJECTS_CONFIG_FILE)) {
|
|
148
|
+
console.log(`Projects config already exists at ${PROJECTS_CONFIG_FILE}`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const exampleYaml = `# Panopticon Project Registry
|
|
152
|
+
# Maps Linear teams to project paths for workspace creation
|
|
153
|
+
|
|
154
|
+
projects:
|
|
155
|
+
# Example: Mind Your Now project
|
|
156
|
+
# myn:
|
|
157
|
+
# name: "Mind Your Now"
|
|
158
|
+
# path: /home/user/projects/myn
|
|
159
|
+
# linear_team: MIN
|
|
160
|
+
# issue_routing:
|
|
161
|
+
# # Route docs/marketing issues to docs repo
|
|
162
|
+
# - labels: [docs, marketing, seo, landing-pages]
|
|
163
|
+
# path: /home/user/projects/myn/docs
|
|
164
|
+
# # Default: main repo
|
|
165
|
+
# - default: true
|
|
166
|
+
# path: /home/user/projects/myn
|
|
167
|
+
# specialists:
|
|
168
|
+
# context_runs: 5
|
|
169
|
+
# digest_model: null # Use same model as specialist
|
|
170
|
+
# retention:
|
|
171
|
+
# max_days: 30
|
|
172
|
+
# max_runs: 50
|
|
173
|
+
# prompts:
|
|
174
|
+
# review-agent: |
|
|
175
|
+
# Pay special attention to:
|
|
176
|
+
# - Database migration safety
|
|
177
|
+
# - API backward compatibility
|
|
178
|
+
|
|
179
|
+
# Example: Panopticon itself
|
|
180
|
+
# panopticon:
|
|
181
|
+
# name: "Panopticon"
|
|
182
|
+
# path: /home/user/projects/panopticon
|
|
183
|
+
# linear_team: PAN
|
|
184
|
+
`;
|
|
185
|
+
const dir = PANOPTICON_HOME;
|
|
186
|
+
if (!existsSync(dir)) {
|
|
187
|
+
mkdirSync(dir, { recursive: true });
|
|
188
|
+
}
|
|
189
|
+
writeFileSync(PROJECTS_CONFIG_FILE, exampleYaml, "utf-8");
|
|
190
|
+
console.log(`Created example projects config at ${PROJECTS_CONFIG_FILE}`);
|
|
191
|
+
}
|
|
192
|
+
function getSpecialistConfig(projectKey) {
|
|
193
|
+
const project = getProject(projectKey);
|
|
194
|
+
if (!project || !project.specialists) {
|
|
195
|
+
return DEFAULT_SPECIALIST_CONFIG;
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
context_runs: project.specialists.context_runs ?? DEFAULT_SPECIALIST_CONFIG.context_runs,
|
|
199
|
+
digest_model: project.specialists.digest_model ?? DEFAULT_SPECIALIST_CONFIG.digest_model,
|
|
200
|
+
retention: {
|
|
201
|
+
max_days: project.specialists.retention?.max_days ?? DEFAULT_SPECIALIST_CONFIG.retention.max_days,
|
|
202
|
+
max_runs: project.specialists.retention?.max_runs ?? DEFAULT_SPECIALIST_CONFIG.retention.max_runs
|
|
203
|
+
},
|
|
204
|
+
prompts: project.specialists.prompts ?? DEFAULT_SPECIALIST_CONFIG.prompts
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function getSpecialistRetention(projectKey) {
|
|
208
|
+
const config = getSpecialistConfig(projectKey);
|
|
209
|
+
return config.retention;
|
|
210
|
+
}
|
|
211
|
+
function getSpecialistPromptOverride(projectKey, specialistType) {
|
|
212
|
+
const config = getSpecialistConfig(projectKey);
|
|
213
|
+
return config.prompts[specialistType] || null;
|
|
214
|
+
}
|
|
215
|
+
var PROJECTS_CONFIG_FILE, DEFAULT_SPECIALIST_CONFIG;
|
|
216
|
+
var init_projects = __esm({
|
|
217
|
+
"src/lib/projects.ts"() {
|
|
218
|
+
init_esm_shims();
|
|
219
|
+
init_paths();
|
|
220
|
+
PROJECTS_CONFIG_FILE = join(PANOPTICON_HOME, "projects.yaml");
|
|
221
|
+
DEFAULT_SPECIALIST_CONFIG = {
|
|
222
|
+
context_runs: 5,
|
|
223
|
+
digest_model: null,
|
|
224
|
+
retention: {
|
|
225
|
+
max_days: 30,
|
|
226
|
+
max_runs: 50
|
|
227
|
+
},
|
|
228
|
+
prompts: {}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
export {
|
|
234
|
+
PROJECTS_CONFIG_FILE,
|
|
235
|
+
loadProjectsConfig,
|
|
236
|
+
saveProjectsConfig,
|
|
237
|
+
listProjects,
|
|
238
|
+
registerProject,
|
|
239
|
+
unregisterProject,
|
|
240
|
+
extractTeamPrefix,
|
|
241
|
+
findProjectByTeam,
|
|
242
|
+
resolveProjectPath,
|
|
243
|
+
resolveProjectFromIssue,
|
|
244
|
+
getProject,
|
|
245
|
+
hasProjects,
|
|
246
|
+
createDefaultProjectsConfig,
|
|
247
|
+
initializeProjectsConfig,
|
|
248
|
+
getSpecialistConfig,
|
|
249
|
+
getSpecialistRetention,
|
|
250
|
+
getSpecialistPromptOverride,
|
|
251
|
+
projects_exports,
|
|
252
|
+
init_projects
|
|
253
|
+
};
|
|
254
|
+
//# sourceMappingURL=chunk-K45YD6A3.js.map
|
|
@@ -0,0 +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 * Workspace configuration (imported from workspace-config.ts for full details)\n */\nexport interface WorkspaceConfig {\n type?: 'polyrepo' | 'monorepo';\n workspaces_dir?: string;\n repos?: Array<{ name: string; path: string; branch_prefix?: string }>;\n dns?: { domain: string; entries: string[]; sync_method?: 'wsl2hosts' | 'hosts_file' | 'dnsmasq' };\n ports?: Record<string, { range: [number, number] }>;\n docker?: { traefik?: string; compose_template?: string };\n database?: { seed_file?: string; container_name?: string; [key: string]: any };\n agent?: { template_dir: string; templates?: Array<{ source: string; target: string }>; symlinks?: string[] };\n env?: { template?: string; secrets_file?: string };\n services?: Array<{ name: string; path: string; start_command: string; docker_command?: string; health_url?: string; port?: number }>;\n}\n\n/**\n * Test configuration\n */\nexport interface TestConfig {\n type: string;\n path: string;\n command: string;\n container?: boolean;\n container_name?: string;\n env?: Record<string, string>;\n}\n\n/**\n * Specialist configuration for per-project specialists\n */\nexport interface SpecialistConfig {\n /** Number of recent runs to include in context digest (default: 5) */\n context_runs?: number;\n /** Model to use for generating context digests (null = same as specialist) */\n digest_model?: string | null;\n /** Log retention policy */\n retention?: {\n /** Maximum days to keep logs */\n max_days: number;\n /** Maximum number of runs to keep (whichever is more permissive) */\n max_runs: number;\n };\n /** Per-specialist prompt overrides */\n prompts?: {\n 'review-agent'?: string;\n 'test-agent'?: string;\n 'merge-agent'?: string;\n };\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 /** Workspace configuration */\n workspace?: WorkspaceConfig;\n /** Test configuration by name */\n tests?: Record<string, TestConfig>;\n /** Custom command to create workspaces (e.g., infra/new-feature for MYN) */\n workspace_command?: string;\n /** Custom command to remove workspaces */\n workspace_remove_command?: string;\n /** Specialist agent configuration */\n specialists?: SpecialistConfig;\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 # specialists:\n # context_runs: 5\n # digest_model: null # Use same model as specialist\n # retention:\n # max_days: 30\n # max_runs: 50\n # prompts:\n # review-agent: |\n # Pay special attention to:\n # - Database migration safety\n # - API backward compatibility\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\n/**\n * Default specialist configuration values\n */\nconst DEFAULT_SPECIALIST_CONFIG: Required<SpecialistConfig> = {\n context_runs: 5,\n digest_model: null,\n retention: {\n max_days: 30,\n max_runs: 50,\n },\n prompts: {},\n};\n\n/**\n * Get specialist configuration for a project with defaults\n *\n * @param projectKey - Project key\n * @returns Specialist config with defaults applied\n */\nexport function getSpecialistConfig(projectKey: string): Required<SpecialistConfig> {\n const project = getProject(projectKey);\n\n if (!project || !project.specialists) {\n return DEFAULT_SPECIALIST_CONFIG;\n }\n\n return {\n context_runs: project.specialists.context_runs ?? DEFAULT_SPECIALIST_CONFIG.context_runs,\n digest_model: project.specialists.digest_model ?? DEFAULT_SPECIALIST_CONFIG.digest_model,\n retention: {\n max_days: project.specialists.retention?.max_days ?? DEFAULT_SPECIALIST_CONFIG.retention.max_days,\n max_runs: project.specialists.retention?.max_runs ?? DEFAULT_SPECIALIST_CONFIG.retention.max_runs,\n },\n prompts: project.specialists.prompts ?? DEFAULT_SPECIALIST_CONFIG.prompts,\n };\n}\n\n/**\n * Get retention policy for a project's specialists\n *\n * @param projectKey - Project key\n * @returns Retention policy\n */\nexport function getSpecialistRetention(projectKey: string): { max_days: number; max_runs: number } {\n const config = getSpecialistConfig(projectKey);\n return config.retention;\n}\n\n/**\n * Get custom prompt override for a specialist (if configured)\n *\n * @param projectKey - Project key\n * @param specialistType - Specialist type\n * @returns Custom prompt or null if not configured\n */\nexport function getSpecialistPromptOverride(\n projectKey: string,\n specialistType: 'review-agent' | 'test-agent' | 'merge-agent'\n): string | null {\n const config = getSpecialistConfig(projectKey);\n return config.prompts[specialistType] || null;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__esm,
|
|
3
|
+
init_esm_shims
|
|
4
|
+
} from "./chunk-ZHC57RCV.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/paths.ts
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { existsSync } from "fs";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import { dirname } from "path";
|
|
12
|
+
function isDevMode() {
|
|
13
|
+
try {
|
|
14
|
+
return existsSync(SOURCE_DEV_SKILLS_DIR);
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
var PANOPTICON_HOME, CONFIG_DIR, SKILLS_DIR, COMMANDS_DIR, AGENTS_DIR, BIN_DIR, BACKUPS_DIR, COSTS_DIR, HEARTBEATS_DIR, TRAEFIK_DIR, TRAEFIK_DYNAMIC_DIR, TRAEFIK_CERTS_DIR, CERTS_DIR, CONFIG_FILE, SETTINGS_FILE, CLAUDE_DIR, CODEX_DIR, CURSOR_DIR, GEMINI_DIR, OPENCODE_DIR, SYNC_TARGETS, TEMPLATES_DIR, CLAUDE_MD_TEMPLATES, currentFile, currentDir, packageRoot, SOURCE_TEMPLATES_DIR, SOURCE_TRAEFIK_TEMPLATES, SOURCE_SCRIPTS_DIR, SOURCE_SKILLS_DIR, SOURCE_DEV_SKILLS_DIR, INIT_DIRS;
|
|
20
|
+
var init_paths = __esm({
|
|
21
|
+
"src/lib/paths.ts"() {
|
|
22
|
+
"use strict";
|
|
23
|
+
init_esm_shims();
|
|
24
|
+
PANOPTICON_HOME = process.env.PANOPTICON_HOME || join(homedir(), ".panopticon");
|
|
25
|
+
CONFIG_DIR = PANOPTICON_HOME;
|
|
26
|
+
SKILLS_DIR = join(PANOPTICON_HOME, "skills");
|
|
27
|
+
COMMANDS_DIR = join(PANOPTICON_HOME, "commands");
|
|
28
|
+
AGENTS_DIR = join(PANOPTICON_HOME, "agents");
|
|
29
|
+
BIN_DIR = join(PANOPTICON_HOME, "bin");
|
|
30
|
+
BACKUPS_DIR = join(PANOPTICON_HOME, "backups");
|
|
31
|
+
COSTS_DIR = join(PANOPTICON_HOME, "costs");
|
|
32
|
+
HEARTBEATS_DIR = join(PANOPTICON_HOME, "heartbeats");
|
|
33
|
+
TRAEFIK_DIR = join(PANOPTICON_HOME, "traefik");
|
|
34
|
+
TRAEFIK_DYNAMIC_DIR = join(TRAEFIK_DIR, "dynamic");
|
|
35
|
+
TRAEFIK_CERTS_DIR = join(TRAEFIK_DIR, "certs");
|
|
36
|
+
CERTS_DIR = join(PANOPTICON_HOME, "certs");
|
|
37
|
+
CONFIG_FILE = join(CONFIG_DIR, "config.toml");
|
|
38
|
+
SETTINGS_FILE = join(CONFIG_DIR, "settings.json");
|
|
39
|
+
CLAUDE_DIR = join(homedir(), ".claude");
|
|
40
|
+
CODEX_DIR = join(homedir(), ".codex");
|
|
41
|
+
CURSOR_DIR = join(homedir(), ".cursor");
|
|
42
|
+
GEMINI_DIR = join(homedir(), ".gemini");
|
|
43
|
+
OPENCODE_DIR = join(homedir(), ".opencode");
|
|
44
|
+
SYNC_TARGETS = {
|
|
45
|
+
claude: {
|
|
46
|
+
skills: join(CLAUDE_DIR, "skills"),
|
|
47
|
+
commands: join(CLAUDE_DIR, "commands"),
|
|
48
|
+
agents: join(CLAUDE_DIR, "agents")
|
|
49
|
+
},
|
|
50
|
+
codex: {
|
|
51
|
+
skills: join(CODEX_DIR, "skills"),
|
|
52
|
+
commands: join(CODEX_DIR, "commands"),
|
|
53
|
+
agents: join(CODEX_DIR, "agents")
|
|
54
|
+
},
|
|
55
|
+
cursor: {
|
|
56
|
+
skills: join(CURSOR_DIR, "skills"),
|
|
57
|
+
commands: join(CURSOR_DIR, "commands"),
|
|
58
|
+
agents: join(CURSOR_DIR, "agents")
|
|
59
|
+
},
|
|
60
|
+
gemini: {
|
|
61
|
+
skills: join(GEMINI_DIR, "skills"),
|
|
62
|
+
commands: join(GEMINI_DIR, "commands"),
|
|
63
|
+
agents: join(GEMINI_DIR, "agents")
|
|
64
|
+
},
|
|
65
|
+
opencode: {
|
|
66
|
+
skills: join(OPENCODE_DIR, "skills"),
|
|
67
|
+
commands: join(OPENCODE_DIR, "commands"),
|
|
68
|
+
agents: join(OPENCODE_DIR, "agents")
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
TEMPLATES_DIR = join(PANOPTICON_HOME, "templates");
|
|
72
|
+
CLAUDE_MD_TEMPLATES = join(TEMPLATES_DIR, "claude-md", "sections");
|
|
73
|
+
currentFile = fileURLToPath(import.meta.url);
|
|
74
|
+
currentDir = dirname(currentFile);
|
|
75
|
+
if (currentDir.includes("/src/")) {
|
|
76
|
+
packageRoot = dirname(dirname(currentDir));
|
|
77
|
+
} else {
|
|
78
|
+
packageRoot = currentDir.endsWith("/lib") ? dirname(dirname(currentDir)) : dirname(currentDir);
|
|
79
|
+
}
|
|
80
|
+
SOURCE_TEMPLATES_DIR = join(packageRoot, "templates");
|
|
81
|
+
SOURCE_TRAEFIK_TEMPLATES = join(SOURCE_TEMPLATES_DIR, "traefik");
|
|
82
|
+
SOURCE_SCRIPTS_DIR = join(packageRoot, "scripts");
|
|
83
|
+
SOURCE_SKILLS_DIR = join(packageRoot, "skills");
|
|
84
|
+
SOURCE_DEV_SKILLS_DIR = join(packageRoot, "dev-skills");
|
|
85
|
+
INIT_DIRS = [
|
|
86
|
+
PANOPTICON_HOME,
|
|
87
|
+
SKILLS_DIR,
|
|
88
|
+
COMMANDS_DIR,
|
|
89
|
+
AGENTS_DIR,
|
|
90
|
+
BIN_DIR,
|
|
91
|
+
BACKUPS_DIR,
|
|
92
|
+
COSTS_DIR,
|
|
93
|
+
HEARTBEATS_DIR,
|
|
94
|
+
TEMPLATES_DIR,
|
|
95
|
+
CLAUDE_MD_TEMPLATES,
|
|
96
|
+
CERTS_DIR,
|
|
97
|
+
TRAEFIK_DIR,
|
|
98
|
+
TRAEFIK_DYNAMIC_DIR,
|
|
99
|
+
TRAEFIK_CERTS_DIR
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
export {
|
|
105
|
+
PANOPTICON_HOME,
|
|
106
|
+
CONFIG_DIR,
|
|
107
|
+
SKILLS_DIR,
|
|
108
|
+
COMMANDS_DIR,
|
|
109
|
+
AGENTS_DIR,
|
|
110
|
+
BIN_DIR,
|
|
111
|
+
BACKUPS_DIR,
|
|
112
|
+
COSTS_DIR,
|
|
113
|
+
HEARTBEATS_DIR,
|
|
114
|
+
TRAEFIK_DIR,
|
|
115
|
+
TRAEFIK_DYNAMIC_DIR,
|
|
116
|
+
TRAEFIK_CERTS_DIR,
|
|
117
|
+
CERTS_DIR,
|
|
118
|
+
CONFIG_FILE,
|
|
119
|
+
SETTINGS_FILE,
|
|
120
|
+
CLAUDE_DIR,
|
|
121
|
+
CODEX_DIR,
|
|
122
|
+
CURSOR_DIR,
|
|
123
|
+
GEMINI_DIR,
|
|
124
|
+
OPENCODE_DIR,
|
|
125
|
+
SYNC_TARGETS,
|
|
126
|
+
TEMPLATES_DIR,
|
|
127
|
+
CLAUDE_MD_TEMPLATES,
|
|
128
|
+
SOURCE_TEMPLATES_DIR,
|
|
129
|
+
SOURCE_TRAEFIK_TEMPLATES,
|
|
130
|
+
SOURCE_SCRIPTS_DIR,
|
|
131
|
+
SOURCE_SKILLS_DIR,
|
|
132
|
+
SOURCE_DEV_SKILLS_DIR,
|
|
133
|
+
isDevMode,
|
|
134
|
+
INIT_DIRS,
|
|
135
|
+
init_paths
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=chunk-KGPRXDMX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/paths.ts"],"sourcesContent":["import { homedir } from 'os';\nimport { join } from 'path';\nimport { existsSync } from 'fs';\n\n// Panopticon home directory (can be overridden for testing)\nexport const PANOPTICON_HOME = process.env.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');\nexport const SETTINGS_FILE = join(CONFIG_DIR, 'settings.json');\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');\nexport const OPENCODE_DIR = join(homedir(), '.opencode');\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 opencode: {\n skills: join(OPENCODE_DIR, 'skills'),\n commands: join(OPENCODE_DIR, 'commands'),\n agents: join(OPENCODE_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');\nexport const SOURCE_SKILLS_DIR = join(packageRoot, 'skills');\nexport const SOURCE_DEV_SKILLS_DIR = join(packageRoot, 'dev-skills');\n\n/**\n * Detect if running in development mode (from npm link or panopticon repo)\n *\n * Dev mode is detected if:\n * 1. Running from the panopticon source directory (npm link)\n * 2. The SOURCE_DEV_SKILLS_DIR exists (only present in repo, not in npm package)\n */\nexport function isDevMode(): boolean {\n try {\n // Check if dev-skills directory exists - this is only in the repo, not npm package\n return existsSync(SOURCE_DEV_SKILLS_DIR);\n } catch {\n return false;\n }\n}\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":"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","names":[]}
|