panopticon-cli 0.4.30 → 0.4.32

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.
Files changed (41) hide show
  1. package/dist/{agents-ZG4JIPO2.js → agents-BDFHF4T3.js} +4 -3
  2. package/dist/{chunk-42O6XJWZ.js → chunk-2NIAOCIC.js} +14 -14
  3. package/dist/{chunk-SIAUVHVO.js → chunk-3XAB4IXF.js} +4 -2
  4. package/dist/{chunk-SIAUVHVO.js.map → chunk-3XAB4IXF.js.map} +1 -1
  5. package/dist/chunk-ELK6Q7QI.js +545 -0
  6. package/dist/chunk-ELK6Q7QI.js.map +1 -0
  7. package/dist/chunk-LYSBSZYV.js +1523 -0
  8. package/dist/chunk-LYSBSZYV.js.map +1 -0
  9. package/dist/{chunk-VTMXR7JF.js → chunk-VU4FLXV5.js} +47 -40
  10. package/dist/{chunk-VTMXR7JF.js.map → chunk-VU4FLXV5.js.map} +1 -1
  11. package/dist/{chunk-VH27COUW.js → chunk-XP2DXWYP.js} +49 -10
  12. package/dist/chunk-XP2DXWYP.js.map +1 -0
  13. package/dist/cli/index.js +52 -29
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/{config-QWTS63TU.js → config-BOAMSKTF.js} +4 -2
  16. package/dist/dashboard/public/assets/{index-Dhwz2I7n.js → index-ClYqpcAJ.js} +84 -84
  17. package/dist/dashboard/public/index.html +1 -1
  18. package/dist/dashboard/server.js +93512 -23114
  19. package/dist/index.js +16 -14
  20. package/dist/index.js.map +1 -1
  21. package/dist/{remote-workspace-FNXLMNBG.js → remote-workspace-2G6V2KNP.js} +7 -5
  22. package/dist/{remote-workspace-FNXLMNBG.js.map → remote-workspace-2G6V2KNP.js.map} +1 -1
  23. package/dist/{specialist-context-QVRSHPYN.js → specialist-context-N32QBNNQ.js} +2 -2
  24. package/dist/{specialist-logs-7TQMHCUN.js → specialist-logs-GF3YV4KL.js} +2 -2
  25. package/dist/{specialists-UUXB5KFV.js → specialists-JBIW6MP4.js} +2 -2
  26. package/dist/{traefik-7OLLXUD7.js → traefik-CUJM6K5Z.js} +3 -3
  27. package/package.json +3 -2
  28. package/scripts/record-cost-event.js +243 -79
  29. package/scripts/record-cost-event.ts +128 -68
  30. package/dist/chunk-J3J32DIR.js +0 -279
  31. package/dist/chunk-J3J32DIR.js.map +0 -1
  32. package/dist/chunk-SUMIHS2B.js +0 -1714
  33. package/dist/chunk-SUMIHS2B.js.map +0 -1
  34. package/dist/chunk-VH27COUW.js.map +0 -1
  35. /package/dist/{agents-ZG4JIPO2.js.map → agents-BDFHF4T3.js.map} +0 -0
  36. /package/dist/{chunk-42O6XJWZ.js.map → chunk-2NIAOCIC.js.map} +0 -0
  37. /package/dist/{config-QWTS63TU.js.map → config-BOAMSKTF.js.map} +0 -0
  38. /package/dist/{specialist-context-QVRSHPYN.js.map → specialist-context-N32QBNNQ.js.map} +0 -0
  39. /package/dist/{specialist-logs-7TQMHCUN.js.map → specialist-logs-GF3YV4KL.js.map} +0 -0
  40. /package/dist/{specialists-UUXB5KFV.js.map → specialists-JBIW6MP4.js.map} +0 -0
  41. /package/dist/{traefik-7OLLXUD7.js.map → traefik-CUJM6K5Z.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,25 +1,14 @@
1
1
  import {
2
- GitHubTracker,
3
- GitLabTracker,
4
- IssueNotFoundError,
5
- LinearTracker,
6
2
  LinkManager,
7
- NotImplementedError,
8
- TrackerAuthError,
9
3
  addAlias,
10
4
  cleanOldBackups,
11
5
  createBackup,
12
6
  createBackupTimestamp,
13
- createTracker,
14
- createTrackerFromConfig,
15
7
  detectShell,
16
8
  executeSync,
17
9
  formatIssueRef,
18
10
  getAliasInstructions,
19
- getAllTrackers,
20
11
  getLinkManager,
21
- getPrimaryTracker,
22
- getSecondaryTracker,
23
12
  getShellRcFile,
24
13
  hasAlias,
25
14
  isPanopticonSymlink,
@@ -29,17 +18,28 @@ import {
29
18
  planSync,
30
19
  restoreBackup,
31
20
  syncHooks
32
- } from "./chunk-SUMIHS2B.js";
21
+ } from "./chunk-ELK6Q7QI.js";
33
22
  import {
23
+ GitHubTracker,
24
+ GitLabTracker,
25
+ IssueNotFoundError,
26
+ LinearTracker,
27
+ NotImplementedError,
34
28
  PROVIDERS,
29
+ TrackerAuthError,
30
+ createTracker,
31
+ createTrackerFromConfig,
35
32
  getAgentCommand,
33
+ getAllTrackers,
36
34
  getAvailableModels,
37
35
  getClaudeModelFlag,
38
36
  getDefaultSettings,
39
37
  getDirectProviders,
38
+ getPrimaryTracker,
40
39
  getProviderEnv,
41
40
  getProviderForModel,
42
41
  getRouterProviders,
42
+ getSecondaryTracker,
43
43
  init_providers,
44
44
  init_settings,
45
45
  isAnthropicModel,
@@ -48,14 +48,15 @@ import {
48
48
  requiresRouter,
49
49
  saveSettings,
50
50
  validateSettings
51
- } from "./chunk-J3J32DIR.js";
51
+ } from "./chunk-LYSBSZYV.js";
52
52
  import "./chunk-BBCUK6N2.js";
53
53
  import {
54
54
  getDashboardApiUrl,
55
55
  getDefaultConfig,
56
+ init_config,
56
57
  loadConfig,
57
58
  saveConfig
58
- } from "./chunk-VTMXR7JF.js";
59
+ } from "./chunk-VU4FLXV5.js";
59
60
  import {
60
61
  AGENTS_DIR,
61
62
  BACKUPS_DIR,
@@ -97,6 +98,7 @@ import {
97
98
  // src/index.ts
98
99
  init_esm_shims();
99
100
  init_paths();
101
+ init_config();
100
102
  init_providers();
101
103
  init_settings();
102
104
  export {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Panopticon CLI - Main exports for library usage\nexport * from './lib/paths.js';\nexport * from './lib/config.js';\nexport * from './lib/shell.js';\nexport * from './lib/backup.js';\nexport * from './lib/sync.js';\nexport * from './lib/tracker/index.js';\nexport * from './lib/providers.js';\nexport * from './lib/settings.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AACA;AAMA;AACA;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Panopticon CLI - Main exports for library usage\nexport * from './lib/paths.js';\nexport * from './lib/config.js';\nexport * from './lib/shell.js';\nexport * from './lib/backup.js';\nexport * from './lib/sync.js';\nexport * from './lib/tracker/index.js';\nexport * from './lib/providers.js';\nexport * from './lib/settings.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AACA;AACA;AAKA;AACA;","names":[]}
@@ -1,9 +1,6 @@
1
1
  import {
2
2
  saveWorkspaceMetadata
3
3
  } from "./chunk-44EOY2ZL.js";
4
- import {
5
- loadConfig
6
- } from "./chunk-VTMXR7JF.js";
7
4
  import {
8
5
  extractTeamPrefix,
9
6
  findProjectByTeam,
@@ -13,6 +10,10 @@ import {
13
10
  createExeProvider,
14
11
  init_exe_provider
15
12
  } from "./chunk-JM6V62LT.js";
13
+ import {
14
+ init_config,
15
+ loadConfig
16
+ } from "./chunk-VU4FLXV5.js";
16
17
  import "./chunk-6HXKTOD7.js";
17
18
  import {
18
19
  init_esm_shims
@@ -20,10 +21,11 @@ import {
20
21
 
21
22
  // src/lib/remote-workspace.ts
22
23
  init_esm_shims();
24
+ init_config();
25
+ init_exe_provider();
23
26
  import chalk from "chalk";
24
27
  import { exec } from "child_process";
25
28
  import { promisify } from "util";
26
- init_exe_provider();
27
29
  init_projects();
28
30
  var execAsync = promisify(exec);
29
31
  async function createRemoteWorkspace(issueId, options = {}) {
@@ -176,4 +178,4 @@ EOF`);
176
178
  export {
177
179
  createRemoteWorkspace
178
180
  };
179
- //# sourceMappingURL=remote-workspace-FNXLMNBG.js.map
181
+ //# sourceMappingURL=remote-workspace-2G6V2KNP.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/remote-workspace.ts"],"sourcesContent":["/**\n * Remote Workspace Creation\n *\n * Shared module for creating remote workspaces.\n * Used by both workspace.ts (explicit creation) and work/issue.ts (auto-creation).\n */\n\nimport chalk from 'chalk';\nimport { existsSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { loadConfig } from './config.js';\nimport { createExeProvider } from './remote/exe-provider.js';\nimport { saveWorkspaceMetadata } from './remote/workspace-metadata.js';\nimport type { RemoteWorkspaceMetadata } from './remote/interface.js';\nimport { extractTeamPrefix, findProjectByTeam, resolveProjectFromIssue } from './projects.js';\n\nconst execAsync = promisify(exec);\n\nexport interface CreateRemoteWorkspaceOptions {\n dryRun?: boolean;\n spinner?: { text: string };\n}\n\n/**\n * Create a remote workspace on exe.dev\n */\nexport async function createRemoteWorkspace(\n issueId: string,\n options: CreateRemoteWorkspaceOptions = {}\n): Promise<RemoteWorkspaceMetadata> {\n const config = loadConfig();\n const remoteConfig = config.remote;\n\n if (!remoteConfig?.enabled) {\n throw new Error('Remote workspaces not enabled. Run `pan remote setup`');\n }\n\n const normalizedId = issueId.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n const branchName = `feature/${normalizedId}`;\n const infraVm = remoteConfig.exe?.infra_vm || 'pan-infra';\n const exe = createExeProvider({ infraVm });\n\n // Determine project context\n const teamPrefix = extractTeamPrefix(issueId);\n const projectConfig = teamPrefix ? findProjectByTeam(teamPrefix) : null;\n const projectRoot = projectConfig?.path || process.cwd();\n\n // Determine project identifier for VM name\n let projectId = teamPrefix?.toLowerCase();\n if (!projectId && projectConfig?.linear_team) {\n projectId = projectConfig.linear_team.toLowerCase();\n }\n if (!projectId) {\n try {\n const { stdout } = await execAsync('git remote get-url origin', {\n cwd: projectRoot,\n encoding: 'utf-8',\n });\n const repoMatch = stdout.trim().match(/\\/([^\\/]+?)(\\.git)?$/);\n projectId = repoMatch ? repoMatch[1].toLowerCase().replace(/[^a-z0-9-]/g, '-') : 'proj';\n } catch {\n projectId = 'proj';\n }\n }\n\n // VM names must be valid hostnames (start with letter, alphanumeric + hyphens)\n const vmName = `${projectId}-${normalizedId}-ws`.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n\n if (options.dryRun) {\n console.log(chalk.bold('Would create remote workspace:'));\n console.log(` VM: ${chalk.cyan(vmName)}`);\n console.log(` Project: ${chalk.dim(projectId)}`);\n console.log(` Infra: ${chalk.dim(infraVm)}`);\n console.log(` Branch: ${chalk.dim(branchName)}`);\n throw new Error('Dry run - not implemented in this module');\n }\n\n // Get git remote URL\n let repoUrl = '';\n try {\n const { stdout } = await execAsync('git remote get-url origin', {\n cwd: projectRoot,\n encoding: 'utf-8',\n });\n repoUrl = stdout.trim();\n } catch {\n throw new Error('Could not determine git remote URL. Make sure you are in a git repository with a remote origin.');\n }\n\n if (options.spinner) {\n options.spinner.text = 'Creating VM (this may take 1-2 minutes)...';\n }\n\n // Step 1: Create VM\n await exe.createVm(vmName);\n\n // Step 2: Add GitHub host key and clone repository on VM\n if (options.spinner) {\n options.spinner.text = 'Cloning repository on VM...';\n }\n await exe.ssh(vmName, 'mkdir -p ~/.ssh && ssh-keyscan -t ed25519,rsa github.com >> ~/.ssh/known_hosts 2>/dev/null');\n const cloneResult = await exe.ssh(vmName, `git clone ${repoUrl} ~/workspace`);\n if (cloneResult.exitCode !== 0) {\n await exe.deleteVm(vmName);\n throw new Error(`Failed to clone: ${cloneResult.stderr}`);\n }\n\n // Step 3: Create feature branch\n if (options.spinner) {\n options.spinner.text = 'Creating feature branch...';\n }\n const branchResult = await exe.ssh(vmName, `cd ~/workspace && git checkout -b ${branchName}`);\n if (branchResult.exitCode !== 0) {\n await exe.ssh(vmName, `cd ~/workspace && git checkout ${branchName} || git checkout -b ${branchName}`);\n }\n\n // Step 4: Configure environment for shared infra\n const envContent = `\n# Panopticon Remote Workspace\nWORKSPACE_ID=${normalizedId}\nISSUE_ID=${issueId.toUpperCase()}\n\n# Shared Infrastructure\nPOSTGRES_HOST=${infraVm}\nPOSTGRES_PORT=5432\nPOSTGRES_USER=postgres\nPOSTGRES_PASSWORD=\\${PAN_POSTGRES_PASSWORD:-panopticon}\nDATABASE_NAME=myn_${normalizedId.replace(/-/g, '_')}\n\nREDIS_HOST=${infraVm}\nREDIS_PORT=6379\nREDIS_DATABASE=0\n\n# Spring Boot (if applicable)\nSPRING_DATASOURCE_URL=jdbc:postgresql://${infraVm}:5432/myn_${normalizedId.replace(/-/g, '_')}\nSPRING_DATA_REDIS_HOST=${infraVm}\n`;\n\n await exe.ssh(vmName, `cat > ~/workspace/.env.remote << 'EOF'\n${envContent}\nEOF`);\n\n // Step 5: Create database on shared postgres\n const dbName = `myn_${normalizedId.replace(/-/g, '_')}`;\n try {\n await exe.ssh(infraVm, `docker exec pan-postgres psql -U postgres -c \"CREATE DATABASE ${dbName}\" 2>/dev/null || true`);\n } catch {\n // Non-fatal - database might already exist\n }\n\n // Step 6: Install beads CLI globally on remote VM\n if (options.spinner) {\n options.spinner.text = 'Installing beads CLI...';\n }\n const bdInstalled = await exe.installBeads(vmName);\n if (bdInstalled) {\n await exe.initBeads(vmName, '~/workspace');\n }\n\n // Step 6.5: Copy essential skills to remote VM\n if (options.spinner) {\n options.spinner.text = 'Copying skills to remote VM...';\n }\n await exe.copySkillsToVm(vmName);\n\n // Step 7: Start containers if docker compose exists\n let containersStarted = false;\n let frontendUrl = '';\n let apiUrl = '';\n\n const composeCheck = await exe.ssh(vmName, 'ls ~/workspace/docker-compose.yml ~/workspace/.devcontainer/docker-compose.yml 2>/dev/null | head -1');\n\n if (composeCheck.stdout.trim()) {\n if (options.spinner) {\n options.spinner.text = 'Starting containers...';\n }\n const composeDir = composeCheck.stdout.includes('.devcontainer')\n ? '~/workspace/.devcontainer'\n : '~/workspace';\n\n const upResult = await exe.ssh(vmName, `cd ${composeDir} && docker compose up -d 2>&1`);\n containersStarted = upResult.exitCode === 0;\n\n if (containersStarted) {\n if (options.spinner) {\n options.spinner.text = 'Exposing ports...';\n }\n try {\n frontendUrl = await exe.exposePort(vmName, 4173);\n apiUrl = await exe.exposePort(vmName, 7000);\n } catch {\n // Port exposure failed - not critical\n }\n }\n }\n\n // Step 8: Save workspace metadata\n const metadata: RemoteWorkspaceMetadata = {\n id: normalizedId,\n issue: issueId.toUpperCase(),\n provider: 'exe',\n vmName,\n infraVm,\n database: dbName,\n redisDb: 0,\n urls: {\n frontend: frontendUrl || undefined,\n api: apiUrl || undefined,\n },\n created: new Date(),\n location: 'remote',\n };\n\n saveWorkspaceMetadata(metadata);\n\n return metadata;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/lib/remote-workspace.ts"],"sourcesContent":["/**\n * Remote Workspace Creation\n *\n * Shared module for creating remote workspaces.\n * Used by both workspace.ts (explicit creation) and work/issue.ts (auto-creation).\n */\n\nimport chalk from 'chalk';\nimport { existsSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { loadConfig } from './config.js';\nimport { createExeProvider } from './remote/exe-provider.js';\nimport { saveWorkspaceMetadata } from './remote/workspace-metadata.js';\nimport type { RemoteWorkspaceMetadata } from './remote/interface.js';\nimport { extractTeamPrefix, findProjectByTeam, resolveProjectFromIssue } from './projects.js';\n\nconst execAsync = promisify(exec);\n\nexport interface CreateRemoteWorkspaceOptions {\n dryRun?: boolean;\n spinner?: { text: string };\n}\n\n/**\n * Create a remote workspace on exe.dev\n */\nexport async function createRemoteWorkspace(\n issueId: string,\n options: CreateRemoteWorkspaceOptions = {}\n): Promise<RemoteWorkspaceMetadata> {\n const config = loadConfig();\n const remoteConfig = config.remote;\n\n if (!remoteConfig?.enabled) {\n throw new Error('Remote workspaces not enabled. Run `pan remote setup`');\n }\n\n const normalizedId = issueId.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n const branchName = `feature/${normalizedId}`;\n const infraVm = remoteConfig.exe?.infra_vm || 'pan-infra';\n const exe = createExeProvider({ infraVm });\n\n // Determine project context\n const teamPrefix = extractTeamPrefix(issueId);\n const projectConfig = teamPrefix ? findProjectByTeam(teamPrefix) : null;\n const projectRoot = projectConfig?.path || process.cwd();\n\n // Determine project identifier for VM name\n let projectId = teamPrefix?.toLowerCase();\n if (!projectId && projectConfig?.linear_team) {\n projectId = projectConfig.linear_team.toLowerCase();\n }\n if (!projectId) {\n try {\n const { stdout } = await execAsync('git remote get-url origin', {\n cwd: projectRoot,\n encoding: 'utf-8',\n });\n const repoMatch = stdout.trim().match(/\\/([^\\/]+?)(\\.git)?$/);\n projectId = repoMatch ? repoMatch[1].toLowerCase().replace(/[^a-z0-9-]/g, '-') : 'proj';\n } catch {\n projectId = 'proj';\n }\n }\n\n // VM names must be valid hostnames (start with letter, alphanumeric + hyphens)\n const vmName = `${projectId}-${normalizedId}-ws`.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n\n if (options.dryRun) {\n console.log(chalk.bold('Would create remote workspace:'));\n console.log(` VM: ${chalk.cyan(vmName)}`);\n console.log(` Project: ${chalk.dim(projectId)}`);\n console.log(` Infra: ${chalk.dim(infraVm)}`);\n console.log(` Branch: ${chalk.dim(branchName)}`);\n throw new Error('Dry run - not implemented in this module');\n }\n\n // Get git remote URL\n let repoUrl = '';\n try {\n const { stdout } = await execAsync('git remote get-url origin', {\n cwd: projectRoot,\n encoding: 'utf-8',\n });\n repoUrl = stdout.trim();\n } catch {\n throw new Error('Could not determine git remote URL. Make sure you are in a git repository with a remote origin.');\n }\n\n if (options.spinner) {\n options.spinner.text = 'Creating VM (this may take 1-2 minutes)...';\n }\n\n // Step 1: Create VM\n await exe.createVm(vmName);\n\n // Step 2: Add GitHub host key and clone repository on VM\n if (options.spinner) {\n options.spinner.text = 'Cloning repository on VM...';\n }\n await exe.ssh(vmName, 'mkdir -p ~/.ssh && ssh-keyscan -t ed25519,rsa github.com >> ~/.ssh/known_hosts 2>/dev/null');\n const cloneResult = await exe.ssh(vmName, `git clone ${repoUrl} ~/workspace`);\n if (cloneResult.exitCode !== 0) {\n await exe.deleteVm(vmName);\n throw new Error(`Failed to clone: ${cloneResult.stderr}`);\n }\n\n // Step 3: Create feature branch\n if (options.spinner) {\n options.spinner.text = 'Creating feature branch...';\n }\n const branchResult = await exe.ssh(vmName, `cd ~/workspace && git checkout -b ${branchName}`);\n if (branchResult.exitCode !== 0) {\n await exe.ssh(vmName, `cd ~/workspace && git checkout ${branchName} || git checkout -b ${branchName}`);\n }\n\n // Step 4: Configure environment for shared infra\n const envContent = `\n# Panopticon Remote Workspace\nWORKSPACE_ID=${normalizedId}\nISSUE_ID=${issueId.toUpperCase()}\n\n# Shared Infrastructure\nPOSTGRES_HOST=${infraVm}\nPOSTGRES_PORT=5432\nPOSTGRES_USER=postgres\nPOSTGRES_PASSWORD=\\${PAN_POSTGRES_PASSWORD:-panopticon}\nDATABASE_NAME=myn_${normalizedId.replace(/-/g, '_')}\n\nREDIS_HOST=${infraVm}\nREDIS_PORT=6379\nREDIS_DATABASE=0\n\n# Spring Boot (if applicable)\nSPRING_DATASOURCE_URL=jdbc:postgresql://${infraVm}:5432/myn_${normalizedId.replace(/-/g, '_')}\nSPRING_DATA_REDIS_HOST=${infraVm}\n`;\n\n await exe.ssh(vmName, `cat > ~/workspace/.env.remote << 'EOF'\n${envContent}\nEOF`);\n\n // Step 5: Create database on shared postgres\n const dbName = `myn_${normalizedId.replace(/-/g, '_')}`;\n try {\n await exe.ssh(infraVm, `docker exec pan-postgres psql -U postgres -c \"CREATE DATABASE ${dbName}\" 2>/dev/null || true`);\n } catch {\n // Non-fatal - database might already exist\n }\n\n // Step 6: Install beads CLI globally on remote VM\n if (options.spinner) {\n options.spinner.text = 'Installing beads CLI...';\n }\n const bdInstalled = await exe.installBeads(vmName);\n if (bdInstalled) {\n await exe.initBeads(vmName, '~/workspace');\n }\n\n // Step 6.5: Copy essential skills to remote VM\n if (options.spinner) {\n options.spinner.text = 'Copying skills to remote VM...';\n }\n await exe.copySkillsToVm(vmName);\n\n // Step 7: Start containers if docker compose exists\n let containersStarted = false;\n let frontendUrl = '';\n let apiUrl = '';\n\n const composeCheck = await exe.ssh(vmName, 'ls ~/workspace/docker-compose.yml ~/workspace/.devcontainer/docker-compose.yml 2>/dev/null | head -1');\n\n if (composeCheck.stdout.trim()) {\n if (options.spinner) {\n options.spinner.text = 'Starting containers...';\n }\n const composeDir = composeCheck.stdout.includes('.devcontainer')\n ? '~/workspace/.devcontainer'\n : '~/workspace';\n\n const upResult = await exe.ssh(vmName, `cd ${composeDir} && docker compose up -d 2>&1`);\n containersStarted = upResult.exitCode === 0;\n\n if (containersStarted) {\n if (options.spinner) {\n options.spinner.text = 'Exposing ports...';\n }\n try {\n frontendUrl = await exe.exposePort(vmName, 4173);\n apiUrl = await exe.exposePort(vmName, 7000);\n } catch {\n // Port exposure failed - not critical\n }\n }\n }\n\n // Step 8: Save workspace metadata\n const metadata: RemoteWorkspaceMetadata = {\n id: normalizedId,\n issue: issueId.toUpperCase(),\n provider: 'exe',\n vmName,\n infraVm,\n database: dbName,\n redisDb: 0,\n urls: {\n frontend: frontendUrl || undefined,\n api: apiUrl || undefined,\n },\n created: new Date(),\n location: 'remote',\n };\n\n saveWorkspaceMetadata(metadata);\n\n return metadata;\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getRecentRunLogs,
3
3
  init_specialist_logs
4
- } from "./chunk-42O6XJWZ.js";
4
+ } from "./chunk-2NIAOCIC.js";
5
5
  import {
6
6
  getModelId,
7
7
  init_work_type_router
@@ -253,4 +253,4 @@ export {
253
253
  regenerateContextDigest,
254
254
  scheduleDigestGeneration
255
255
  };
256
- //# sourceMappingURL=specialist-context-QVRSHPYN.js.map
256
+ //# sourceMappingURL=specialist-context-N32QBNNQ.js.map
@@ -16,7 +16,7 @@ import {
16
16
  isRunLogActive,
17
17
  listRunLogs,
18
18
  parseLogMetadata
19
- } from "./chunk-42O6XJWZ.js";
19
+ } from "./chunk-2NIAOCIC.js";
20
20
  import "./chunk-VIWUCJ4V.js";
21
21
  import "./chunk-BBCUK6N2.js";
22
22
  import "./chunk-JY7R7V4G.js";
@@ -41,4 +41,4 @@ export {
41
41
  listRunLogs,
42
42
  parseLogMetadata
43
43
  };
44
- //# sourceMappingURL=specialist-logs-7TQMHCUN.js.map
44
+ //# sourceMappingURL=specialist-logs-GF3YV4KL.js.map
@@ -55,7 +55,7 @@ import {
55
55
  wakeSpecialist,
56
56
  wakeSpecialistOrQueue,
57
57
  wakeSpecialistWithTask
58
- } from "./chunk-42O6XJWZ.js";
58
+ } from "./chunk-2NIAOCIC.js";
59
59
  import "./chunk-VIWUCJ4V.js";
60
60
  import "./chunk-BBCUK6N2.js";
61
61
  import "./chunk-JY7R7V4G.js";
@@ -119,4 +119,4 @@ export {
119
119
  wakeSpecialistOrQueue,
120
120
  wakeSpecialistWithTask
121
121
  };
122
- //# sourceMappingURL=specialists-UUXB5KFV.js.map
122
+ //# sourceMappingURL=specialists-JBIW6MP4.js.map
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  cleanupTemplateFiles,
3
3
  generatePanopticonTraefikConfig
4
- } from "./chunk-SIAUVHVO.js";
5
- import "./chunk-VTMXR7JF.js";
4
+ } from "./chunk-3XAB4IXF.js";
5
+ import "./chunk-VU4FLXV5.js";
6
6
  import "./chunk-6HXKTOD7.js";
7
7
  import "./chunk-ZHC57RCV.js";
8
8
  export {
9
9
  cleanupTemplateFiles,
10
10
  generatePanopticonTraefikConfig
11
11
  };
12
- //# sourceMappingURL=traefik-7OLLXUD7.js.map
12
+ //# sourceMappingURL=traefik-CUJM6K5Z.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panopticon-cli",
3
- "version": "0.4.30",
3
+ "version": "0.4.32",
4
4
  "description": "Multi-agent orchestration for AI coding assistants (Claude Code, Codex, Cursor, Gemini CLI)",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -48,8 +48,9 @@
48
48
  },
49
49
  "scripts": {
50
50
  "dev": "tsx watch src/cli/index.ts",
51
- "build": "npm run build:cli && npm run build:dashboard",
51
+ "build": "npm run build:cli && npm run build:scripts && npm run build:dashboard",
52
52
  "build:cli": "tsup",
53
+ "build:scripts": "esbuild scripts/record-cost-event.ts --bundle --platform=node --format=esm --outfile=scripts/record-cost-event.js",
53
54
  "build:dashboard": "npm run build:dashboard:frontend && npm run build:dashboard:server",
54
55
  "build:dashboard:frontend": "cd src/dashboard/frontend && npm run build",
55
56
  "build:dashboard:server": "cd src/dashboard/server && npm run build",
@@ -1,94 +1,258 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * Record a cost event from Claude Code tool usage
4
- * Called by heartbeat-hook with JSON input on stdin
5
- */
6
2
 
7
- import { readFileSync } from 'fs';
8
- import { calculateCost, getPricing } from '../src/lib/cost.js';
9
- import { appendCostEvent } from '../src/lib/costs/events.js';
3
+ // scripts/record-cost-event.ts
4
+ import { readFileSync as readFileSync2, existsSync as existsSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, openSync, readSync, fstatSync, closeSync } from "fs";
5
+ import { join as join4 } from "path";
6
+ import { homedir as homedir3 } from "os";
10
7
 
11
- // Read tool info from stdin
12
- let toolInfo;
13
- try {
14
- const input = readFileSync(0, 'utf-8');
15
- toolInfo = JSON.parse(input);
16
- } catch (err) {
17
- // Silent failure - don't break Claude Code execution
18
- process.exit(0);
19
- }
8
+ // src/lib/cost.ts
9
+ import { join as join2 } from "path";
20
10
 
21
- // Extract usage data from tool info
22
- const usage = toolInfo?.usage || toolInfo?.message?.usage;
23
- if (!usage) {
24
- // No usage data - not a Claude API call
25
- process.exit(0);
11
+ // src/lib/paths.ts
12
+ import { homedir } from "os";
13
+ import { join } from "path";
14
+ import { fileURLToPath } from "url";
15
+ import { dirname } from "path";
16
+ var PANOPTICON_HOME = process.env.PANOPTICON_HOME || join(homedir(), ".panopticon");
17
+ var CONFIG_DIR = PANOPTICON_HOME;
18
+ var SKILLS_DIR = join(PANOPTICON_HOME, "skills");
19
+ var COMMANDS_DIR = join(PANOPTICON_HOME, "commands");
20
+ var AGENTS_DIR = join(PANOPTICON_HOME, "agents");
21
+ var BIN_DIR = join(PANOPTICON_HOME, "bin");
22
+ var BACKUPS_DIR = join(PANOPTICON_HOME, "backups");
23
+ var COSTS_DIR = join(PANOPTICON_HOME, "costs");
24
+ var HEARTBEATS_DIR = join(PANOPTICON_HOME, "heartbeats");
25
+ var TRAEFIK_DIR = join(PANOPTICON_HOME, "traefik");
26
+ var TRAEFIK_DYNAMIC_DIR = join(TRAEFIK_DIR, "dynamic");
27
+ var TRAEFIK_CERTS_DIR = join(TRAEFIK_DIR, "certs");
28
+ var CERTS_DIR = join(PANOPTICON_HOME, "certs");
29
+ var CONFIG_FILE = join(CONFIG_DIR, "config.toml");
30
+ var SETTINGS_FILE = join(CONFIG_DIR, "settings.json");
31
+ var CLAUDE_DIR = join(homedir(), ".claude");
32
+ var CODEX_DIR = join(homedir(), ".codex");
33
+ var CURSOR_DIR = join(homedir(), ".cursor");
34
+ var GEMINI_DIR = join(homedir(), ".gemini");
35
+ var OPENCODE_DIR = join(homedir(), ".opencode");
36
+ var SYNC_TARGETS = {
37
+ claude: {
38
+ skills: join(CLAUDE_DIR, "skills"),
39
+ commands: join(CLAUDE_DIR, "commands"),
40
+ agents: join(CLAUDE_DIR, "agents")
41
+ },
42
+ codex: {
43
+ skills: join(CODEX_DIR, "skills"),
44
+ commands: join(CODEX_DIR, "commands"),
45
+ agents: join(CODEX_DIR, "agents")
46
+ },
47
+ cursor: {
48
+ skills: join(CURSOR_DIR, "skills"),
49
+ commands: join(CURSOR_DIR, "commands"),
50
+ agents: join(CURSOR_DIR, "agents")
51
+ },
52
+ gemini: {
53
+ skills: join(GEMINI_DIR, "skills"),
54
+ commands: join(GEMINI_DIR, "commands"),
55
+ agents: join(GEMINI_DIR, "agents")
56
+ },
57
+ opencode: {
58
+ skills: join(OPENCODE_DIR, "skills"),
59
+ commands: join(OPENCODE_DIR, "commands"),
60
+ agents: join(OPENCODE_DIR, "agents")
61
+ }
62
+ };
63
+ var TEMPLATES_DIR = join(PANOPTICON_HOME, "templates");
64
+ var CLAUDE_MD_TEMPLATES = join(TEMPLATES_DIR, "claude-md", "sections");
65
+ var currentFile = fileURLToPath(import.meta.url);
66
+ var currentDir = dirname(currentFile);
67
+ var packageRoot;
68
+ if (currentDir.includes("/src/")) {
69
+ packageRoot = dirname(dirname(currentDir));
70
+ } else {
71
+ packageRoot = currentDir.endsWith("/lib") ? dirname(dirname(currentDir)) : dirname(currentDir);
26
72
  }
73
+ var SOURCE_TEMPLATES_DIR = join(packageRoot, "templates");
74
+ var SOURCE_TRAEFIK_TEMPLATES = join(SOURCE_TEMPLATES_DIR, "traefik");
75
+ var SOURCE_SCRIPTS_DIR = join(packageRoot, "scripts");
76
+ var SOURCE_SKILLS_DIR = join(packageRoot, "skills");
77
+ var SOURCE_DEV_SKILLS_DIR = join(packageRoot, "dev-skills");
27
78
 
28
- // Extract token counts
29
- const inputTokens = usage.input_tokens || 0;
30
- const outputTokens = usage.output_tokens || 0;
31
- const cacheReadTokens = usage.cache_read_input_tokens || 0;
32
- const cacheWriteTokens = usage.cache_creation_input_tokens || 0;
33
-
34
- // Must have at least some tokens to record
35
- if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === 0 && cacheWriteTokens === 0) {
36
- process.exit(0);
79
+ // src/lib/cost.ts
80
+ var DEFAULT_PRICING = [
81
+ // Anthropic - 4.6 series
82
+ { provider: "anthropic", model: "claude-opus-4.6", inputPer1k: 5e-3, outputPer1k: 0.025, cacheReadPer1k: 5e-4, cacheWrite5mPer1k: 625e-5, cacheWrite1hPer1k: 0.01, currency: "USD" },
83
+ { provider: "anthropic", model: "claude-sonnet-4.5", inputPer1k: 3e-3, outputPer1k: 0.015, cacheReadPer1k: 3e-4, cacheWrite5mPer1k: 375e-5, cacheWrite1hPer1k: 6e-3, currency: "USD" },
84
+ { provider: "anthropic", model: "claude-haiku-4.5", inputPer1k: 1e-3, outputPer1k: 5e-3, cacheReadPer1k: 1e-4, cacheWrite5mPer1k: 125e-5, cacheWrite1hPer1k: 2e-3, currency: "USD" },
85
+ // Anthropic - 4.x series
86
+ { provider: "anthropic", model: "claude-opus-4-1", inputPer1k: 0.015, outputPer1k: 0.075, cacheReadPer1k: 15e-4, cacheWrite5mPer1k: 0.01875, cacheWrite1hPer1k: 0.03, currency: "USD" },
87
+ { provider: "anthropic", model: "claude-opus-4", inputPer1k: 0.015, outputPer1k: 0.075, cacheReadPer1k: 15e-4, cacheWrite5mPer1k: 0.01875, cacheWrite1hPer1k: 0.03, currency: "USD" },
88
+ { provider: "anthropic", model: "claude-sonnet-4", inputPer1k: 3e-3, outputPer1k: 0.015, cacheReadPer1k: 3e-4, cacheWrite5mPer1k: 375e-5, cacheWrite1hPer1k: 6e-3, currency: "USD" },
89
+ // Anthropic - Legacy
90
+ { provider: "anthropic", model: "claude-haiku-3", inputPer1k: 25e-5, outputPer1k: 125e-5, cacheReadPer1k: 3e-5, cacheWrite5mPer1k: 3e-4, cacheWrite1hPer1k: 5e-4, currency: "USD" },
91
+ // OpenAI
92
+ { provider: "openai", model: "gpt-4-turbo", inputPer1k: 0.01, outputPer1k: 0.03, currency: "USD" },
93
+ { provider: "openai", model: "gpt-4o", inputPer1k: 5e-3, outputPer1k: 0.015, currency: "USD" },
94
+ { provider: "openai", model: "gpt-4o-mini", inputPer1k: 15e-5, outputPer1k: 6e-4, currency: "USD" },
95
+ // Google
96
+ { provider: "google", model: "gemini-1.5-pro", inputPer1k: 125e-5, outputPer1k: 5e-3, currency: "USD" },
97
+ { provider: "google", model: "gemini-1.5-flash", inputPer1k: 75e-6, outputPer1k: 3e-4, currency: "USD" },
98
+ // Moonshot AI (Kimi)
99
+ { provider: "custom", model: "kimi-for-coding", inputPer1k: 6e-4, outputPer1k: 2e-3, cacheReadPer1k: 6e-5, cacheWrite5mPer1k: 75e-5, currency: "USD" },
100
+ { provider: "custom", model: "kimi-k2.5", inputPer1k: 6e-4, outputPer1k: 2e-3, cacheReadPer1k: 6e-5, cacheWrite5mPer1k: 75e-5, currency: "USD" }
101
+ ];
102
+ function calculateCost(usage, pricing) {
103
+ let cost = 0;
104
+ let inputMultiplier = 1;
105
+ let outputMultiplier = 1;
106
+ const totalInputTokens = usage.inputTokens + (usage.cacheReadTokens || 0) + (usage.cacheWriteTokens || 0);
107
+ if ((pricing.model === "claude-sonnet-4" || pricing.model === "claude-sonnet-4.5") && totalInputTokens > 2e5) {
108
+ inputMultiplier = 2;
109
+ outputMultiplier = 1.5;
110
+ }
111
+ cost += usage.inputTokens / 1e3 * pricing.inputPer1k * inputMultiplier;
112
+ cost += usage.outputTokens / 1e3 * pricing.outputPer1k * outputMultiplier;
113
+ if (usage.cacheReadTokens && pricing.cacheReadPer1k) {
114
+ cost += usage.cacheReadTokens / 1e3 * pricing.cacheReadPer1k;
115
+ }
116
+ if (usage.cacheWriteTokens) {
117
+ const ttl = usage.cacheTTL || "5m";
118
+ const cacheWritePrice = ttl === "1h" ? pricing.cacheWrite1hPer1k : pricing.cacheWrite5mPer1k;
119
+ if (cacheWritePrice) {
120
+ cost += usage.cacheWriteTokens / 1e3 * cacheWritePrice;
121
+ }
122
+ }
123
+ return Math.round(cost * 1e6) / 1e6;
37
124
  }
125
+ function getPricing(provider, model) {
126
+ let pricing = DEFAULT_PRICING.find(
127
+ (p) => p.provider === provider && p.model === model
128
+ );
129
+ if (!pricing) {
130
+ pricing = DEFAULT_PRICING.find(
131
+ (p) => p.provider === provider && model.startsWith(p.model)
132
+ );
133
+ }
134
+ return pricing || null;
135
+ }
136
+ var BUDGETS_FILE = join2(COSTS_DIR, "budgets.json");
38
137
 
39
- // Extract model name
40
- const model = toolInfo?.model || toolInfo?.message?.model || 'claude-sonnet-4';
41
-
42
- // Determine provider from model name
43
- let provider = 'anthropic';
44
- if (model.includes('gpt')) {
45
- provider = 'openai';
46
- } else if (model.includes('gemini')) {
47
- provider = 'google';
138
+ // src/lib/costs/events.ts
139
+ import { existsSync, mkdirSync, readFileSync, appendFileSync, writeFileSync, renameSync } from "fs";
140
+ import { join as join3 } from "path";
141
+ import { homedir as homedir2 } from "os";
142
+ function getCostsDir() {
143
+ return join3(process.env.HOME || homedir2(), ".panopticon", "costs");
144
+ }
145
+ function getEventsFile() {
146
+ return join3(getCostsDir(), "events.jsonl");
147
+ }
148
+ function ensureEventsFile() {
149
+ const costsDir = getCostsDir();
150
+ const eventsFile = getEventsFile();
151
+ mkdirSync(costsDir, { recursive: true });
152
+ if (!existsSync(eventsFile)) {
153
+ writeFileSync(eventsFile, "", "utf-8");
154
+ }
155
+ }
156
+ function appendCostEvent(event2) {
157
+ ensureEventsFile();
158
+ if (!event2.ts || !event2.agentId || !event2.issueId || !event2.model) {
159
+ throw new Error("Missing required event fields: ts, agentId, issueId, model");
160
+ }
161
+ const line = JSON.stringify(event2) + "\n";
162
+ appendFileSync(getEventsFile(), line, "utf-8");
48
163
  }
49
164
 
50
- // Get pricing and calculate cost
51
- const pricing = getPricing(provider, model);
52
- if (!pricing) {
53
- console.warn(`No pricing found for ${provider}/${model}`);
165
+ // scripts/record-cost-event.ts
166
+ var event;
167
+ try {
168
+ const input = readFileSync2(0, "utf-8");
169
+ event = JSON.parse(input);
170
+ } catch {
54
171
  process.exit(0);
55
172
  }
56
-
57
- const cost = calculateCost({
58
- inputTokens,
59
- outputTokens,
60
- cacheReadTokens,
61
- cacheWriteTokens,
62
- cacheTTL: '5m',
63
- }, pricing);
64
-
65
- // Get agent and issue context from environment
66
- // PANOPTICON_AGENT_ID should always be set by pan work or heartbeat-hook
67
- // If not set, use a fallback that makes it clear costs are unattributed
68
- const agentId = process.env.PANOPTICON_AGENT_ID || 'unattributed';
69
-
70
- const issueId = process.env.PANOPTICON_ISSUE_ID || 'UNKNOWN';
71
- const sessionType = process.env.PANOPTICON_SESSION_TYPE || 'implementation';
72
-
73
- // Record cost event
173
+ var transcriptPath = event?.transcript_path;
174
+ if (!transcriptPath || !existsSync2(transcriptPath)) {
175
+ process.exit(0);
176
+ }
177
+ var sessionId = event?.session_id || "unknown";
178
+ var stateDir = join4(process.env.HOME || homedir3(), ".panopticon", "costs", "state");
179
+ mkdirSync2(stateDir, { recursive: true });
180
+ var stateFile = join4(stateDir, `${sessionId}.offset`);
181
+ var lastOffset = 0;
182
+ if (existsSync2(stateFile)) {
183
+ try {
184
+ lastOffset = parseInt(readFileSync2(stateFile, "utf-8").trim(), 10) || 0;
185
+ } catch {
186
+ }
187
+ }
188
+ var fd;
74
189
  try {
75
- appendCostEvent({
76
- ts: new Date().toISOString(),
77
- type: 'cost',
78
- agentId,
79
- issueId,
80
- sessionType,
81
- provider,
82
- model,
83
- input: inputTokens,
84
- output: outputTokens,
85
- cacheRead: cacheReadTokens,
86
- cacheWrite: cacheWriteTokens,
87
- cost,
88
- });
89
- } catch (err) {
90
- // Silent failure - don't break Claude Code execution
91
- console.error('Failed to record cost event:', err);
190
+ fd = openSync(transcriptPath, "r");
191
+ } catch {
192
+ process.exit(0);
92
193
  }
93
-
194
+ var stat = fstatSync(fd);
195
+ if (stat.size <= lastOffset) {
196
+ closeSync(fd);
197
+ writeFileSync2(stateFile, String(stat.size), "utf-8");
198
+ process.exit(0);
199
+ }
200
+ var bytesToRead = stat.size - lastOffset;
201
+ var buffer = Buffer.alloc(bytesToRead);
202
+ readSync(fd, buffer, 0, bytesToRead, lastOffset);
203
+ closeSync(fd);
204
+ var newContent = buffer.toString("utf-8");
205
+ var lines = newContent.split("\n");
206
+ var agentId = process.env.PANOPTICON_AGENT_ID || "unattributed";
207
+ var issueId = process.env.PANOPTICON_ISSUE_ID || "UNKNOWN";
208
+ var sessionType = process.env.PANOPTICON_SESSION_TYPE || "implementation";
209
+ for (const line of lines) {
210
+ if (!line.trim()) continue;
211
+ try {
212
+ const entry = JSON.parse(line);
213
+ if (entry.type !== "assistant" || !entry.message?.usage) {
214
+ continue;
215
+ }
216
+ const usage = entry.message.usage;
217
+ const model = entry.message.model || "claude-sonnet-4";
218
+ const inputTokens = usage.input_tokens || 0;
219
+ const outputTokens = usage.output_tokens || 0;
220
+ const cacheReadTokens = usage.cache_read_input_tokens || 0;
221
+ const cacheWriteTokens = usage.cache_creation_input_tokens || 0;
222
+ if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === 0 && cacheWriteTokens === 0) {
223
+ continue;
224
+ }
225
+ let provider = "anthropic";
226
+ if (model.includes("gpt")) {
227
+ provider = "openai";
228
+ } else if (model.includes("gemini")) {
229
+ provider = "google";
230
+ }
231
+ const pricing = getPricing(provider, model);
232
+ if (!pricing) continue;
233
+ const cost = calculateCost({
234
+ inputTokens,
235
+ outputTokens,
236
+ cacheReadTokens,
237
+ cacheWriteTokens,
238
+ cacheTTL: "5m"
239
+ }, pricing);
240
+ appendCostEvent({
241
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
242
+ type: "cost",
243
+ agentId,
244
+ issueId,
245
+ sessionType,
246
+ provider,
247
+ model,
248
+ input: inputTokens,
249
+ output: outputTokens,
250
+ cacheRead: cacheReadTokens,
251
+ cacheWrite: cacheWriteTokens,
252
+ cost
253
+ });
254
+ } catch {
255
+ }
256
+ }
257
+ writeFileSync2(stateFile, String(stat.size), "utf-8");
94
258
  process.exit(0);