@rulemetric/cli 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-GQCISMAH.js → chunk-2BCPVUSF.js} +6 -6
- package/dist/{chunk-7FAA3YNB.js → chunk-42A3HEEH.js} +50 -3
- package/dist/chunk-42A3HEEH.js.map +7 -0
- package/dist/{chunk-RJ7BHDLM.js → chunk-46LMTDRE.js} +2 -2
- package/dist/{chunk-B5KNTGMJ.js → chunk-4QGATD5Y.js} +209 -2
- package/dist/chunk-4QGATD5Y.js.map +7 -0
- package/dist/{chunk-XPUEGHXN.js → chunk-EHABPBGH.js} +4 -4
- package/dist/{chunk-EOOP6WTC.js → chunk-FUS5C4SB.js} +2 -2
- package/dist/chunk-JFDWKLQC.js +23 -0
- package/dist/chunk-JFDWKLQC.js.map +7 -0
- package/dist/{chunk-PHLMIZKM.js → chunk-JNFIYBFL.js} +30 -5
- package/dist/chunk-JNFIYBFL.js.map +7 -0
- package/dist/{chunk-WFULKPEQ.js → chunk-LCT6MT2V.js} +12 -12
- package/dist/{chunk-R5MFHGHR.js → chunk-MTLAIJED.js} +3 -3
- package/dist/{chunk-NIDLJO6F.js → chunk-QSN77T7C.js} +23 -1
- package/dist/{chunk-NIDLJO6F.js.map → chunk-QSN77T7C.js.map} +2 -2
- package/dist/{chunk-4WFE4PFP.js → chunk-XMWHTOCC.js} +3 -3
- package/dist/{chunk-AULP2OWH.js → chunk-Y2ARLMWZ.js} +6 -6
- package/dist/{chunk-IGXX624H.js → chunk-YEBBX4HQ.js} +2 -2
- package/dist/commands/crons/list.js +3 -13
- package/dist/commands/crons/list.js.map +2 -2
- package/dist/commands/crons/run.js +16 -14
- package/dist/commands/crons/run.js.map +2 -2
- package/dist/commands/evals/agent.js +19 -21
- package/dist/commands/evals/agent.js.map +2 -2
- package/dist/commands/hooks/install.js +2 -2
- package/dist/commands/service/install.js +180 -11
- package/dist/commands/service/install.js.map +2 -2
- package/dist/commands/service/uninstall.js +33 -2
- package/dist/commands/service/uninstall.js.map +2 -2
- package/dist/commands/suggestions/accept.js +3 -3
- package/dist/commands/suggestions/dismiss.js +3 -3
- package/dist/dashboard/Dashboard.js +3 -3
- package/dist/dashboard/data.js +2 -2
- package/dist/{dist-CSUYFYKI.js → dist-NEECHU47.js} +6 -2
- package/dist/lib/agent-loop.js +17 -17
- package/dist/lib/handlers/process-announcement.js +3 -3
- package/dist/lib/handlers/process-changelog.js +3 -3
- package/dist/lib/handlers/process-insights.js +2 -2
- package/dist/lib/handlers/process-launch.js +3 -3
- package/dist/lib/handlers/process-run-cleanup.js +4 -4
- package/dist/lib/handlers/process-session-goal.js +2 -2
- package/dist/lib/manual-tasks-meta.js +8 -0
- package/dist/lib/manual-tasks-meta.js.map +7 -0
- package/dist/lib/manual-tasks.js +65 -8
- package/dist/lib/manual-tasks.js.map +3 -3
- package/dist/lib/statusline-shim.js +1 -1
- package/dist/lib/worktree.js +2 -2
- package/oclif.manifest.json +11 -4
- package/package.json +18 -15
- package/dist/chunk-7FAA3YNB.js.map +0 -7
- package/dist/chunk-B5KNTGMJ.js.map +0 -7
- package/dist/chunk-DU73QUS3.js +0 -75
- package/dist/chunk-DU73QUS3.js.map +0 -7
- package/dist/chunk-PHLMIZKM.js.map +0 -7
- /package/dist/{chunk-GQCISMAH.js.map → chunk-2BCPVUSF.js.map} +0 -0
- /package/dist/{chunk-RJ7BHDLM.js.map → chunk-46LMTDRE.js.map} +0 -0
- /package/dist/{chunk-XPUEGHXN.js.map → chunk-EHABPBGH.js.map} +0 -0
- /package/dist/{chunk-EOOP6WTC.js.map → chunk-FUS5C4SB.js.map} +0 -0
- /package/dist/{chunk-WFULKPEQ.js.map → chunk-LCT6MT2V.js.map} +0 -0
- /package/dist/{chunk-R5MFHGHR.js.map → chunk-MTLAIJED.js.map} +0 -0
- /package/dist/{chunk-4WFE4PFP.js.map → chunk-XMWHTOCC.js.map} +0 -0
- /package/dist/{chunk-AULP2OWH.js.map → chunk-Y2ARLMWZ.js.map} +0 -0
- /package/dist/{chunk-IGXX624H.js.map → chunk-YEBBX4HQ.js.map} +0 -0
- /package/dist/{dist-CSUYFYKI.js.map → dist-NEECHU47.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/commands/service/install.ts"],
|
|
4
|
-
"sourcesContent": ["import { Flags } from '@oclif/core';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { execSync, execFileSync, spawnSync } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\nimport { bootstrapActiveOrg } from '../../lib/active-org-refresh.js';\nimport { detectTmux } from '../../lib/detect-tmux.js';\n\n// Only these env vars are written to the plist \u2014 never leak unrelated secrets\nconst ALLOWED_ENV_VARS = new Set([\n 'SUPABASE_URL',\n 'SUPABASE_ANON_KEY',\n 'SUPABASE_SERVICE_ROLE_KEY',\n // Enables local HS256 JWT verification in the API's authMiddleware \u2014\n // without it every authed request pays a /auth/v1/user network round-trip\n // (measured p50 ~244ms) to Supabase.\n 'SUPABASE_JWT_SECRET',\n 'DATABASE_URL',\n 'RULEMETRIC_API_URL',\n 'RULEMETRIC_API_KEY',\n 'RULEMETRIC_ACCESS_TOKEN',\n // Recipient for the weekly cron_data_integrity_audit notification.\n // Without one of these, the audit cron runs but nobody is notified \u2014\n // see cron-data-integrity-audit.ts (defaults: NOTIFY_USER_ID then USER_ID).\n 'RULEMETRIC_AUDIT_NOTIFY_USER_ID',\n 'RULEMETRIC_USER_ID',\n // cron_refresh_skills needs an authenticated GitHub API budget (5000\n // req/hr) \u2014 without it the handler skips and the skills registry goes\n // stale, which makes cron_suggest_instructions mark every suggestion\n // stale on each nightly run.\n 'GITHUB_TOKEN',\n // Pool-tuning overrides so the plist generators (buildPlist / buildWorkerPlist)\n // actually honor a user-set value instead of always falling back to the pinned\n // default \u2014 without these in the allow-list the `envVars.PG_* ?? '<default>'`\n // escape hatch was silently dead. Pool-exhaustion RCA 2026-06-21.\n 'PG_POOL_MAX',\n 'PG_IDLE_TIMEOUT',\n 'PG_STATEMENT_TIMEOUT_MS',\n]);\n\nconst PLIST_LABEL = 'com.rulemetric.api';\nconst PLIST_PATH = join(homedir(), 'Library', 'LaunchAgents', `${PLIST_LABEL}.plist`);\nconst WORKER_PLIST_LABEL = 'com.rulemetric.worker';\nconst WORKER_PLIST_PATH = join(homedir(), 'Library', 'LaunchAgents', `${WORKER_PLIST_LABEL}.plist`);\nconst GATEWAY_PLIST_LABEL = 'com.rulemetric.gateway';\nconst GATEWAY_PLIST_PATH = join(homedir(), 'Library', 'LaunchAgents', `${GATEWAY_PLIST_LABEL}.plist`);\nconst PROXY_PLIST_LABEL = 'com.rulemetric.proxy';\nconst PROXY_PLIST_PATH = join(homedir(), 'Library', 'LaunchAgents', `${PROXY_PLIST_LABEL}.plist`);\n// Daily kickstart agent \u2014 see Phase 4c cleanup block below. Kept as a constant\n// so we can tear it down on every install if a stale copy is lying around.\nconst WORKER_RESTART_PLIST_LABEL = 'com.rulemetric.worker.restart';\nconst WORKER_RESTART_PLIST_PATH = join(homedir(), 'Library', 'LaunchAgents', `${WORKER_RESTART_PLIST_LABEL}.plist`);\nconst CONFIG_DIR = join(homedir(), '.config', 'rulemetric');\nconst LOG_DIR = join(CONFIG_DIR, 'logs');\nconst GATEWAY_PORT = 8787;\nconst PROXY_PORT = 8788;\n\nexport default class ServiceInstall extends BaseCommand {\n static override description = 'Install RuleMetric API, worker, gateway, and capture proxy as persistent macOS launchd services';\n\n static override examples = [\n '<%= config.bin %> service install',\n '<%= config.bin %> service install --port 3000',\n '<%= config.bin %> service install --skip-build',\n '<%= config.bin %> service install --no-proxy',\n ];\n\n static override flags = {\n port: Flags.integer({\n char: 'p',\n default: 3000,\n description: 'API server port',\n }),\n force: Flags.boolean({\n char: 'f',\n default: false,\n description: 'Overwrite existing service configuration and reclaim conflicting ports',\n }),\n 'skip-build': Flags.boolean({\n default: false,\n description: 'Skip the pre-install workspace build (API/web/cli dist must already exist)',\n }),\n 'no-proxy': Flags.boolean({\n default: false,\n description: 'Skip gateway + mitmproxy plists (just install API + worker)',\n }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(ServiceInstall);\n const port = flags.port;\n const skipBuild = flags['skip-build'];\n const noProxy = flags['no-proxy'];\n\n // macOS only\n if (process.platform !== 'darwin') {\n this.error('Service install is currently macOS-only (launchd). For Linux, use systemd manually.');\n }\n\n // Existing-service guard. Presence of the API plist implies a prior\n // install \u2014 we only proceed automatically when --force is set so a\n // user doesn't accidentally overwrite a working config.\n if (existsSync(PLIST_PATH) && !flags.force) {\n this.log(`Service already installed at ${PLIST_PATH}`);\n this.log('Use --force to overwrite, or run: rulemetric service status');\n return;\n }\n\n // API port conflict. Without --force we bail; with --force we'll reclaim\n // the port later in killStaleProcess so launchd can actually bind it.\n try {\n const res = await fetch(`http://localhost:${port}/health`);\n if (res.ok && !flags.force) {\n this.warn(`Port ${port} is already in use (API may be running via start.sh or in another terminal).`);\n this.warn('Stop it first with scripts/stop.sh, or use --force to take over the port.');\n return;\n }\n } catch {\n // Port free \u2014 good\n }\n\n // Find project root\n const projectRoot = this.findProjectRoot();\n if (!projectRoot) {\n this.error(\n 'Could not find RuleMetric project root. Run this command from within the momento-mori repo, or set RULEMETRIC_PROJECT_ROOT.',\n );\n }\n\n // Verify .env.local\n const envLocal = join(projectRoot, '.env.local');\n if (!existsSync(envLocal)) {\n this.error(`.env.local not found at ${envLocal}. The API needs Supabase credentials to start.`);\n }\n\n // Load env vars (filtered by ALLOWED_ENV_VARS)\n const envVars = this.guardAlwaysOnDatabaseUrl(this.loadEnvVars(projectRoot));\n\n // Resolve binaries\n const nodePath = this.findBinary('node');\n const pnpmPath = this.findBinary('pnpm');\n if (!nodePath || !pnpmPath) {\n this.error('Could not find node or pnpm. Ensure they are in your PATH.');\n }\n\n // Resolve mitmdump (required unless --no-proxy). We resolve via `which`\n // so the plist gets an absolute path \u2014 launchd's PATH at GUI session\n // start can be narrow, and pyenv shims often don't appear in it.\n let mitmdumpPath: string | null = null;\n if (!noProxy) {\n mitmdumpPath = this.findBinary('mitmdump');\n if (!mitmdumpPath) {\n this.error(\n 'mitmdump not found in PATH. Install with: pipx install mitmproxy (or brew install mitmproxy), then re-run.\\n' +\n 'Or skip the capture proxy with --no-proxy.',\n );\n }\n }\n\n // \u2500\u2500 Pre-install build \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Mirrors the Dockerfile prod stage: API + web + CLI all need their\n // dist/ directories to exist before launchd starts the processes. We\n // run the whole set via turbo so the cache eats the cost on re-installs.\n if (!skipBuild) {\n this.log('[..] Building workspace artifacts (api, web, cli) via turbo...');\n const buildFilters = [\n '--filter', '@rulemetric/api...',\n '--filter', '@rulemetric/web...',\n '--filter', '@rulemetric/cli',\n ];\n const build = spawnSync(pnpmPath!, ['exec', 'turbo', 'run', 'build', ...buildFilters], {\n cwd: projectRoot,\n stdio: 'inherit',\n });\n if (build.status !== 0) {\n this.error(\n `Workspace build failed (exit ${build.status}). Fix the build first, or use --skip-build if dist/ already exists.`,\n );\n }\n this.log('[OK] Build complete');\n } else {\n this.log('[..] Skipping pre-install build (--skip-build)');\n }\n\n // Verify the build artifacts launchd is about to invoke. A missing\n // entry would crash-loop the plist; surfacing it here is friendlier.\n const apiEntry = join(projectRoot, 'apps', 'api', 'dist', 'index.js');\n if (!existsSync(apiEntry)) {\n this.error(`API entry not found at ${apiEntry}. Re-run without --skip-build, or: pnpm --filter @rulemetric/api... build`);\n }\n const webIndex = join(projectRoot, 'apps', 'web', 'dist', 'index.html');\n if (!existsSync(webIndex)) {\n this.warn(`Web bundle not found at ${webIndex}. Dashboard at :${port} will 404 until you build apps/web.`);\n }\n if (!noProxy) {\n const gatewayEntry = join(projectRoot, 'apps', 'cli', 'dist', 'lib', 'gateway-entry.js');\n if (!existsSync(gatewayEntry)) {\n this.error(`Gateway entry not found at ${gatewayEntry}. Re-run without --skip-build, or: pnpm --filter @rulemetric/cli build`);\n }\n }\n\n // \u2500\u2500 Clean up conflicting PID-file-managed or orphan processes \u2500\n // hooks install + proxy start use PID files; we replace those with\n // launchd-managed lifecycles so both ports need to be free before\n // bootstrap. With --force we also clear whatever is squatting on\n // the API port.\n if (flags.force) {\n this.killStaleProcess(port, null, 'API on :' + port);\n }\n if (!noProxy) {\n this.killStaleProcess(GATEWAY_PORT, join(CONFIG_DIR, 'gateway.pid'), 'gateway');\n this.killStaleProcess(PROXY_PORT, join(CONFIG_DIR, 'proxy.pid'), 'mitmproxy');\n }\n\n // Create dirs\n mkdirSync(join(homedir(), 'Library', 'LaunchAgents'), { recursive: true });\n mkdirSync(LOG_DIR, { recursive: true });\n\n const uid = execSync('id -u', { encoding: 'utf-8' }).trim();\n\n // \u2500\u2500 Install API service \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const apiPlist = this.buildPlist({ port, projectRoot, nodePath: nodePath!, pnpmPath: pnpmPath!, envVars });\n this.bootoutAndWrite(uid, PLIST_PATH, apiPlist, 'API');\n this.bootstrapPlist(uid, PLIST_PATH, 'API');\n\n // Wait for API to come up\n this.log(`[..] Waiting for API on :${port}...`);\n const apiStarted = await this.waitForApi(port, 15);\n if (apiStarted) {\n this.log(`[OK] RuleMetric API running on :${port}`);\n if (existsSync(webIndex)) {\n this.log(`[OK] Dashboard available at http://localhost:${port}`);\n }\n } else {\n this.warn('API did not respond within 15s. Check logs:');\n this.log(` tail -f ${LOG_DIR}/api-stdout.log`);\n this.log(` tail -f ${LOG_DIR}/api-stderr.log`);\n }\n\n // \u2500\u2500 Install worker service \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n this.log('');\n this.log('Installing worker service...');\n // Reap detached, unsupervised evals-agent orphans before bootstrapping the\n // worker \u2014 they hold no listening port, so killStaleProcess can't find them\n // (a fleet of 4 ran 3+ days and helped saturate the pool). RCA 2026-06-21.\n this.killStaleAgents();\n const workerPlist = this.buildWorkerPlist({ projectRoot, nodePath: nodePath!, pnpmPath: pnpmPath!, envVars });\n this.bootoutAndWrite(uid, WORKER_PLIST_PATH, workerPlist, 'Worker');\n this.bootstrapPlist(uid, WORKER_PLIST_PATH, 'Worker');\n\n // \u2500\u2500 Install gateway + capture-proxy services \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (!noProxy) {\n this.log('');\n this.log('Installing gateway + capture proxy services...');\n\n const gatewayPlist = this.buildGatewayPlist({ projectRoot, nodePath: nodePath! });\n this.bootoutAndWrite(uid, GATEWAY_PLIST_PATH, gatewayPlist, 'Gateway');\n this.bootstrapPlist(uid, GATEWAY_PLIST_PATH, 'Gateway');\n\n const proxyPlist = this.buildProxyPlist({\n projectRoot,\n mitmdumpPath: mitmdumpPath!,\n envVars,\n });\n this.bootoutAndWrite(uid, PROXY_PLIST_PATH, proxyPlist, 'Proxy');\n this.bootstrapPlist(uid, PROXY_PLIST_PATH, 'Proxy');\n }\n\n // Phase 4c cleanup: the daily worker-restart agent (04:00 kickstart)\n // existed to dodge the graphile-LISTEN-wedge class observed at ~25h\n // worker uptime. The agent path has no LISTEN socket, so the wedge\n // class is structurally gone \u2014 the daily kickstart is dead weight.\n // Bootout any leftover plist from prior installs so reinstall is a\n // truly clean state.\n if (existsSync(WORKER_RESTART_PLIST_PATH)) {\n try {\n execFileSync('launchctl', ['bootout', `gui/${uid}`, WORKER_RESTART_PLIST_PATH], { encoding: 'utf-8' });\n } catch {\n // Expected if not loaded\n }\n try {\n execFileSync('rm', ['-f', WORKER_RESTART_PLIST_PATH]);\n this.log('[OK] Removed stale daily worker-restart agent (no longer needed on agent path)');\n } catch {\n // Best-effort\n }\n }\n\n this.log('');\n if (noProxy) {\n this.log('Installed: API + worker. Gateway/proxy skipped (--no-proxy).');\n } else {\n this.log('Installed: API + worker + gateway + capture proxy.');\n }\n this.log('All services start at login, restart on crash, and pick up rebuilds via WatchPaths.');\n this.log('');\n this.log('NOTE: Env vars are baked in at install time. If you change');\n this.log('.env.local or credentials, re-run: rulemetric service install --force');\n this.log('');\n this.log('Manage with:');\n this.log(' rulemetric service status \u2014 check if running');\n this.log(' rulemetric service uninstall \u2014 remove all services');\n\n // Ensure the active-org cache reflects the user's current active_org_id\n // before the worker spawns its first capture. Belt-and-suspenders with\n // BaseCommand's guarded refresh.\n await bootstrapActiveOrg();\n\n // Soft tmux capability check \u2014 see hooks/install.ts for the same line.\n const tmux = await detectTmux();\n this.log('');\n if (tmux.available) {\n this.log(`[OK] tmux found${tmux.version ? ` (v${tmux.version})` : ''} \u2014 live send enabled`);\n } else {\n this.log('[!!] tmux not found \u2014 live send will fall back to opening Terminal. Install with: brew install tmux');\n }\n }\n\n private findProjectRoot(): string | null {\n // Check env var override\n if (process.env.RULEMETRIC_PROJECT_ROOT) {\n return process.env.RULEMETRIC_PROJECT_ROOT;\n }\n // Walk up from cwd looking for the monorepo marker\n let dir = process.cwd();\n for (let i = 0; i < 10; i++) {\n if (existsSync(join(dir, 'apps', 'api', 'package.json'))) {\n return dir;\n }\n const parent = join(dir, '..');\n if (parent === dir) break;\n dir = parent;\n }\n return null;\n }\n\n private findBinary(name: string): string | null {\n try {\n return execFileSync('which', [name], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim() || null;\n } catch {\n return null;\n }\n }\n\n private loadEnvVars(projectRoot: string): Record<string, string> {\n const vars: Record<string, string> = {};\n\n // Load from .env.local (Supabase credentials)\n const envLocal = join(projectRoot, '.env.local');\n if (existsSync(envLocal)) {\n for (const line of readFileSync(envLocal, 'utf-8').split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eqIdx = trimmed.indexOf('=');\n if (eqIdx > 0) {\n vars[trimmed.slice(0, eqIdx)] = trimmed.slice(eqIdx + 1);\n }\n }\n }\n\n // Load from ~/.config/rulemetric/env (API key)\n const globalEnv = join(CONFIG_DIR, 'env');\n if (existsSync(globalEnv)) {\n for (const line of readFileSync(globalEnv, 'utf-8').split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eqIdx = trimmed.indexOf('=');\n if (eqIdx > 0) {\n vars[trimmed.slice(0, eqIdx)] = trimmed.slice(eqIdx + 1);\n }\n }\n }\n\n // Only keep allowed env vars \u2014 never leak unrelated secrets\n return Object.fromEntries(\n Object.entries(vars).filter(([k]) => ALLOWED_ENV_VARS.has(k)),\n );\n }\n\n // Guard: the always-on API + worker MUST use the TRANSACTION-mode pooler\n // (port 6543). A SESSION-mode URL (:5432, 15-slot cap) baked into these\n // services was the trigger of the recurring pool saturation \u2014 a pool-cascade\n // debugging override leaked into the plists through this very installer. Auto-\n // correct the obvious session\u2192transaction port and warn loudly; warn (but do\n // not block) on a direct, non-pooler URL. Pool-exhaustion RCA 2026-06-21.\n private guardAlwaysOnDatabaseUrl(envVars: Record<string, string>): Record<string, string> {\n const url = envVars.DATABASE_URL;\n if (!url) return envVars;\n if (url.includes('pooler.supabase.com:5432')) {\n this.warn(\n 'DATABASE_URL points at the SESSION-mode pooler (:5432, 15-slot cap). The ' +\n 'always-on API/worker must use TRANSACTION mode (:6543) \u2014 auto-correcting ' +\n 'for the installed services. Fix .env.local to silence this warning.',\n );\n return { ...envVars, DATABASE_URL: url.replace('pooler.supabase.com:5432', 'pooler.supabase.com:6543') };\n }\n if (/@db\\.[a-z0-9]+\\.supabase\\.co[:/]/.test(url)) {\n this.warn(\n 'DATABASE_URL is a DIRECT database connection (db.<ref>.supabase.co), not the ' +\n 'pooler. Always-on services should use the transaction pooler ' +\n '(aws-*.pooler.supabase.com:6543) to avoid exhausting Postgres connections.',\n );\n }\n return envVars;\n }\n\n // Reap orphaned, unsupervised `evals agent` worker processes before\n // (re)bootstrapping the launchd worker. The worker holds no listening port,\n // so killStaleProcess (port/pidfile based) cannot find detached agents \u2014 a\n // fleet of 4 ran 3+ days and helped saturate the pool (RCA 2026-06-21). pgrep\n // the command line and SIGTERM every match except ourselves; the agent's own\n // single-instance lock then keeps a fresh fleet from forming.\n private killStaleAgents(): void {\n try {\n const out = execFileSync('pgrep', ['-f', 'bin/run.js evals agent'], {\n encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'],\n });\n const pids = out.trim().split('\\n').filter(Boolean)\n .map((s) => parseInt(s, 10))\n .filter((p) => !isNaN(p) && p !== process.pid);\n for (const pid of pids) {\n try {\n process.kill(pid, 'SIGTERM');\n this.log(`[OK] Reaped orphan evals agent (PID ${pid})`);\n } catch {\n // already gone\n }\n }\n if (pids.length) spawnSync('sleep', ['1'], { stdio: 'ignore' });\n } catch {\n // pgrep exits non-zero when nothing matches \u2014 fine\n }\n }\n\n // Best-effort cleanup of a process that would conflict with an\n // about-to-bootstrap launchd plist. Tries two routes:\n // (a) the PID file written by the legacy spawn path\n // (gateway-lifecycle / proxy start), if pidFile is non-null\n // (b) anything actively listening on the expected port\n // Either way: SIGTERM + small grace + SIGKILL fallback. Stale PID file\n // gets unlinked.\n private killStaleProcess(port: number, pidFile: string | null, name: string): void {\n let killedAny = false;\n\n if (pidFile && existsSync(pidFile)) {\n try {\n const pidStr = readFileSync(pidFile, 'utf-8').trim();\n const pid = parseInt(pidStr, 10);\n if (!isNaN(pid)) {\n try {\n process.kill(pid, 'SIGTERM');\n killedAny = true;\n this.log(`[OK] Stopped existing ${name} (PID ${pid} from ${pidFile})`);\n } catch {\n // Already dead \u2014 fine\n }\n }\n } catch {\n // Unreadable PID file\n }\n try { execFileSync('rm', ['-f', pidFile]); } catch { /* best-effort */ }\n }\n\n // Port listener (catches orphans not in the PID file, and the foreground\n // dev API when --force is set on the API port)\n try {\n const result = execFileSync('lsof', ['-ti', `:${port}`, '-sTCP:LISTEN'], {\n encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'],\n });\n const pids = result.trim().split('\\n').filter(Boolean);\n for (const pidStr of pids) {\n const pid = parseInt(pidStr, 10);\n if (!isNaN(pid)) {\n try {\n process.kill(pid, 'SIGTERM');\n killedAny = true;\n this.log(`[OK] Stopped orphan on :${port} (PID ${pid})`);\n } catch { /* already gone */ }\n }\n }\n } catch {\n // lsof exits non-zero when nothing matches \u2014 that's fine\n }\n\n if (killedAny) {\n // Brief grace so the port releases before launchd binds it\n spawnSync('sleep', ['1'], { stdio: 'ignore' });\n }\n }\n\n private bootoutAndWrite(uid: string, plistPath: string, content: string, name: string): void {\n // First install of a service has no prior load \u2192 launchctl bootout exits\n // non-zero. The messaging is inconsistent across macOS versions (\"Could\n // not find specified service\" on some, \"Boot-out failed: 5: Input/output\n // error\" on others). We treat all of these as \"nothing to unload\" and\n // only warn on errors that don't look like that family.\n const benignBootoutPatterns = [\n 'Could not find specified service',\n 'No such process',\n '3: No such process',\n '5: Input/output error',\n ];\n try {\n execFileSync('launchctl', ['bootout', `gui/${uid}`, plistPath], {\n encoding: 'utf-8',\n // Capture stderr instead of letting it print to the user's terminal.\n // The plist was either already-loaded (we unload it cleanly) or not\n // loaded (we want to swallow the error) \u2014 the user shouldn't see\n // launchctl's raw output either way.\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n this.log(`[OK] Unloaded existing ${name} service`);\n } catch (err) {\n const msg = (err as Error).message || '';\n const stderr = ((err as { stderr?: Buffer | string }).stderr ?? '').toString();\n const combined = `${msg}\\n${stderr}`;\n if (!benignBootoutPatterns.some((p) => combined.includes(p))) {\n this.warn(`Could not unload existing ${name} service: ${msg.trim()}`);\n }\n }\n writeFileSync(plistPath, content, { mode: 0o600 });\n this.log(`[OK] ${name} plist written to ${plistPath}`);\n }\n\n private bootstrapPlist(uid: string, plistPath: string, name: string): void {\n try {\n execFileSync('launchctl', ['bootstrap', `gui/${uid}`, plistPath]);\n this.log(`[OK] ${name} service loaded into launchd`);\n } catch (bootstrapErr) {\n try {\n execFileSync('launchctl', ['load', plistPath]);\n this.log(`[OK] ${name} service loaded into launchd (legacy)`);\n } catch (loadErr) {\n this.warn(\n `Could not load ${name} service.\\n` +\n ` bootstrap: ${(bootstrapErr as Error).message.trim()}\\n` +\n ` load: ${(loadErr as Error).message.trim()}`,\n );\n }\n }\n }\n\n private buildPlist(opts: {\n port: number;\n projectRoot: string;\n nodePath: string;\n pnpmPath: string;\n envVars: Record<string, string>;\n }): string {\n const { port, projectRoot, nodePath, pnpmPath, envVars } = opts;\n\n // Derive PATH from resolved binary locations (works with nvm, volta, fnm, homebrew)\n const pathDirs = [...new Set([dirname(nodePath), dirname(pnpmPath), '/usr/local/bin', '/usr/bin', '/bin'])];\n\n // Build environment dict entries. NODE_ENV=production mirrors fly.toml\n // so the bundled web build is served exactly as it is on rulemetric.com,\n // and middleware paths that branch on NODE_ENV match prod behavior\n // locally (rate-limits, error verbosity, etc.).\n const envEntries = Object.entries({\n ...envVars,\n PORT: String(port),\n NODE_ENV: 'production',\n PATH: pathDirs.join(':'),\n // Pool resilience for the always-on API (NOT the worker, which runs\n // legitimately long analytics queries). A wedged Supavisor socket with\n // the default max=3 pool and no query timeout hangs every authenticated\n // request \u2014 the 2026-06-09 login-hang. statement_timeout lets Postgres\n // cancel a stuck query so the connection returns to the pool (verified\n // honored through the transaction pooler); the larger pool adds headroom.\n // Both env-overridable. See packages/db/src/client.ts.\n PG_POOL_MAX: envVars.PG_POOL_MAX ?? '10',\n PG_STATEMENT_TIMEOUT_MS: envVars.PG_STATEMENT_TIMEOUT_MS ?? '10000',\n // Keep the pool warm so the API stops reconnecting (and re-resolving the\n // rotating pooler ELB) on nearly every request \u2014 the churn that exposed\n // it to intermittent CONNECT_TIMEOUT/ENOTFOUND. Worker/tests keep the\n // conservative 5s default. See packages/db/src/client.ts (2026-06-09).\n PG_IDLE_TIMEOUT: envVars.PG_IDLE_TIMEOUT ?? '30',\n // Silence per-span stdout dumps on the local API \u2014 it has no OTLP\n // endpoint and a fixed (now rotated) log file; the ConsoleSpanExporter\n // grew api-stdout to 370MB / ~400k spans. Fly leaves this unset so its\n // console spans stay grep-able in the managed log stream.\n OTEL_SPAN_EXPORT: envVars.OTEL_SPAN_EXPORT ?? 'none',\n })\n .map(([k, v]) => ` <key>${escapeXml(k)}</key>\\n <string>${escapeXml(v)}</string>`)\n .join('\\n');\n\n // Run the compiled API directly \u2014 same shape as the prod Dockerfile\n // CMD. Avoids tsx startup cost on every launchd restart, and serves\n // apps/web/dist from the same process so :3000 is \"API + dashboard\",\n // matching rulemetric.com. WatchPaths on apps/api/dist auto-bounces\n // the service on rebuild, so the dev cycle is: edit src \u2192 pnpm build\n // \u2192 launchd kicks the new code automatically (no manual kickstart).\n const apiEntry = join(projectRoot, 'apps', 'api', 'dist', 'index.js');\n const apiDist = join(projectRoot, 'apps', 'api', 'dist');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${PLIST_LABEL}</string>\n\n <key>ProgramArguments</key>\n <array>\n <string>${escapeXml(nodePath)}</string>\n <string>${escapeXml(apiEntry)}</string>\n </array>\n\n <key>WorkingDirectory</key>\n <string>${escapeXml(projectRoot)}</string>\n\n <key>EnvironmentVariables</key>\n <dict>\n${envEntries}\n </dict>\n\n <key>RunAtLoad</key>\n <true/>\n\n <key>KeepAlive</key>\n <dict>\n <key>SuccessfulExit</key>\n <false/>\n </dict>\n\n <key>ThrottleInterval</key>\n <integer>10</integer>\n\n <key>WatchPaths</key>\n <array>\n <string>${escapeXml(apiDist)}</string>\n </array>\n\n <key>StandardOutPath</key>\n <string>${escapeXml(join(LOG_DIR, 'api-stdout.log'))}</string>\n\n <key>StandardErrorPath</key>\n <string>${escapeXml(join(LOG_DIR, 'api-stderr.log'))}</string>\n\n <key>ProcessType</key>\n <string>Background</string>\n</dict>\n</plist>\n`;\n }\n\n private buildWorkerPlist(opts: {\n projectRoot: string;\n nodePath: string;\n pnpmPath: string;\n envVars: Record<string, string>;\n }): string {\n const { projectRoot, nodePath, pnpmPath, envVars } = opts;\n\n const pathDirs = [...new Set([dirname(nodePath), dirname(pnpmPath), '/usr/local/bin', '/usr/bin', '/bin'])];\n\n const envEntries = Object.entries({\n ...envVars,\n NODE_ENV: 'development',\n PATH: pathDirs.join(':'),\n // Bound the worker's pooler footprint. The worker is mostly idle (HTTP\n // long-poll); an UNPINNED pool plus the previously-missing single-instance\n // guard let an orphaned fleet of agents each hold a pool and saturate\n // Supavisor's 200-client cap. A small pool + short idle timeout releases\n // sessions promptly. Pool-exhaustion RCA 2026-06-21.\n PG_POOL_MAX: envVars.PG_POOL_MAX ?? '3',\n PG_IDLE_TIMEOUT: envVars.PG_IDLE_TIMEOUT ?? '10',\n })\n .map(([k, v]) => ` <key>${escapeXml(k)}</key>\\n <string>${escapeXml(v)}</string>`)\n .join('\\n');\n\n // Run the CLI bin directly via node. Earlier this used\n // `pnpm --filter @rulemetric/cli exec rulemetric` but pnpm-exec couldn't\n // resolve the workspace bin symlink reliably under launchd, crash-looping\n // with `Command \"rulemetric\" not found`. Direct node invocation is both\n // simpler (one process less) and avoids the bin-symlink lookup entirely.\n const cliBin = join(projectRoot, 'apps', 'cli', 'bin', 'run.js');\n // Watch the CLI's dist directory: when `pnpm build` overwrites it, launchd\n // bounces the worker so a freshly-built version of the eval-worker code\n // (e.g. with a new job-kind capability) takes effect without a manual\n // kickstart. WatchPaths fires on any change beneath the directory.\n const cliDist = join(projectRoot, 'apps', 'cli', 'dist');\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${WORKER_PLIST_LABEL}</string>\n\n <key>ProgramArguments</key>\n <array>\n <string>${escapeXml(nodePath)}</string>\n <string>${escapeXml(cliBin)}</string>\n <string>evals</string>\n <string>agent</string>\n </array>\n\n <key>WorkingDirectory</key>\n <string>${escapeXml(projectRoot)}</string>\n\n <key>EnvironmentVariables</key>\n <dict>\n${envEntries}\n </dict>\n\n <key>RunAtLoad</key>\n <true/>\n\n <key>KeepAlive</key>\n <dict>\n <key>SuccessfulExit</key>\n <false/>\n </dict>\n\n <key>ThrottleInterval</key>\n <integer>15</integer>\n\n <key>WatchPaths</key>\n <array>\n <string>${escapeXml(cliDist)}</string>\n </array>\n\n <key>StandardOutPath</key>\n <string>${escapeXml(join(LOG_DIR, 'worker-stdout.log'))}</string>\n\n <key>StandardErrorPath</key>\n <string>${escapeXml(join(LOG_DIR, 'worker-stderr.log'))}</string>\n\n <key>ProcessType</key>\n <string>Background</string>\n</dict>\n</plist>\n`;\n }\n\n // CONNECT-proxy router that fronts mitmproxy on :8787. Always-on so\n // .claude/settings.json's HTTPS_PROXY pointer never resolves to a dead\n // socket (which would break Claude Code entirely \u2014 see CLAUDE.md).\n // NOT watched on rebuild: bouncing the gateway mid-request would drop\n // in-flight streaming responses. Gateway code changes rarely; on\n // upgrade, the user can `launchctl kickstart gui/<uid>/com.rulemetric.gateway`.\n private buildGatewayPlist(opts: {\n projectRoot: string;\n nodePath: string;\n }): string {\n const { projectRoot, nodePath } = opts;\n const gatewayEntry = join(projectRoot, 'apps', 'cli', 'dist', 'lib', 'gateway-entry.js');\n const cliBin = join(projectRoot, 'apps', 'cli', 'bin', 'run.js');\n\n const pathDirs = [...new Set([dirname(nodePath), '/usr/local/bin', '/usr/bin', '/bin'])];\n\n // Gateway only needs PATH + the CLI metadata used for drift detection\n // (gateway.ts:43 \u2014 when the addon's on-disk version file diverges from\n // the gateway's runtime version, it re-invokes the CLI to restart\n // mitmproxy). It does NOT need Supabase/auth env vars.\n const env = {\n RULEMETRIC_CLI_VERSION: this.config.version,\n RULEMETRIC_CLI_ENTRY: cliBin,\n PATH: pathDirs.join(':'),\n };\n\n const envEntries = Object.entries(env)\n .map(([k, v]) => ` <key>${escapeXml(k)}</key>\\n <string>${escapeXml(v)}</string>`)\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${GATEWAY_PLIST_LABEL}</string>\n\n <key>ProgramArguments</key>\n <array>\n <string>${escapeXml(nodePath)}</string>\n <string>${escapeXml(gatewayEntry)}</string>\n </array>\n\n <key>WorkingDirectory</key>\n <string>${escapeXml(projectRoot)}</string>\n\n <key>EnvironmentVariables</key>\n <dict>\n${envEntries}\n </dict>\n\n <key>RunAtLoad</key>\n <true/>\n\n <key>KeepAlive</key>\n <dict>\n <key>SuccessfulExit</key>\n <false/>\n </dict>\n\n <key>ThrottleInterval</key>\n <integer>10</integer>\n\n <key>StandardOutPath</key>\n <string>${escapeXml(join(LOG_DIR, 'gateway-stdout.log'))}</string>\n\n <key>StandardErrorPath</key>\n <string>${escapeXml(join(LOG_DIR, 'gateway-stderr.log'))}</string>\n\n <key>ProcessType</key>\n <string>Background</string>\n</dict>\n</plist>\n`;\n }\n\n // mitmproxy with the RuleMetric capture addon. Same invocation as\n // `rulemetric proxy start` (see commands/proxy/start.ts:308) so the\n // launchd path and the CLI path are interchangeable. We don't WatchPath\n // the Python addon \u2014 mitmdump has its own addon reload that handles\n // edits without a full process restart.\n private buildProxyPlist(opts: {\n projectRoot: string;\n mitmdumpPath: string;\n envVars: Record<string, string>;\n }): string {\n const { projectRoot, mitmdumpPath, envVars } = opts;\n const addonPath = join(projectRoot, 'packages', 'proxy', 'addon', 'rulemetric_addon.py');\n\n const pathDirs = [...new Set([dirname(mitmdumpPath), '/usr/local/bin', '/usr/bin', '/bin'])];\n\n // Proxy needs:\n // - auth env vars (so its reporter can POST events to the API)\n // - RULEMETRIC_PROJECT_PATH (writes session-meta + addon-version files)\n // - RULEMETRIC_CLI_VERSION (gateway uses this to detect drift)\n const env = {\n ...envVars,\n RULEMETRIC_PROJECT_PATH: projectRoot,\n RULEMETRIC_CLI_VERSION: this.config.version,\n PATH: pathDirs.join(':'),\n };\n\n const envEntries = Object.entries(env)\n .map(([k, v]) => ` <key>${escapeXml(k)}</key>\\n <string>${escapeXml(v)}</string>`)\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${PROXY_PLIST_LABEL}</string>\n\n <key>ProgramArguments</key>\n <array>\n <string>${escapeXml(mitmdumpPath)}</string>\n <string>--listen-port</string>\n <string>${PROXY_PORT}</string>\n <string>-s</string>\n <string>${escapeXml(addonPath)}</string>\n <string>--set</string>\n <string>console_eventlog_verbosity=info</string>\n </array>\n\n <key>WorkingDirectory</key>\n <string>${escapeXml(projectRoot)}</string>\n\n <key>EnvironmentVariables</key>\n <dict>\n${envEntries}\n </dict>\n\n <key>RunAtLoad</key>\n <true/>\n\n <key>KeepAlive</key>\n <dict>\n <key>SuccessfulExit</key>\n <false/>\n </dict>\n\n <key>ThrottleInterval</key>\n <integer>15</integer>\n\n <key>StandardOutPath</key>\n <string>${escapeXml(join(LOG_DIR, 'proxy-stdout.log'))}</string>\n\n <key>StandardErrorPath</key>\n <string>${escapeXml(join(LOG_DIR, 'proxy-stderr.log'))}</string>\n\n <key>ProcessType</key>\n <string>Background</string>\n</dict>\n</plist>\n`;\n }\n\n private async waitForApi(port: number, timeoutSecs: number): Promise<boolean> {\n let lastError: string | null = null;\n for (let i = 0; i < timeoutSecs; i++) {\n try {\n const res = await fetch(`http://localhost:${port}/health`);\n if (res.ok) return true;\n lastError = `health check returned ${res.status}`;\n } catch (err) {\n lastError = (err as Error).message;\n }\n await new Promise(r => setTimeout(r, 1000));\n }\n if (lastError) {\n this.warn(`Last health check error: ${lastError}`);\n }\n return false;\n }\n}\n\nfunction escapeXml(s: string): string {\n return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa;AACtB,SAAS,YAAY,WAAW,cAAc,qBAAqB;AACnE,SAAS,UAAU,cAAc,iBAAiB;AAClD,SAAS,eAAe;AACxB,SAAS,SAAS,YAAY;AAM9B,IAAM,mBAAmB,oBAAI,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAIA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAIA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,cAAc;AACpB,IAAM,aAAa,KAAK,QAAQ,GAAG,WAAW,gBAAgB,GAAG,WAAW,QAAQ;AACpF,IAAM,qBAAqB;AAC3B,IAAM,oBAAoB,KAAK,QAAQ,GAAG,WAAW,gBAAgB,GAAG,kBAAkB,QAAQ;AAClG,IAAM,sBAAsB;AAC5B,IAAM,qBAAqB,KAAK,QAAQ,GAAG,WAAW,gBAAgB,GAAG,mBAAmB,QAAQ;AACpG,IAAM,oBAAoB;AAC1B,IAAM,mBAAmB,KAAK,QAAQ,GAAG,WAAW,gBAAgB,GAAG,iBAAiB,QAAQ;AAGhG,IAAM,6BAA6B;AACnC,IAAM,4BAA4B,KAAK,QAAQ,GAAG,WAAW,gBAAgB,GAAG,0BAA0B,QAAQ;AAClH,IAAM,aAAa,KAAK,QAAQ,GAAG,WAAW,YAAY;AAC1D,IAAM,UAAU,KAAK,YAAY,MAAM;AACvC,IAAM,eAAe;AACrB,IAAM,aAAa;AAEnB,IAAqB,iBAArB,MAAqB,wBAAuB,YAAY;AAAA,EACtD,OAAgB,cAAc;AAAA,EAE9B,OAAgB,WAAW;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EAEA,OAAgB,QAAQ;AAAA,IACtB,MAAM,MAAM,QAAQ;AAAA,MAClB,MAAM;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,IACf,CAAC;AAAA,IACD,OAAO,MAAM,QAAQ;AAAA,MACnB,MAAM;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,IACf,CAAC;AAAA,IACD,cAAc,MAAM,QAAQ;AAAA,MAC1B,SAAS;AAAA,MACT,aAAa;AAAA,IACf,CAAC;AAAA,IACD,YAAY,MAAM,QAAQ;AAAA,MACxB,SAAS;AAAA,MACT,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAqB;AACzB,UAAM,EAAE,MAAM,IAAI,MAAM,KAAK,MAAM,eAAc;AACjD,UAAM,OAAO,MAAM;AACnB,UAAM,YAAY,MAAM,YAAY;AACpC,UAAM,UAAU,MAAM,UAAU;AAGhC,QAAI,QAAQ,aAAa,UAAU;AACjC,WAAK,MAAM,qFAAqF;AAAA,IAClG;AAKA,QAAI,WAAW,UAAU,KAAK,CAAC,MAAM,OAAO;AAC1C,WAAK,IAAI,gCAAgC,UAAU,EAAE;AACrD,WAAK,IAAI,6DAA6D;AACtE;AAAA,IACF;AAIA,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,oBAAoB,IAAI,SAAS;AACzD,UAAI,IAAI,MAAM,CAAC,MAAM,OAAO;AAC1B,aAAK,KAAK,QAAQ,IAAI,8EAA8E;AACpG,aAAK,KAAK,2EAA2E;AACrF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAGA,UAAM,cAAc,KAAK,gBAAgB;AACzC,QAAI,CAAC,aAAa;AAChB,WAAK;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAGA,UAAM,WAAW,KAAK,aAAa,YAAY;AAC/C,QAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,WAAK,MAAM,2BAA2B,QAAQ,gDAAgD;AAAA,IAChG;AAGA,UAAM,UAAU,KAAK,yBAAyB,KAAK,YAAY,WAAW,CAAC;AAG3E,UAAM,WAAW,KAAK,WAAW,MAAM;AACvC,UAAM,WAAW,KAAK,WAAW,MAAM;AACvC,QAAI,CAAC,YAAY,CAAC,UAAU;AAC1B,WAAK,MAAM,4DAA4D;AAAA,IACzE;AAKA,QAAI,eAA8B;AAClC,QAAI,CAAC,SAAS;AACZ,qBAAe,KAAK,WAAW,UAAU;AACzC,UAAI,CAAC,cAAc;AACjB,aAAK;AAAA,UACH;AAAA,QAEF;AAAA,MACF;AAAA,IACF;AAMA,QAAI,CAAC,WAAW;AACd,WAAK,IAAI,gEAAgE;AACzE,YAAM,eAAe;AAAA,QACnB;AAAA,QAAY;AAAA,QACZ;AAAA,QAAY;AAAA,QACZ;AAAA,QAAY;AAAA,MACd;AACA,YAAM,QAAQ,UAAU,UAAW,CAAC,QAAQ,SAAS,OAAO,SAAS,GAAG,YAAY,GAAG;AAAA,QACrF,KAAK;AAAA,QACL,OAAO;AAAA,MACT,CAAC;AACD,UAAI,MAAM,WAAW,GAAG;AACtB,aAAK;AAAA,UACH,gCAAgC,MAAM,MAAM;AAAA,QAC9C;AAAA,MACF;AACA,WAAK,IAAI,qBAAqB;AAAA,IAChC,OAAO;AACL,WAAK,IAAI,gDAAgD;AAAA,IAC3D;AAIA,UAAM,WAAW,KAAK,aAAa,QAAQ,OAAO,QAAQ,UAAU;AACpE,QAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,WAAK,MAAM,0BAA0B,QAAQ,2EAA2E;AAAA,IAC1H;AACA,UAAM,WAAW,KAAK,aAAa,QAAQ,OAAO,QAAQ,YAAY;AACtE,QAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,WAAK,KAAK,2BAA2B,QAAQ,mBAAmB,IAAI,qCAAqC;AAAA,IAC3G;AACA,QAAI,CAAC,SAAS;AACZ,YAAM,eAAe,KAAK,aAAa,QAAQ,OAAO,QAAQ,OAAO,kBAAkB;AACvF,UAAI,CAAC,WAAW,YAAY,GAAG;AAC7B,aAAK,MAAM,8BAA8B,YAAY,wEAAwE;AAAA,MAC/H;AAAA,IACF;AAOA,QAAI,MAAM,OAAO;AACf,WAAK,iBAAiB,MAAM,MAAM,aAAa,IAAI;AAAA,IACrD;AACA,QAAI,CAAC,SAAS;AACZ,WAAK,iBAAiB,cAAc,KAAK,YAAY,aAAa,GAAG,SAAS;AAC9E,WAAK,iBAAiB,YAAY,KAAK,YAAY,WAAW,GAAG,WAAW;AAAA,IAC9E;AAGA,cAAU,KAAK,QAAQ,GAAG,WAAW,cAAc,GAAG,EAAE,WAAW,KAAK,CAAC;AACzE,cAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAEtC,UAAM,MAAM,SAAS,SAAS,EAAE,UAAU,QAAQ,CAAC,EAAE,KAAK;AAG1D,UAAM,WAAW,KAAK,WAAW,EAAE,MAAM,aAAa,UAAqB,UAAqB,QAAQ,CAAC;AACzG,SAAK,gBAAgB,KAAK,YAAY,UAAU,KAAK;AACrD,SAAK,eAAe,KAAK,YAAY,KAAK;AAG1C,SAAK,IAAI,4BAA4B,IAAI,KAAK;AAC9C,UAAM,aAAa,MAAM,KAAK,WAAW,MAAM,EAAE;AACjD,QAAI,YAAY;AACd,WAAK,IAAI,mCAAmC,IAAI,EAAE;AAClD,UAAI,WAAW,QAAQ,GAAG;AACxB,aAAK,IAAI,gDAAgD,IAAI,EAAE;AAAA,MACjE;AAAA,IACF,OAAO;AACL,WAAK,KAAK,6CAA6C;AACvD,WAAK,IAAI,aAAa,OAAO,iBAAiB;AAC9C,WAAK,IAAI,aAAa,OAAO,iBAAiB;AAAA,IAChD;AAGA,SAAK,IAAI,EAAE;AACX,SAAK,IAAI,8BAA8B;AAIvC,SAAK,gBAAgB;AACrB,UAAM,cAAc,KAAK,iBAAiB,EAAE,aAAa,UAAqB,UAAqB,QAAQ,CAAC;AAC5G,SAAK,gBAAgB,KAAK,mBAAmB,aAAa,QAAQ;AAClE,SAAK,eAAe,KAAK,mBAAmB,QAAQ;AAGpD,QAAI,CAAC,SAAS;AACZ,WAAK,IAAI,EAAE;AACX,WAAK,IAAI,gDAAgD;AAEzD,YAAM,eAAe,KAAK,kBAAkB,EAAE,aAAa,SAAoB,CAAC;AAChF,WAAK,gBAAgB,KAAK,oBAAoB,cAAc,SAAS;AACrE,WAAK,eAAe,KAAK,oBAAoB,SAAS;AAEtD,YAAM,aAAa,KAAK,gBAAgB;AAAA,QACtC;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AACD,WAAK,gBAAgB,KAAK,kBAAkB,YAAY,OAAO;AAC/D,WAAK,eAAe,KAAK,kBAAkB,OAAO;AAAA,IACpD;AAQA,QAAI,WAAW,yBAAyB,GAAG;AACzC,UAAI;AACF,qBAAa,aAAa,CAAC,WAAW,OAAO,GAAG,IAAI,yBAAyB,GAAG,EAAE,UAAU,QAAQ,CAAC;AAAA,MACvG,QAAQ;AAAA,MAER;AACA,UAAI;AACF,qBAAa,MAAM,CAAC,MAAM,yBAAyB,CAAC;AACpD,aAAK,IAAI,gFAAgF;AAAA,MAC3F,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,SAAK,IAAI,EAAE;AACX,QAAI,SAAS;AACX,WAAK,IAAI,8DAA8D;AAAA,IACzE,OAAO;AACL,WAAK,IAAI,oDAAoD;AAAA,IAC/D;AACA,SAAK,IAAI,qFAAqF;AAC9F,SAAK,IAAI,EAAE;AACX,SAAK,IAAI,4DAA4D;AACrE,SAAK,IAAI,uEAAuE;AAChF,SAAK,IAAI,EAAE;AACX,SAAK,IAAI,cAAc;AACvB,SAAK,IAAI,wDAAmD;AAC5D,SAAK,IAAI,2DAAsD;AAK/D,UAAM,mBAAmB;AAGzB,UAAM,OAAO,MAAM,WAAW;AAC9B,SAAK,IAAI,EAAE;AACX,QAAI,KAAK,WAAW;AAClB,WAAK,IAAI,kBAAkB,KAAK,UAAU,MAAM,KAAK,OAAO,MAAM,EAAE,2BAAsB;AAAA,IAC5F,OAAO;AACL,WAAK,IAAI,0GAAqG;AAAA,IAChH;AAAA,EACF;AAAA,EAEQ,kBAAiC;AAEvC,QAAI,QAAQ,IAAI,yBAAyB;AACvC,aAAO,QAAQ,IAAI;AAAA,IACrB;AAEA,QAAI,MAAM,QAAQ,IAAI;AACtB,aAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,UAAI,WAAW,KAAK,KAAK,QAAQ,OAAO,cAAc,CAAC,GAAG;AACxD,eAAO;AAAA,MACT;AACA,YAAM,SAAS,KAAK,KAAK,IAAI;AAC7B,UAAI,WAAW,IAAK;AACpB,YAAM;AAAA,IACR;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,WAAW,MAA6B;AAC9C,QAAI;AACF,aAAO,aAAa,SAAS,CAAC,IAAI,GAAG,EAAE,UAAU,SAAS,OAAO,CAAC,QAAQ,QAAQ,QAAQ,EAAE,CAAC,EAAE,KAAK,KAAK;AAAA,IAC3G,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,YAAY,aAA6C;AAC/D,UAAM,OAA+B,CAAC;AAGtC,UAAM,WAAW,KAAK,aAAa,YAAY;AAC/C,QAAI,WAAW,QAAQ,GAAG;AACxB,iBAAW,QAAQ,aAAa,UAAU,OAAO,EAAE,MAAM,IAAI,GAAG;AAC9D,cAAM,UAAU,KAAK,KAAK;AAC1B,YAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,EAAG;AACzC,cAAM,QAAQ,QAAQ,QAAQ,GAAG;AACjC,YAAI,QAAQ,GAAG;AACb,eAAK,QAAQ,MAAM,GAAG,KAAK,CAAC,IAAI,QAAQ,MAAM,QAAQ,CAAC;AAAA,QACzD;AAAA,MACF;AAAA,IACF;AAGA,UAAM,YAAY,KAAK,YAAY,KAAK;AACxC,QAAI,WAAW,SAAS,GAAG;AACzB,iBAAW,QAAQ,aAAa,WAAW,OAAO,EAAE,MAAM,IAAI,GAAG;AAC/D,cAAM,UAAU,KAAK,KAAK;AAC1B,YAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,EAAG;AACzC,cAAM,QAAQ,QAAQ,QAAQ,GAAG;AACjC,YAAI,QAAQ,GAAG;AACb,eAAK,QAAQ,MAAM,GAAG,KAAK,CAAC,IAAI,QAAQ,MAAM,QAAQ,CAAC;AAAA,QACzD;AAAA,MACF;AAAA,IACF;AAGA,WAAO,OAAO;AAAA,MACZ,OAAO,QAAQ,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,iBAAiB,IAAI,CAAC,CAAC;AAAA,IAC9D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,yBAAyB,SAAyD;AACxF,UAAM,MAAM,QAAQ;AACpB,QAAI,CAAC,IAAK,QAAO;AACjB,QAAI,IAAI,SAAS,0BAA0B,GAAG;AAC5C,WAAK;AAAA,QACH;AAAA,MAGF;AACA,aAAO,EAAE,GAAG,SAAS,cAAc,IAAI,QAAQ,4BAA4B,0BAA0B,EAAE;AAAA,IACzG;AACA,QAAI,mCAAmC,KAAK,GAAG,GAAG;AAChD,WAAK;AAAA,QACH;AAAA,MAGF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,kBAAwB;AAC9B,QAAI;AACF,YAAM,MAAM,aAAa,SAAS,CAAC,MAAM,wBAAwB,GAAG;AAAA,QAClE,UAAU;AAAA,QAAS,OAAO,CAAC,UAAU,QAAQ,QAAQ;AAAA,MACvD,CAAC;AACD,YAAM,OAAO,IAAI,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO,EAC/C,IAAI,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC,EAC1B,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,MAAM,QAAQ,GAAG;AAC/C,iBAAW,OAAO,MAAM;AACtB,YAAI;AACF,kBAAQ,KAAK,KAAK,SAAS;AAC3B,eAAK,IAAI,uCAAuC,GAAG,GAAG;AAAA,QACxD,QAAQ;AAAA,QAER;AAAA,MACF;AACA,UAAI,KAAK,OAAQ,WAAU,SAAS,CAAC,GAAG,GAAG,EAAE,OAAO,SAAS,CAAC;AAAA,IAChE,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,iBAAiB,MAAc,SAAwB,MAAoB;AACjF,QAAI,YAAY;AAEhB,QAAI,WAAW,WAAW,OAAO,GAAG;AAClC,UAAI;AACF,cAAM,SAAS,aAAa,SAAS,OAAO,EAAE,KAAK;AACnD,cAAM,MAAM,SAAS,QAAQ,EAAE;AAC/B,YAAI,CAAC,MAAM,GAAG,GAAG;AACf,cAAI;AACF,oBAAQ,KAAK,KAAK,SAAS;AAC3B,wBAAY;AACZ,iBAAK,IAAI,yBAAyB,IAAI,SAAS,GAAG,SAAS,OAAO,GAAG;AAAA,UACvE,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AACA,UAAI;AAAE,qBAAa,MAAM,CAAC,MAAM,OAAO,CAAC;AAAA,MAAG,QAAQ;AAAA,MAAoB;AAAA,IACzE;AAIA,QAAI;AACF,YAAM,SAAS,aAAa,QAAQ,CAAC,OAAO,IAAI,IAAI,IAAI,cAAc,GAAG;AAAA,QACvE,UAAU;AAAA,QAAS,OAAO,CAAC,UAAU,QAAQ,QAAQ;AAAA,MACvD,CAAC;AACD,YAAM,OAAO,OAAO,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO;AACrD,iBAAW,UAAU,MAAM;AACzB,cAAM,MAAM,SAAS,QAAQ,EAAE;AAC/B,YAAI,CAAC,MAAM,GAAG,GAAG;AACf,cAAI;AACF,oBAAQ,KAAK,KAAK,SAAS;AAC3B,wBAAY;AACZ,iBAAK,IAAI,2BAA2B,IAAI,SAAS,GAAG,GAAG;AAAA,UACzD,QAAQ;AAAA,UAAqB;AAAA,QAC/B;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,QAAI,WAAW;AAEb,gBAAU,SAAS,CAAC,GAAG,GAAG,EAAE,OAAO,SAAS,CAAC;AAAA,IAC/C;AAAA,EACF;AAAA,EAEQ,gBAAgB,KAAa,WAAmB,SAAiB,MAAoB;AAM3F,UAAM,wBAAwB;AAAA,MAC5B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI;AACF,mBAAa,aAAa,CAAC,WAAW,OAAO,GAAG,IAAI,SAAS,GAAG;AAAA,QAC9D,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,QAKV,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,MAClC,CAAC;AACD,WAAK,IAAI,0BAA0B,IAAI,UAAU;AAAA,IACnD,SAAS,KAAK;AACZ,YAAM,MAAO,IAAc,WAAW;AACtC,YAAM,UAAW,IAAqC,UAAU,IAAI,SAAS;AAC7E,YAAM,WAAW,GAAG,GAAG;AAAA,EAAK,MAAM;AAClC,UAAI,CAAC,sBAAsB,KAAK,CAAC,MAAM,SAAS,SAAS,CAAC,CAAC,GAAG;AAC5D,aAAK,KAAK,6BAA6B,IAAI,aAAa,IAAI,KAAK,CAAC,EAAE;AAAA,MACtE;AAAA,IACF;AACA,kBAAc,WAAW,SAAS,EAAE,MAAM,IAAM,CAAC;AACjD,SAAK,IAAI,QAAQ,IAAI,qBAAqB,SAAS,EAAE;AAAA,EACvD;AAAA,EAEQ,eAAe,KAAa,WAAmB,MAAoB;AACzE,QAAI;AACF,mBAAa,aAAa,CAAC,aAAa,OAAO,GAAG,IAAI,SAAS,CAAC;AAChE,WAAK,IAAI,QAAQ,IAAI,8BAA8B;AAAA,IACrD,SAAS,cAAc;AACrB,UAAI;AACF,qBAAa,aAAa,CAAC,QAAQ,SAAS,CAAC;AAC7C,aAAK,IAAI,QAAQ,IAAI,uCAAuC;AAAA,MAC9D,SAAS,SAAS;AAChB,aAAK;AAAA,UACH,kBAAkB,IAAI;AAAA,eACL,aAAuB,QAAQ,KAAK,CAAC;AAAA,UAC1C,QAAkB,QAAQ,KAAK,CAAC;AAAA,QAC9C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,WAAW,MAMR;AACT,UAAM,EAAE,MAAM,aAAa,UAAU,UAAU,QAAQ,IAAI;AAG3D,UAAM,WAAW,CAAC,GAAG,oBAAI,IAAI,CAAC,QAAQ,QAAQ,GAAG,QAAQ,QAAQ,GAAG,kBAAkB,YAAY,MAAM,CAAC,CAAC;AAM1G,UAAM,aAAa,OAAO,QAAQ;AAAA,MAChC,GAAG;AAAA,MACH,MAAM,OAAO,IAAI;AAAA,MACjB,UAAU;AAAA,MACV,MAAM,SAAS,KAAK,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQvB,aAAa,QAAQ,eAAe;AAAA,MACpC,yBAAyB,QAAQ,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA,MAK5D,iBAAiB,QAAQ,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA,MAK5C,kBAAkB,QAAQ,oBAAoB;AAAA,IAChD,CAAC,EACE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,cAAc,UAAU,CAAC,CAAC;AAAA,gBAAyB,UAAU,CAAC,CAAC,WAAW,EAC1F,KAAK,IAAI;AAQZ,UAAM,WAAW,KAAK,aAAa,QAAQ,OAAO,QAAQ,UAAU;AACpE,UAAM,UAAU,KAAK,aAAa,QAAQ,OAAO,MAAM;AAEvD,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA,YAKC,WAAW;AAAA;AAAA;AAAA;AAAA,cAIT,UAAU,QAAQ,CAAC;AAAA,cACnB,UAAU,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA,YAIrB,UAAU,WAAW,CAAC;AAAA;AAAA;AAAA;AAAA,EAIhC,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAiBE,UAAU,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA,YAIpB,UAAU,KAAK,SAAS,gBAAgB,CAAC,CAAC;AAAA;AAAA;AAAA,YAG1C,UAAU,KAAK,SAAS,gBAAgB,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOpD;AAAA,EAEQ,iBAAiB,MAKd;AACT,UAAM,EAAE,aAAa,UAAU,UAAU,QAAQ,IAAI;AAErD,UAAM,WAAW,CAAC,GAAG,oBAAI,IAAI,CAAC,QAAQ,QAAQ,GAAG,QAAQ,QAAQ,GAAG,kBAAkB,YAAY,MAAM,CAAC,CAAC;AAE1G,UAAM,aAAa,OAAO,QAAQ;AAAA,MAChC,GAAG;AAAA,MACH,UAAU;AAAA,MACV,MAAM,SAAS,KAAK,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMvB,aAAa,QAAQ,eAAe;AAAA,MACpC,iBAAiB,QAAQ,mBAAmB;AAAA,IAC9C,CAAC,EACE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,cAAc,UAAU,CAAC,CAAC;AAAA,gBAAyB,UAAU,CAAC,CAAC,WAAW,EAC1F,KAAK,IAAI;AAOZ,UAAM,SAAS,KAAK,aAAa,QAAQ,OAAO,OAAO,QAAQ;AAK/D,UAAM,UAAU,KAAK,aAAa,QAAQ,OAAO,MAAM;AACvD,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA,YAKC,kBAAkB;AAAA;AAAA;AAAA;AAAA,cAIhB,UAAU,QAAQ,CAAC;AAAA,cACnB,UAAU,MAAM,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAMnB,UAAU,WAAW,CAAC;AAAA;AAAA;AAAA;AAAA,EAIhC,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAiBE,UAAU,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA,YAIpB,UAAU,KAAK,SAAS,mBAAmB,CAAC,CAAC;AAAA;AAAA;AAAA,YAG7C,UAAU,KAAK,SAAS,mBAAmB,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,kBAAkB,MAGf;AACT,UAAM,EAAE,aAAa,SAAS,IAAI;AAClC,UAAM,eAAe,KAAK,aAAa,QAAQ,OAAO,QAAQ,OAAO,kBAAkB;AACvF,UAAM,SAAS,KAAK,aAAa,QAAQ,OAAO,OAAO,QAAQ;AAE/D,UAAM,WAAW,CAAC,GAAG,oBAAI,IAAI,CAAC,QAAQ,QAAQ,GAAG,kBAAkB,YAAY,MAAM,CAAC,CAAC;AAMvF,UAAM,MAAM;AAAA,MACV,wBAAwB,KAAK,OAAO;AAAA,MACpC,sBAAsB;AAAA,MACtB,MAAM,SAAS,KAAK,GAAG;AAAA,IACzB;AAEA,UAAM,aAAa,OAAO,QAAQ,GAAG,EAClC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,cAAc,UAAU,CAAC,CAAC;AAAA,gBAAyB,UAAU,CAAC,CAAC,WAAW,EAC1F,KAAK,IAAI;AAEZ,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA,YAKC,mBAAmB;AAAA;AAAA;AAAA;AAAA,cAIjB,UAAU,QAAQ,CAAC;AAAA,cACnB,UAAU,YAAY,CAAC;AAAA;AAAA;AAAA;AAAA,YAIzB,UAAU,WAAW,CAAC;AAAA;AAAA;AAAA;AAAA,EAIhC,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAgBA,UAAU,KAAK,SAAS,oBAAoB,CAAC,CAAC;AAAA;AAAA;AAAA,YAG9C,UAAU,KAAK,SAAS,oBAAoB,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOxD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,gBAAgB,MAIb;AACT,UAAM,EAAE,aAAa,cAAc,QAAQ,IAAI;AAC/C,UAAM,YAAY,KAAK,aAAa,YAAY,SAAS,SAAS,qBAAqB;AAEvF,UAAM,WAAW,CAAC,GAAG,oBAAI,IAAI,CAAC,QAAQ,YAAY,GAAG,kBAAkB,YAAY,MAAM,CAAC,CAAC;AAM3F,UAAM,MAAM;AAAA,MACV,GAAG;AAAA,MACH,yBAAyB;AAAA,MACzB,wBAAwB,KAAK,OAAO;AAAA,MACpC,MAAM,SAAS,KAAK,GAAG;AAAA,IACzB;AAEA,UAAM,aAAa,OAAO,QAAQ,GAAG,EAClC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,cAAc,UAAU,CAAC,CAAC;AAAA,gBAAyB,UAAU,CAAC,CAAC,WAAW,EAC1F,KAAK,IAAI;AAEZ,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA,YAKC,iBAAiB;AAAA;AAAA;AAAA;AAAA,cAIf,UAAU,YAAY,CAAC;AAAA;AAAA,cAEvB,UAAU;AAAA;AAAA,cAEV,UAAU,SAAS,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAMtB,UAAU,WAAW,CAAC;AAAA;AAAA;AAAA;AAAA,EAIhC,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAgBA,UAAU,KAAK,SAAS,kBAAkB,CAAC,CAAC;AAAA;AAAA;AAAA,YAG5C,UAAU,KAAK,SAAS,kBAAkB,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOtD;AAAA,EAEA,MAAc,WAAW,MAAc,aAAuC;AAC5E,QAAI,YAA2B;AAC/B,aAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,oBAAoB,IAAI,SAAS;AACzD,YAAI,IAAI,GAAI,QAAO;AACnB,oBAAY,yBAAyB,IAAI,MAAM;AAAA,MACjD,SAAS,KAAK;AACZ,oBAAa,IAAc;AAAA,MAC7B;AACA,YAAM,IAAI,QAAQ,OAAK,WAAW,GAAG,GAAI,CAAC;AAAA,IAC5C;AACA,QAAI,WAAW;AACb,WAAK,KAAK,4BAA4B,SAAS,EAAE;AAAA,IACnD;AACA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,UAAU,GAAmB;AACpC,SAAO,EAAE,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,EAAE,QAAQ,MAAM,MAAM,EAAE,QAAQ,MAAM,QAAQ;AACpG;",
|
|
4
|
+
"sourcesContent": ["import { Flags } from '@oclif/core';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { execSync, execFileSync, spawnSync } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\nimport { bootstrapActiveOrg } from '../../lib/active-org-refresh.js';\nimport { detectTmux } from '../../lib/detect-tmux.js';\n\n// Only these env vars are written to the plist \u2014 never leak unrelated secrets\nconst ALLOWED_ENV_VARS = new Set([\n 'SUPABASE_URL',\n 'SUPABASE_ANON_KEY',\n 'SUPABASE_SERVICE_ROLE_KEY',\n // Enables local HS256 JWT verification in the API's authMiddleware \u2014\n // without it every authed request pays a /auth/v1/user network round-trip\n // (measured p50 ~244ms) to Supabase.\n 'SUPABASE_JWT_SECRET',\n 'DATABASE_URL',\n 'RULEMETRIC_API_URL',\n 'RULEMETRIC_API_KEY',\n 'RULEMETRIC_ACCESS_TOKEN',\n // Recipient for the weekly cron_data_integrity_audit notification.\n // Without one of these, the audit cron runs but nobody is notified \u2014\n // see cron-data-integrity-audit.ts (defaults: NOTIFY_USER_ID then USER_ID).\n 'RULEMETRIC_AUDIT_NOTIFY_USER_ID',\n 'RULEMETRIC_USER_ID',\n // cron_refresh_skills needs an authenticated GitHub API budget (5000\n // req/hr) \u2014 without it the handler skips and the skills registry goes\n // stale, which makes cron_suggest_instructions mark every suggestion\n // stale on each nightly run.\n 'GITHUB_TOKEN',\n // Pool-tuning overrides so the plist generators (buildPlist / buildWorkerPlist)\n // actually honor a user-set value instead of always falling back to the pinned\n // default \u2014 without these in the allow-list the `envVars.PG_* ?? '<default>'`\n // escape hatch was silently dead. Pool-exhaustion RCA 2026-06-21.\n 'PG_POOL_MAX',\n 'PG_IDLE_TIMEOUT',\n 'PG_STATEMENT_TIMEOUT_MS',\n // Resend transactional email. The worker (`evals agent`) sends changelog\n // announcements via `@rulemetric/email` \u2192 `sendEmail()`, which picks the\n // provider from `process.env.RESEND_API_KEY`. Without these in the allow-list\n // the installer stripped them out of the worker plist, the CLI never loads\n // `.env.local`, so the worker fell back to the `noop` provider and SILENTLY\n // dropped every announcement email. RESEND_FROM_EMAIL must be the verified\n // sender (noreply@rulemetric.com) \u2014 otherwise the provider defaults to\n // Resend's shared sandbox `onboarding@resend.dev`, which Gmail spam-filters.\n 'RESEND_API_KEY',\n 'RESEND_FROM_EMAIL',\n]);\n\nconst PLIST_LABEL = 'com.rulemetric.api';\nconst PLIST_PATH = join(homedir(), 'Library', 'LaunchAgents', `${PLIST_LABEL}.plist`);\nconst WORKER_PLIST_LABEL = 'com.rulemetric.worker';\nconst WORKER_PLIST_PATH = join(homedir(), 'Library', 'LaunchAgents', `${WORKER_PLIST_LABEL}.plist`);\nconst GATEWAY_PLIST_LABEL = 'com.rulemetric.gateway';\nconst GATEWAY_PLIST_PATH = join(homedir(), 'Library', 'LaunchAgents', `${GATEWAY_PLIST_LABEL}.plist`);\nconst PROXY_PLIST_LABEL = 'com.rulemetric.proxy';\nconst PROXY_PLIST_PATH = join(homedir(), 'Library', 'LaunchAgents', `${PROXY_PLIST_LABEL}.plist`);\n// Daily kickstart agent \u2014 see Phase 4c cleanup block below. Kept as a constant\n// so we can tear it down on every install if a stale copy is lying around.\nconst WORKER_RESTART_PLIST_LABEL = 'com.rulemetric.worker.restart';\nconst WORKER_RESTART_PLIST_PATH = join(homedir(), 'Library', 'LaunchAgents', `${WORKER_RESTART_PLIST_LABEL}.plist`);\nconst CONFIG_DIR = join(homedir(), '.config', 'rulemetric');\nconst LOG_DIR = join(CONFIG_DIR, 'logs');\nconst GATEWAY_PORT = 8787;\nconst PROXY_PORT = 8788;\n// Linux systemd --user unit for the worker-only install path.\nconst WORKER_SYSTEMD_DIR = join(homedir(), '.config', 'systemd', 'user');\nconst WORKER_SYSTEMD_UNIT = 'rulemetric-worker.service';\nconst WORKER_SYSTEMD_PATH = join(WORKER_SYSTEMD_DIR, WORKER_SYSTEMD_UNIT);\n\nexport default class ServiceInstall extends BaseCommand {\n static override description =\n 'Install RuleMetric services. Default (in a repo checkout): API + worker + gateway + capture proxy as launchd agents. With --worker-only: just the job worker, targeting the globally-installed CLI \u2014 no repo, no build (works from `npm i -g @rulemetric/cli`, macOS launchd or Linux systemd).';\n\n static override examples = [\n '<%= config.bin %> service install',\n '<%= config.bin %> service install --port 3000',\n '<%= config.bin %> service install --skip-build',\n '<%= config.bin %> service install --no-proxy',\n '<%= config.bin %> service install --worker-only',\n ];\n\n static override flags = {\n port: Flags.integer({\n char: 'p',\n default: 3000,\n description: 'API server port',\n }),\n force: Flags.boolean({\n char: 'f',\n default: false,\n description: 'Overwrite existing service configuration and reclaim conflicting ports',\n }),\n 'skip-build': Flags.boolean({\n default: false,\n description: 'Skip the pre-install workspace build (API/web/cli dist must already exist)',\n }),\n 'no-proxy': Flags.boolean({\n default: false,\n description: 'Skip gateway + mitmproxy plists (just install API + worker)',\n }),\n 'worker-only': Flags.boolean({\n default: false,\n description:\n 'Install ONLY the job worker as a persistent service, targeting the globally-installed CLI (no repo checkout, no build, no API/gateway/proxy). Sources auth from ~/.config/rulemetric/env. macOS (launchd) + Linux (systemd --user).',\n }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(ServiceInstall);\n const port = flags.port;\n const skipBuild = flags['skip-build'];\n const noProxy = flags['no-proxy'];\n\n // Worker-only path: a no-repo, no-build installer for end users on the\n // globally-installed CLI. Diverges so completely from the full monorepo\n // install (no findProjectRoot, no .env.local, no turbo build, no\n // API/gateway/proxy) that it gets its own method rather than threading\n // `if (!workerOnly)` through 200 lines below. Supports macOS + Linux.\n if (flags['worker-only']) {\n await this.runWorkerOnly(flags.force);\n return;\n }\n\n // The full install (API + web + gateway + proxy) is macOS-only.\n if (process.platform !== 'darwin') {\n this.error(\n 'The full service install is macOS-only (launchd). For just the job worker on Linux (or a clean npm install), use: rulemetric service install --worker-only',\n );\n }\n\n // Existing-service guard. Presence of the API plist implies a prior\n // install \u2014 we only proceed automatically when --force is set so a\n // user doesn't accidentally overwrite a working config.\n if (existsSync(PLIST_PATH) && !flags.force) {\n this.log(`Service already installed at ${PLIST_PATH}`);\n this.log('Use --force to overwrite, or run: rulemetric service status');\n return;\n }\n\n // API port conflict. Without --force we bail; with --force we'll reclaim\n // the port later in killStaleProcess so launchd can actually bind it.\n try {\n const res = await fetch(`http://localhost:${port}/health`);\n if (res.ok && !flags.force) {\n this.warn(`Port ${port} is already in use (API may be running via start.sh or in another terminal).`);\n this.warn('Stop it first with scripts/stop.sh, or use --force to take over the port.');\n return;\n }\n } catch {\n // Port free \u2014 good\n }\n\n // Find project root\n const projectRoot = this.findProjectRoot();\n if (!projectRoot) {\n this.error(\n 'Could not find RuleMetric project root. Run this command from within the momento-mori repo, or set RULEMETRIC_PROJECT_ROOT.',\n );\n }\n\n // Verify .env.local\n const envLocal = join(projectRoot, '.env.local');\n if (!existsSync(envLocal)) {\n this.error(`.env.local not found at ${envLocal}. The API needs Supabase credentials to start.`);\n }\n\n // Load env vars (filtered by ALLOWED_ENV_VARS)\n const envVars = this.guardAlwaysOnDatabaseUrl(this.loadEnvVars(projectRoot));\n\n // Resolve binaries\n const nodePath = this.findBinary('node');\n const pnpmPath = this.findBinary('pnpm');\n if (!nodePath || !pnpmPath) {\n this.error('Could not find node or pnpm. Ensure they are in your PATH.');\n }\n\n // Resolve mitmdump (required unless --no-proxy). We resolve via `which`\n // so the plist gets an absolute path \u2014 launchd's PATH at GUI session\n // start can be narrow, and pyenv shims often don't appear in it.\n let mitmdumpPath: string | null = null;\n if (!noProxy) {\n mitmdumpPath = this.findBinary('mitmdump');\n if (!mitmdumpPath) {\n this.error(\n 'mitmdump not found in PATH. Install with: pipx install mitmproxy (or brew install mitmproxy), then re-run.\\n' +\n 'Or skip the capture proxy with --no-proxy.',\n );\n }\n }\n\n // \u2500\u2500 Pre-install build \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Mirrors the Dockerfile prod stage: API + web + CLI all need their\n // dist/ directories to exist before launchd starts the processes. We\n // run the whole set via turbo so the cache eats the cost on re-installs.\n if (!skipBuild) {\n this.log('[..] Building workspace artifacts (api, web, cli) via turbo...');\n const buildFilters = [\n '--filter', '@rulemetric/api...',\n '--filter', '@rulemetric/web...',\n '--filter', '@rulemetric/cli',\n ];\n const build = spawnSync(pnpmPath!, ['exec', 'turbo', 'run', 'build', ...buildFilters], {\n cwd: projectRoot,\n stdio: 'inherit',\n });\n if (build.status !== 0) {\n this.error(\n `Workspace build failed (exit ${build.status}). Fix the build first, or use --skip-build if dist/ already exists.`,\n );\n }\n this.log('[OK] Build complete');\n } else {\n this.log('[..] Skipping pre-install build (--skip-build)');\n }\n\n // Verify the build artifacts launchd is about to invoke. A missing\n // entry would crash-loop the plist; surfacing it here is friendlier.\n const apiEntry = join(projectRoot, 'apps', 'api', 'dist', 'index.js');\n if (!existsSync(apiEntry)) {\n this.error(`API entry not found at ${apiEntry}. Re-run without --skip-build, or: pnpm --filter @rulemetric/api... build`);\n }\n const webIndex = join(projectRoot, 'apps', 'web', 'dist', 'index.html');\n if (!existsSync(webIndex)) {\n this.warn(`Web bundle not found at ${webIndex}. Dashboard at :${port} will 404 until you build apps/web.`);\n }\n if (!noProxy) {\n const gatewayEntry = join(projectRoot, 'apps', 'cli', 'dist', 'lib', 'gateway-entry.js');\n if (!existsSync(gatewayEntry)) {\n this.error(`Gateway entry not found at ${gatewayEntry}. Re-run without --skip-build, or: pnpm --filter @rulemetric/cli build`);\n }\n }\n\n // \u2500\u2500 Clean up conflicting PID-file-managed or orphan processes \u2500\n // hooks install + proxy start use PID files; we replace those with\n // launchd-managed lifecycles so both ports need to be free before\n // bootstrap. With --force we also clear whatever is squatting on\n // the API port.\n if (flags.force) {\n this.killStaleProcess(port, null, 'API on :' + port);\n }\n if (!noProxy) {\n this.killStaleProcess(GATEWAY_PORT, join(CONFIG_DIR, 'gateway.pid'), 'gateway');\n this.killStaleProcess(PROXY_PORT, join(CONFIG_DIR, 'proxy.pid'), 'mitmproxy');\n }\n\n // Create dirs\n mkdirSync(join(homedir(), 'Library', 'LaunchAgents'), { recursive: true });\n mkdirSync(LOG_DIR, { recursive: true });\n\n const uid = execSync('id -u', { encoding: 'utf-8' }).trim();\n\n // \u2500\u2500 Install API service \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const apiPlist = this.buildPlist({ port, projectRoot, nodePath: nodePath!, pnpmPath: pnpmPath!, envVars });\n this.bootoutAndWrite(uid, PLIST_PATH, apiPlist, 'API');\n this.bootstrapPlist(uid, PLIST_PATH, 'API');\n\n // Wait for API to come up\n this.log(`[..] Waiting for API on :${port}...`);\n const apiStarted = await this.waitForApi(port, 15);\n if (apiStarted) {\n this.log(`[OK] RuleMetric API running on :${port}`);\n if (existsSync(webIndex)) {\n this.log(`[OK] Dashboard available at http://localhost:${port}`);\n }\n } else {\n this.warn('API did not respond within 15s. Check logs:');\n this.log(` tail -f ${LOG_DIR}/api-stdout.log`);\n this.log(` tail -f ${LOG_DIR}/api-stderr.log`);\n }\n\n // \u2500\u2500 Install worker service \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n this.log('');\n this.log('Installing worker service...');\n // Reap detached, unsupervised evals-agent orphans before bootstrapping the\n // worker \u2014 they hold no listening port, so killStaleProcess can't find them\n // (a fleet of 4 ran 3+ days and helped saturate the pool). RCA 2026-06-21.\n this.killStaleAgents();\n const workerPlist = this.buildWorkerPlist({\n cliBin: join(projectRoot, 'apps', 'cli', 'bin', 'run.js'),\n cliDist: join(projectRoot, 'apps', 'cli', 'dist'),\n workingDir: projectRoot,\n nodePath: nodePath!,\n pnpmPath: pnpmPath!,\n envVars,\n });\n this.bootoutAndWrite(uid, WORKER_PLIST_PATH, workerPlist, 'Worker');\n this.bootstrapPlist(uid, WORKER_PLIST_PATH, 'Worker');\n\n // \u2500\u2500 Install gateway + capture-proxy services \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (!noProxy) {\n this.log('');\n this.log('Installing gateway + capture proxy services...');\n\n const gatewayPlist = this.buildGatewayPlist({ projectRoot, nodePath: nodePath! });\n this.bootoutAndWrite(uid, GATEWAY_PLIST_PATH, gatewayPlist, 'Gateway');\n this.bootstrapPlist(uid, GATEWAY_PLIST_PATH, 'Gateway');\n\n const proxyPlist = this.buildProxyPlist({\n projectRoot,\n mitmdumpPath: mitmdumpPath!,\n envVars,\n });\n this.bootoutAndWrite(uid, PROXY_PLIST_PATH, proxyPlist, 'Proxy');\n this.bootstrapPlist(uid, PROXY_PLIST_PATH, 'Proxy');\n }\n\n // Phase 4c cleanup: the daily worker-restart agent (04:00 kickstart)\n // existed to dodge the graphile-LISTEN-wedge class observed at ~25h\n // worker uptime. The agent path has no LISTEN socket, so the wedge\n // class is structurally gone \u2014 the daily kickstart is dead weight.\n // Bootout any leftover plist from prior installs so reinstall is a\n // truly clean state.\n if (existsSync(WORKER_RESTART_PLIST_PATH)) {\n try {\n execFileSync('launchctl', ['bootout', `gui/${uid}`, WORKER_RESTART_PLIST_PATH], { encoding: 'utf-8' });\n } catch {\n // Expected if not loaded\n }\n try {\n execFileSync('rm', ['-f', WORKER_RESTART_PLIST_PATH]);\n this.log('[OK] Removed stale daily worker-restart agent (no longer needed on agent path)');\n } catch {\n // Best-effort\n }\n }\n\n this.log('');\n if (noProxy) {\n this.log('Installed: API + worker. Gateway/proxy skipped (--no-proxy).');\n } else {\n this.log('Installed: API + worker + gateway + capture proxy.');\n }\n this.log('All services start at login, restart on crash, and pick up rebuilds via WatchPaths.');\n this.log('');\n this.log('NOTE: Env vars are baked in at install time. If you change');\n this.log('.env.local or credentials, re-run: rulemetric service install --force');\n this.log('');\n this.log('Manage with:');\n this.log(' rulemetric service status \u2014 check if running');\n this.log(' rulemetric service uninstall \u2014 remove all services');\n\n // Ensure the active-org cache reflects the user's current active_org_id\n // before the worker spawns its first capture. Belt-and-suspenders with\n // BaseCommand's guarded refresh.\n await bootstrapActiveOrg();\n\n // Soft tmux capability check \u2014 see hooks/install.ts for the same line.\n const tmux = await detectTmux();\n this.log('');\n if (tmux.available) {\n this.log(`[OK] tmux found${tmux.version ? ` (v${tmux.version})` : ''} \u2014 live send enabled`);\n } else {\n this.log('[!!] tmux not found \u2014 live send will fall back to opening Terminal. Install with: brew install tmux');\n }\n }\n\n /**\n * Worker-only install: a persistent job worker for end users on the\n * globally-installed CLI, with NO repo checkout, NO build, and NO\n * API/gateway/proxy. macOS \u2192 launchd; Linux \u2192 systemd --user. Auth is\n * sourced from ~/.config/rulemetric/env (written by `rulemetric auth\n * login`); the thin-agent worker is HTTP-only (long-polls /api/work/next),\n * so it needs only the API URL + a key/token, never DB credentials.\n */\n private async runWorkerOnly(force: boolean): Promise<void> {\n const platform = process.platform;\n if (platform !== 'darwin' && platform !== 'linux') {\n this.error(`--worker-only supports macOS (launchd) and Linux (systemd) only; detected '${platform}'.`);\n }\n\n // Resolve node + the GLOBAL CLI entry (the installed package root, NOT a\n // repo). oclif's this.config.root is the package directory; bin/run.js is\n // shipped in the published tarball (\"files\": [\"bin\", ...]).\n const nodePath = this.findBinary('node');\n if (!nodePath) {\n this.error('Could not find `node` in PATH.');\n }\n const cliBin = join(this.config.root, 'bin', 'run.js');\n if (!existsSync(cliBin)) {\n this.error(\n `Could not locate the installed CLI entry at ${cliBin}. Reinstall the CLI (e.g. npm i -g @rulemetric/cli) and re-run.`,\n );\n }\n const cliDist = join(this.config.root, 'dist');\n\n const envVars = this.loadGlobalWorkerEnv();\n if (!envVars.RULEMETRIC_API_KEY && !envVars.RULEMETRIC_ACCESS_TOKEN) {\n this.error(\n 'Not authenticated \u2014 no RULEMETRIC_API_KEY/RULEMETRIC_ACCESS_TOKEN in ~/.config/rulemetric/env.\\nRun `rulemetric auth login` first, then re-run `rulemetric service install --worker-only`.',\n );\n }\n // Default to prod so a user who never set RULEMETRIC_API_URL still works.\n if (!envVars.RULEMETRIC_API_URL) {\n envVars.RULEMETRIC_API_URL = 'https://rulemetric.com';\n }\n\n mkdirSync(LOG_DIR, { recursive: true });\n // Reap any orphaned foreground `evals agent` the user may have started by\n // hand \u2014 otherwise two workers double-claim jobs.\n this.killStaleAgents();\n\n if (platform === 'darwin') {\n if (existsSync(WORKER_PLIST_PATH) && !force) {\n this.log(`Worker already installed at ${WORKER_PLIST_PATH}.`);\n this.log('Use --force to overwrite, or run: rulemetric service status');\n return;\n }\n mkdirSync(join(homedir(), 'Library', 'LaunchAgents'), { recursive: true });\n const uid = execSync('id -u', { encoding: 'utf-8' }).trim();\n const plist = this.buildWorkerPlist({\n cliBin,\n cliDist,\n workingDir: homedir(),\n nodePath: nodePath!,\n nodeEnv: 'production',\n envVars,\n });\n this.bootoutAndWrite(uid, WORKER_PLIST_PATH, plist, 'Worker');\n this.bootstrapPlist(uid, WORKER_PLIST_PATH, 'Worker');\n this.log('');\n this.log('[OK] Worker installed (launchd). Starts at login, restarts on crash.');\n this.log(` Logs: tail -f ${join(LOG_DIR, 'worker-stdout.log')}`);\n } else {\n // Linux: systemd --user unit.\n if (existsSync(WORKER_SYSTEMD_PATH) && !force) {\n this.log(`Worker already installed at ${WORKER_SYSTEMD_PATH}.`);\n this.log(`Use --force to overwrite, or: systemctl --user status ${WORKER_SYSTEMD_UNIT}`);\n return;\n }\n mkdirSync(WORKER_SYSTEMD_DIR, { recursive: true });\n const unit = this.buildWorkerSystemdUnit({ cliBin, nodePath: nodePath!, envVars });\n writeFileSync(WORKER_SYSTEMD_PATH, unit, { mode: 0o600 });\n this.log(`[OK] Wrote ${WORKER_SYSTEMD_PATH}`);\n try {\n execFileSync('systemctl', ['--user', 'daemon-reload']);\n execFileSync('systemctl', ['--user', 'enable', '--now', WORKER_SYSTEMD_UNIT]);\n this.log('[OK] Worker enabled + started (systemctl --user).');\n this.log(' Survive logout/reboot: sudo loginctl enable-linger \"$USER\"');\n this.log(` Logs: journalctl --user -u ${WORKER_SYSTEMD_UNIT} -f`);\n } catch (err) {\n this.warn(\n `Wrote the unit but \\`systemctl --user\\` failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n this.log(\n `Enable it manually: systemctl --user daemon-reload && systemctl --user enable --now ${WORKER_SYSTEMD_UNIT}`,\n );\n }\n }\n\n this.log('');\n this.log('Manage with:');\n this.log(' rulemetric service status \u2014 check if running');\n this.log(' rulemetric service uninstall \u2014 remove the worker');\n\n // Keep the active-org cache warm for the worker's first capture.\n await bootstrapActiveOrg();\n }\n\n /**\n * Load worker env from ~/.config/rulemetric/env only (no repo .env.local).\n * Filtered by ALLOWED_ENV_VARS, same as the full install's loader.\n */\n private loadGlobalWorkerEnv(): Record<string, string> {\n const vars: Record<string, string> = {};\n const globalEnv = join(CONFIG_DIR, 'env');\n if (existsSync(globalEnv)) {\n for (const line of readFileSync(globalEnv, 'utf-8').split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eqIdx = trimmed.indexOf('=');\n if (eqIdx > 0) {\n vars[trimmed.slice(0, eqIdx)] = trimmed.slice(eqIdx + 1);\n }\n }\n }\n return Object.fromEntries(Object.entries(vars).filter(([k]) => ALLOWED_ENV_VARS.has(k)));\n }\n\n /** systemd --user unit for the Linux worker-only path. */\n private buildWorkerSystemdUnit(opts: {\n cliBin: string;\n nodePath: string;\n envVars: Record<string, string>;\n }): string {\n const { cliBin, nodePath, envVars } = opts;\n const env: Record<string, string> = {\n ...envVars,\n NODE_ENV: 'production',\n // Mirror the launchd worker's pooler bound (RCA 2026-06-21).\n PG_POOL_MAX: envVars.PG_POOL_MAX ?? '3',\n PG_IDLE_TIMEOUT: envVars.PG_IDLE_TIMEOUT ?? '10',\n };\n // systemd Environment= values: wrap in double quotes and escape embedded\n // quotes/backslashes. Tokens/URLs carry neither, but be defensive.\n const envLines = Object.entries(env)\n .map(([k, v]) => `Environment=\"${k}=${v.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\"`)\n .join('\\n');\n return `[Unit]\nDescription=RuleMetric job worker (evals agent)\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nExecStart=${nodePath} ${cliBin} evals agent\nRestart=on-failure\nRestartSec=15\n${envLines}\n\n[Install]\nWantedBy=default.target\n`;\n }\n\n private findProjectRoot(): string | null {\n // Check env var override\n if (process.env.RULEMETRIC_PROJECT_ROOT) {\n return process.env.RULEMETRIC_PROJECT_ROOT;\n }\n // Walk up from cwd looking for the monorepo marker\n let dir = process.cwd();\n for (let i = 0; i < 10; i++) {\n if (existsSync(join(dir, 'apps', 'api', 'package.json'))) {\n return dir;\n }\n const parent = join(dir, '..');\n if (parent === dir) break;\n dir = parent;\n }\n return null;\n }\n\n private findBinary(name: string): string | null {\n try {\n return execFileSync('which', [name], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim() || null;\n } catch {\n return null;\n }\n }\n\n private loadEnvVars(projectRoot: string): Record<string, string> {\n const vars: Record<string, string> = {};\n\n // Load from .env.local (Supabase credentials)\n const envLocal = join(projectRoot, '.env.local');\n if (existsSync(envLocal)) {\n for (const line of readFileSync(envLocal, 'utf-8').split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eqIdx = trimmed.indexOf('=');\n if (eqIdx > 0) {\n vars[trimmed.slice(0, eqIdx)] = trimmed.slice(eqIdx + 1);\n }\n }\n }\n\n // Load from ~/.config/rulemetric/env (API key)\n const globalEnv = join(CONFIG_DIR, 'env');\n if (existsSync(globalEnv)) {\n for (const line of readFileSync(globalEnv, 'utf-8').split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eqIdx = trimmed.indexOf('=');\n if (eqIdx > 0) {\n vars[trimmed.slice(0, eqIdx)] = trimmed.slice(eqIdx + 1);\n }\n }\n }\n\n // Only keep allowed env vars \u2014 never leak unrelated secrets\n return Object.fromEntries(\n Object.entries(vars).filter(([k]) => ALLOWED_ENV_VARS.has(k)),\n );\n }\n\n // Guard: the always-on API + worker MUST use the TRANSACTION-mode pooler\n // (port 6543). A SESSION-mode URL (:5432, 15-slot cap) baked into these\n // services was the trigger of the recurring pool saturation \u2014 a pool-cascade\n // debugging override leaked into the plists through this very installer. Auto-\n // correct the obvious session\u2192transaction port and warn loudly; warn (but do\n // not block) on a direct, non-pooler URL. Pool-exhaustion RCA 2026-06-21.\n private guardAlwaysOnDatabaseUrl(envVars: Record<string, string>): Record<string, string> {\n const url = envVars.DATABASE_URL;\n if (!url) return envVars;\n if (url.includes('pooler.supabase.com:5432')) {\n this.warn(\n 'DATABASE_URL points at the SESSION-mode pooler (:5432, 15-slot cap). The ' +\n 'always-on API/worker must use TRANSACTION mode (:6543) \u2014 auto-correcting ' +\n 'for the installed services. Fix .env.local to silence this warning.',\n );\n return { ...envVars, DATABASE_URL: url.replace('pooler.supabase.com:5432', 'pooler.supabase.com:6543') };\n }\n if (/@db\\.[a-z0-9]+\\.supabase\\.co[:/]/.test(url)) {\n this.warn(\n 'DATABASE_URL is a DIRECT database connection (db.<ref>.supabase.co), not the ' +\n 'pooler. Always-on services should use the transaction pooler ' +\n '(aws-*.pooler.supabase.com:6543) to avoid exhausting Postgres connections.',\n );\n }\n return envVars;\n }\n\n // Reap orphaned, unsupervised `evals agent` worker processes before\n // (re)bootstrapping the launchd worker. The worker holds no listening port,\n // so killStaleProcess (port/pidfile based) cannot find detached agents \u2014 a\n // fleet of 4 ran 3+ days and helped saturate the pool (RCA 2026-06-21). pgrep\n // the command line and SIGTERM every match except ourselves; the agent's own\n // single-instance lock then keeps a fresh fleet from forming.\n private killStaleAgents(): void {\n try {\n const out = execFileSync('pgrep', ['-f', 'bin/run.js evals agent'], {\n encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'],\n });\n const pids = out.trim().split('\\n').filter(Boolean)\n .map((s) => parseInt(s, 10))\n .filter((p) => !isNaN(p) && p !== process.pid);\n for (const pid of pids) {\n try {\n process.kill(pid, 'SIGTERM');\n this.log(`[OK] Reaped orphan evals agent (PID ${pid})`);\n } catch {\n // already gone\n }\n }\n if (pids.length) spawnSync('sleep', ['1'], { stdio: 'ignore' });\n } catch {\n // pgrep exits non-zero when nothing matches \u2014 fine\n }\n }\n\n // Best-effort cleanup of a process that would conflict with an\n // about-to-bootstrap launchd plist. Tries two routes:\n // (a) the PID file written by the legacy spawn path\n // (gateway-lifecycle / proxy start), if pidFile is non-null\n // (b) anything actively listening on the expected port\n // Either way: SIGTERM + small grace + SIGKILL fallback. Stale PID file\n // gets unlinked.\n private killStaleProcess(port: number, pidFile: string | null, name: string): void {\n let killedAny = false;\n\n if (pidFile && existsSync(pidFile)) {\n try {\n const pidStr = readFileSync(pidFile, 'utf-8').trim();\n const pid = parseInt(pidStr, 10);\n if (!isNaN(pid)) {\n try {\n process.kill(pid, 'SIGTERM');\n killedAny = true;\n this.log(`[OK] Stopped existing ${name} (PID ${pid} from ${pidFile})`);\n } catch {\n // Already dead \u2014 fine\n }\n }\n } catch {\n // Unreadable PID file\n }\n try { execFileSync('rm', ['-f', pidFile]); } catch { /* best-effort */ }\n }\n\n // Port listener (catches orphans not in the PID file, and the foreground\n // dev API when --force is set on the API port)\n try {\n const result = execFileSync('lsof', ['-ti', `:${port}`, '-sTCP:LISTEN'], {\n encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'],\n });\n const pids = result.trim().split('\\n').filter(Boolean);\n for (const pidStr of pids) {\n const pid = parseInt(pidStr, 10);\n if (!isNaN(pid)) {\n try {\n process.kill(pid, 'SIGTERM');\n killedAny = true;\n this.log(`[OK] Stopped orphan on :${port} (PID ${pid})`);\n } catch { /* already gone */ }\n }\n }\n } catch {\n // lsof exits non-zero when nothing matches \u2014 that's fine\n }\n\n if (killedAny) {\n // Brief grace so the port releases before launchd binds it\n spawnSync('sleep', ['1'], { stdio: 'ignore' });\n }\n }\n\n private bootoutAndWrite(uid: string, plistPath: string, content: string, name: string): void {\n // First install of a service has no prior load \u2192 launchctl bootout exits\n // non-zero. The messaging is inconsistent across macOS versions (\"Could\n // not find specified service\" on some, \"Boot-out failed: 5: Input/output\n // error\" on others). We treat all of these as \"nothing to unload\" and\n // only warn on errors that don't look like that family.\n const benignBootoutPatterns = [\n 'Could not find specified service',\n 'No such process',\n '3: No such process',\n '5: Input/output error',\n ];\n try {\n execFileSync('launchctl', ['bootout', `gui/${uid}`, plistPath], {\n encoding: 'utf-8',\n // Capture stderr instead of letting it print to the user's terminal.\n // The plist was either already-loaded (we unload it cleanly) or not\n // loaded (we want to swallow the error) \u2014 the user shouldn't see\n // launchctl's raw output either way.\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n this.log(`[OK] Unloaded existing ${name} service`);\n } catch (err) {\n const msg = (err as Error).message || '';\n const stderr = ((err as { stderr?: Buffer | string }).stderr ?? '').toString();\n const combined = `${msg}\\n${stderr}`;\n if (!benignBootoutPatterns.some((p) => combined.includes(p))) {\n this.warn(`Could not unload existing ${name} service: ${msg.trim()}`);\n }\n }\n writeFileSync(plistPath, content, { mode: 0o600 });\n this.log(`[OK] ${name} plist written to ${plistPath}`);\n }\n\n private bootstrapPlist(uid: string, plistPath: string, name: string): void {\n try {\n execFileSync('launchctl', ['bootstrap', `gui/${uid}`, plistPath]);\n this.log(`[OK] ${name} service loaded into launchd`);\n } catch (bootstrapErr) {\n try {\n execFileSync('launchctl', ['load', plistPath]);\n this.log(`[OK] ${name} service loaded into launchd (legacy)`);\n } catch (loadErr) {\n this.warn(\n `Could not load ${name} service.\\n` +\n ` bootstrap: ${(bootstrapErr as Error).message.trim()}\\n` +\n ` load: ${(loadErr as Error).message.trim()}`,\n );\n }\n }\n }\n\n private buildPlist(opts: {\n port: number;\n projectRoot: string;\n nodePath: string;\n pnpmPath: string;\n envVars: Record<string, string>;\n }): string {\n const { port, projectRoot, nodePath, pnpmPath, envVars } = opts;\n\n // Derive PATH from resolved binary locations (works with nvm, volta, fnm, homebrew)\n const pathDirs = [...new Set([dirname(nodePath), dirname(pnpmPath), '/usr/local/bin', '/usr/bin', '/bin'])];\n\n // Build environment dict entries. NODE_ENV=production mirrors fly.toml\n // so the bundled web build is served exactly as it is on rulemetric.com,\n // and middleware paths that branch on NODE_ENV match prod behavior\n // locally (rate-limits, error verbosity, etc.).\n const envEntries = Object.entries({\n ...envVars,\n PORT: String(port),\n NODE_ENV: 'production',\n PATH: pathDirs.join(':'),\n // Pool resilience for the always-on API (NOT the worker, which runs\n // legitimately long analytics queries). A wedged Supavisor socket with\n // the default max=3 pool and no query timeout hangs every authenticated\n // request \u2014 the 2026-06-09 login-hang. statement_timeout lets Postgres\n // cancel a stuck query so the connection returns to the pool (verified\n // honored through the transaction pooler); the larger pool adds headroom.\n // Both env-overridable. See packages/db/src/client.ts.\n PG_POOL_MAX: envVars.PG_POOL_MAX ?? '10',\n PG_STATEMENT_TIMEOUT_MS: envVars.PG_STATEMENT_TIMEOUT_MS ?? '10000',\n // Keep the pool warm so the API stops reconnecting (and re-resolving the\n // rotating pooler ELB) on nearly every request \u2014 the churn that exposed\n // it to intermittent CONNECT_TIMEOUT/ENOTFOUND. Worker/tests keep the\n // conservative 5s default. See packages/db/src/client.ts (2026-06-09).\n PG_IDLE_TIMEOUT: envVars.PG_IDLE_TIMEOUT ?? '30',\n // Silence per-span stdout dumps on the local API \u2014 it has no OTLP\n // endpoint and a fixed (now rotated) log file; the ConsoleSpanExporter\n // grew api-stdout to 370MB / ~400k spans. Fly leaves this unset so its\n // console spans stay grep-able in the managed log stream.\n OTEL_SPAN_EXPORT: envVars.OTEL_SPAN_EXPORT ?? 'none',\n })\n .map(([k, v]) => ` <key>${escapeXml(k)}</key>\\n <string>${escapeXml(v)}</string>`)\n .join('\\n');\n\n // Run the compiled API directly \u2014 same shape as the prod Dockerfile\n // CMD. Avoids tsx startup cost on every launchd restart, and serves\n // apps/web/dist from the same process so :3000 is \"API + dashboard\",\n // matching rulemetric.com. WatchPaths on apps/api/dist auto-bounces\n // the service on rebuild, so the dev cycle is: edit src \u2192 pnpm build\n // \u2192 launchd kicks the new code automatically (no manual kickstart).\n const apiEntry = join(projectRoot, 'apps', 'api', 'dist', 'index.js');\n const apiDist = join(projectRoot, 'apps', 'api', 'dist');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${PLIST_LABEL}</string>\n\n <key>ProgramArguments</key>\n <array>\n <string>${escapeXml(nodePath)}</string>\n <string>${escapeXml(apiEntry)}</string>\n </array>\n\n <key>WorkingDirectory</key>\n <string>${escapeXml(projectRoot)}</string>\n\n <key>EnvironmentVariables</key>\n <dict>\n${envEntries}\n </dict>\n\n <key>RunAtLoad</key>\n <true/>\n\n <key>KeepAlive</key>\n <dict>\n <key>SuccessfulExit</key>\n <false/>\n </dict>\n\n <key>ThrottleInterval</key>\n <integer>10</integer>\n\n <key>WatchPaths</key>\n <array>\n <string>${escapeXml(apiDist)}</string>\n </array>\n\n <key>StandardOutPath</key>\n <string>${escapeXml(join(LOG_DIR, 'api-stdout.log'))}</string>\n\n <key>StandardErrorPath</key>\n <string>${escapeXml(join(LOG_DIR, 'api-stderr.log'))}</string>\n\n <key>ProcessType</key>\n <string>Background</string>\n</dict>\n</plist>\n`;\n }\n\n private buildWorkerPlist(opts: {\n cliBin: string;\n cliDist: string;\n workingDir: string;\n nodePath: string;\n pnpmPath?: string;\n nodeEnv?: string;\n envVars: Record<string, string>;\n }): string {\n const { cliBin, cliDist, workingDir, nodePath, pnpmPath, envVars } = opts;\n // Repo install runs as the dev host \u2192 'development'. --worker-only is an\n // end-user worker against prod \u2192 'production'.\n const nodeEnv = opts.nodeEnv ?? 'development';\n\n const pathDirs = [\n ...new Set(\n [dirname(nodePath), pnpmPath ? dirname(pnpmPath) : null, '/usr/local/bin', '/usr/bin', '/bin'].filter(\n (d): d is string => Boolean(d),\n ),\n ),\n ];\n\n const envEntries = Object.entries({\n ...envVars,\n NODE_ENV: nodeEnv,\n PATH: pathDirs.join(':'),\n // Bound the worker's pooler footprint. The worker is mostly idle (HTTP\n // long-poll); an UNPINNED pool plus the previously-missing single-instance\n // guard let an orphaned fleet of agents each hold a pool and saturate\n // Supavisor's 200-client cap. A small pool + short idle timeout releases\n // sessions promptly. Pool-exhaustion RCA 2026-06-21.\n PG_POOL_MAX: envVars.PG_POOL_MAX ?? '3',\n PG_IDLE_TIMEOUT: envVars.PG_IDLE_TIMEOUT ?? '10',\n })\n .map(([k, v]) => ` <key>${escapeXml(k)}</key>\\n <string>${escapeXml(v)}</string>`)\n .join('\\n');\n\n // Run the CLI bin directly via node. Earlier this used\n // `pnpm --filter @rulemetric/cli exec rulemetric` but pnpm-exec couldn't\n // resolve the workspace bin symlink reliably under launchd, crash-looping\n // with `Command \"rulemetric\" not found`. Direct node invocation is both\n // simpler (one process less) and avoids the bin-symlink lookup entirely.\n // cliBin/cliDist are passed in: the repo install points them at\n // <repo>/apps/cli/{bin/run.js,dist}; --worker-only points them at the\n // globally-installed package (<config.root>/{bin/run.js,dist}). WatchPaths\n // on cliDist bounces the worker when the code changes \u2014 a local `pnpm\n // build` for the repo path, an `npm update -g` for the global path.\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${WORKER_PLIST_LABEL}</string>\n\n <key>ProgramArguments</key>\n <array>\n <string>${escapeXml(nodePath)}</string>\n <string>${escapeXml(cliBin)}</string>\n <string>evals</string>\n <string>agent</string>\n </array>\n\n <key>WorkingDirectory</key>\n <string>${escapeXml(workingDir)}</string>\n\n <key>EnvironmentVariables</key>\n <dict>\n${envEntries}\n </dict>\n\n <key>RunAtLoad</key>\n <true/>\n\n <key>KeepAlive</key>\n <dict>\n <key>SuccessfulExit</key>\n <false/>\n </dict>\n\n <key>ThrottleInterval</key>\n <integer>15</integer>\n\n <key>WatchPaths</key>\n <array>\n <string>${escapeXml(cliDist)}</string>\n </array>\n\n <key>StandardOutPath</key>\n <string>${escapeXml(join(LOG_DIR, 'worker-stdout.log'))}</string>\n\n <key>StandardErrorPath</key>\n <string>${escapeXml(join(LOG_DIR, 'worker-stderr.log'))}</string>\n\n <key>ProcessType</key>\n <string>Background</string>\n</dict>\n</plist>\n`;\n }\n\n // CONNECT-proxy router that fronts mitmproxy on :8787. Always-on so\n // .claude/settings.json's HTTPS_PROXY pointer never resolves to a dead\n // socket (which would break Claude Code entirely \u2014 see CLAUDE.md).\n // NOT watched on rebuild: bouncing the gateway mid-request would drop\n // in-flight streaming responses. Gateway code changes rarely; on\n // upgrade, the user can `launchctl kickstart gui/<uid>/com.rulemetric.gateway`.\n private buildGatewayPlist(opts: {\n projectRoot: string;\n nodePath: string;\n }): string {\n const { projectRoot, nodePath } = opts;\n const gatewayEntry = join(projectRoot, 'apps', 'cli', 'dist', 'lib', 'gateway-entry.js');\n const cliBin = join(projectRoot, 'apps', 'cli', 'bin', 'run.js');\n\n const pathDirs = [...new Set([dirname(nodePath), '/usr/local/bin', '/usr/bin', '/bin'])];\n\n // Gateway only needs PATH + the CLI metadata used for drift detection\n // (gateway.ts:43 \u2014 when the addon's on-disk version file diverges from\n // the gateway's runtime version, it re-invokes the CLI to restart\n // mitmproxy). It does NOT need Supabase/auth env vars.\n const env = {\n RULEMETRIC_CLI_VERSION: this.config.version,\n RULEMETRIC_CLI_ENTRY: cliBin,\n PATH: pathDirs.join(':'),\n };\n\n const envEntries = Object.entries(env)\n .map(([k, v]) => ` <key>${escapeXml(k)}</key>\\n <string>${escapeXml(v)}</string>`)\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${GATEWAY_PLIST_LABEL}</string>\n\n <key>ProgramArguments</key>\n <array>\n <string>${escapeXml(nodePath)}</string>\n <string>${escapeXml(gatewayEntry)}</string>\n </array>\n\n <key>WorkingDirectory</key>\n <string>${escapeXml(projectRoot)}</string>\n\n <key>EnvironmentVariables</key>\n <dict>\n${envEntries}\n </dict>\n\n <key>RunAtLoad</key>\n <true/>\n\n <key>KeepAlive</key>\n <dict>\n <key>SuccessfulExit</key>\n <false/>\n </dict>\n\n <key>ThrottleInterval</key>\n <integer>10</integer>\n\n <key>StandardOutPath</key>\n <string>${escapeXml(join(LOG_DIR, 'gateway-stdout.log'))}</string>\n\n <key>StandardErrorPath</key>\n <string>${escapeXml(join(LOG_DIR, 'gateway-stderr.log'))}</string>\n\n <key>ProcessType</key>\n <string>Background</string>\n</dict>\n</plist>\n`;\n }\n\n // mitmproxy with the RuleMetric capture addon. Same invocation as\n // `rulemetric proxy start` (see commands/proxy/start.ts:308) so the\n // launchd path and the CLI path are interchangeable. We don't WatchPath\n // the Python addon \u2014 mitmdump has its own addon reload that handles\n // edits without a full process restart.\n private buildProxyPlist(opts: {\n projectRoot: string;\n mitmdumpPath: string;\n envVars: Record<string, string>;\n }): string {\n const { projectRoot, mitmdumpPath, envVars } = opts;\n const addonPath = join(projectRoot, 'packages', 'proxy', 'addon', 'rulemetric_addon.py');\n\n const pathDirs = [...new Set([dirname(mitmdumpPath), '/usr/local/bin', '/usr/bin', '/bin'])];\n\n // Proxy needs:\n // - auth env vars (so its reporter can POST events to the API)\n // - RULEMETRIC_PROJECT_PATH (writes session-meta + addon-version files)\n // - RULEMETRIC_CLI_VERSION (gateway uses this to detect drift)\n const env = {\n ...envVars,\n RULEMETRIC_PROJECT_PATH: projectRoot,\n RULEMETRIC_CLI_VERSION: this.config.version,\n PATH: pathDirs.join(':'),\n };\n\n const envEntries = Object.entries(env)\n .map(([k, v]) => ` <key>${escapeXml(k)}</key>\\n <string>${escapeXml(v)}</string>`)\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${PROXY_PLIST_LABEL}</string>\n\n <key>ProgramArguments</key>\n <array>\n <string>${escapeXml(mitmdumpPath)}</string>\n <string>--listen-port</string>\n <string>${PROXY_PORT}</string>\n <string>-s</string>\n <string>${escapeXml(addonPath)}</string>\n <string>--set</string>\n <string>console_eventlog_verbosity=info</string>\n </array>\n\n <key>WorkingDirectory</key>\n <string>${escapeXml(projectRoot)}</string>\n\n <key>EnvironmentVariables</key>\n <dict>\n${envEntries}\n </dict>\n\n <key>RunAtLoad</key>\n <true/>\n\n <key>KeepAlive</key>\n <dict>\n <key>SuccessfulExit</key>\n <false/>\n </dict>\n\n <key>ThrottleInterval</key>\n <integer>15</integer>\n\n <key>StandardOutPath</key>\n <string>${escapeXml(join(LOG_DIR, 'proxy-stdout.log'))}</string>\n\n <key>StandardErrorPath</key>\n <string>${escapeXml(join(LOG_DIR, 'proxy-stderr.log'))}</string>\n\n <key>ProcessType</key>\n <string>Background</string>\n</dict>\n</plist>\n`;\n }\n\n private async waitForApi(port: number, timeoutSecs: number): Promise<boolean> {\n let lastError: string | null = null;\n for (let i = 0; i < timeoutSecs; i++) {\n try {\n const res = await fetch(`http://localhost:${port}/health`);\n if (res.ok) return true;\n lastError = `health check returned ${res.status}`;\n } catch (err) {\n lastError = (err as Error).message;\n }\n await new Promise(r => setTimeout(r, 1000));\n }\n if (lastError) {\n this.warn(`Last health check error: ${lastError}`);\n }\n return false;\n }\n}\n\nfunction escapeXml(s: string): string {\n return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -26,11 +26,17 @@ var SERVICES = [
|
|
|
26
26
|
function plistPathFor(label) {
|
|
27
27
|
return join(homedir(), "Library", "LaunchAgents", `${label}.plist`);
|
|
28
28
|
}
|
|
29
|
+
var WORKER_SYSTEMD_UNIT = "rulemetric-worker.service";
|
|
30
|
+
var WORKER_SYSTEMD_PATH = join(homedir(), ".config", "systemd", "user", WORKER_SYSTEMD_UNIT);
|
|
29
31
|
var ServiceUninstall = class extends BaseCommand {
|
|
30
|
-
static description = "Remove
|
|
32
|
+
static description = "Remove RuleMetric services (macOS launchd: API/worker/gateway/proxy; Linux: the --worker-only systemd unit)";
|
|
31
33
|
async run() {
|
|
34
|
+
if (process.platform === "linux") {
|
|
35
|
+
this.uninstallLinuxWorker();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
32
38
|
if (process.platform !== "darwin") {
|
|
33
|
-
this.error("Service uninstall
|
|
39
|
+
this.error("Service uninstall supports macOS (launchd) and Linux (systemd) only.");
|
|
34
40
|
}
|
|
35
41
|
const installed = SERVICES.filter((s) => existsSync(plistPathFor(s.label)));
|
|
36
42
|
if (installed.length === 0) {
|
|
@@ -80,6 +86,31 @@ var ServiceUninstall = class extends BaseCommand {
|
|
|
80
86
|
this.warn(`Could not remove ${description} plist: ${err.message}. Try: rm "${plistPath}"`);
|
|
81
87
|
}
|
|
82
88
|
}
|
|
89
|
+
uninstallLinuxWorker() {
|
|
90
|
+
if (!existsSync(WORKER_SYSTEMD_PATH)) {
|
|
91
|
+
this.log("No RuleMetric worker is installed (no systemd unit found).");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
execFileSync("systemctl", ["--user", "disable", "--now", WORKER_SYSTEMD_UNIT], { encoding: "utf-8" });
|
|
96
|
+
this.log("[OK] Worker stopped + disabled (systemctl --user)");
|
|
97
|
+
} catch (err) {
|
|
98
|
+
this.warn(`Could not disable the worker unit: ${err.message}`);
|
|
99
|
+
this.warn(` systemctl --user disable --now ${WORKER_SYSTEMD_UNIT}`);
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
unlinkSync(WORKER_SYSTEMD_PATH);
|
|
103
|
+
this.log(`[OK] Removed ${WORKER_SYSTEMD_PATH}`);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
this.warn(`Could not remove ${WORKER_SYSTEMD_PATH}: ${err.message}. Try: rm "${WORKER_SYSTEMD_PATH}"`);
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
execFileSync("systemctl", ["--user", "daemon-reload"], { encoding: "utf-8" });
|
|
109
|
+
} catch {
|
|
110
|
+
}
|
|
111
|
+
this.log("");
|
|
112
|
+
this.log("RuleMetric worker removed. It will no longer start automatically.");
|
|
113
|
+
}
|
|
83
114
|
};
|
|
84
115
|
export {
|
|
85
116
|
ServiceUninstall as default
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/commands/service/uninstall.ts"],
|
|
4
|
-
"sourcesContent": ["import { existsSync, unlinkSync } from 'node:fs';\nimport { execSync, execFileSync } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\n\n// Every plist label that `service install` may have written. Keeping these in\n// one ordered list lets uninstall march through them with the same teardown\n// logic; if install ever adds a fifth service, this is the only file that\n// needs to learn about it.\nconst SERVICES: Array<{ label: string; description: string }> = [\n { label: 'com.rulemetric.api', description: 'API' },\n { label: 'com.rulemetric.worker', description: 'Worker' },\n { label: 'com.rulemetric.gateway', description: 'Gateway' },\n { label: 'com.rulemetric.proxy', description: 'Proxy' },\n // Legacy daily worker-restart agent \u2014 install removes any stale copy,\n // but uninstall still cleans it up so a user running uninstall on an\n // older box gets a fully clean state.\n { label: 'com.rulemetric.worker.restart', description: 'Worker restart agent' },\n];\n\nfunction plistPathFor(label: string): string {\n return join(homedir(), 'Library', 'LaunchAgents', `${label}.plist`);\n}\n\nexport default class ServiceUninstall extends BaseCommand {\n static override description = 'Remove
|
|
5
|
-
"mappings": ";;;;;;;;;;;AAAA,SAAS,YAAY,kBAAkB;AACvC,SAAS,UAAU,oBAAoB;AACvC,SAAS,eAAe;AACxB,SAAS,YAAY;AAOrB,IAAM,WAA0D;AAAA,EAC9D,EAAE,OAAO,sBAA0B,aAAa,MAAU;AAAA,EAC1D,EAAE,OAAO,yBAA0B,aAAa,SAAU;AAAA,EAC1D,EAAE,OAAO,0BAA0B,aAAa,UAAU;AAAA,EAC1D,EAAE,OAAO,wBAA0B,aAAa,QAAU;AAAA;AAAA;AAAA;AAAA,EAI1D,EAAE,OAAO,iCAAiC,aAAa,uBAAuB;AAChF;AAEA,SAAS,aAAa,OAAuB;AAC3C,SAAO,KAAK,QAAQ,GAAG,WAAW,gBAAgB,GAAG,KAAK,QAAQ;AACpE;
|
|
4
|
+
"sourcesContent": ["import { existsSync, unlinkSync } from 'node:fs';\nimport { execSync, execFileSync } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { BaseCommand } from '../../base-command.js';\n\n// Every plist label that `service install` may have written. Keeping these in\n// one ordered list lets uninstall march through them with the same teardown\n// logic; if install ever adds a fifth service, this is the only file that\n// needs to learn about it.\nconst SERVICES: Array<{ label: string; description: string }> = [\n { label: 'com.rulemetric.api', description: 'API' },\n { label: 'com.rulemetric.worker', description: 'Worker' },\n { label: 'com.rulemetric.gateway', description: 'Gateway' },\n { label: 'com.rulemetric.proxy', description: 'Proxy' },\n // Legacy daily worker-restart agent \u2014 install removes any stale copy,\n // but uninstall still cleans it up so a user running uninstall on an\n // older box gets a fully clean state.\n { label: 'com.rulemetric.worker.restart', description: 'Worker restart agent' },\n];\n\nfunction plistPathFor(label: string): string {\n return join(homedir(), 'Library', 'LaunchAgents', `${label}.plist`);\n}\n\n// Linux systemd --user unit written by `service install --worker-only`. Kept in\n// sync with the constants in install.ts.\nconst WORKER_SYSTEMD_UNIT = 'rulemetric-worker.service';\nconst WORKER_SYSTEMD_PATH = join(homedir(), '.config', 'systemd', 'user', WORKER_SYSTEMD_UNIT);\n\nexport default class ServiceUninstall extends BaseCommand {\n static override description = 'Remove RuleMetric services (macOS launchd: API/worker/gateway/proxy; Linux: the --worker-only systemd unit)';\n\n async run(): Promise<void> {\n // Linux: only the worker-only systemd unit can exist here.\n if (process.platform === 'linux') {\n this.uninstallLinuxWorker();\n return;\n }\n if (process.platform !== 'darwin') {\n this.error('Service uninstall supports macOS (launchd) and Linux (systemd) only.');\n }\n\n const installed = SERVICES.filter((s) => existsSync(plistPathFor(s.label)));\n if (installed.length === 0) {\n this.log('No RuleMetric services are installed.');\n return;\n }\n\n const uid = execSync('id -u', { encoding: 'utf-8' }).trim();\n\n for (const svc of installed) {\n const path = plistPathFor(svc.label);\n this.stopService(uid, path, svc.label, svc.description);\n this.removePlist(path, svc.description);\n }\n\n this.log('');\n this.log('All RuleMetric services removed. They will no longer start automatically.');\n this.log('Use scripts/start.sh (dev) or `rulemetric service install` (always-on) to bring them back.');\n }\n\n private stopService(uid: string, plistPath: string, label: string, description: string): void {\n try {\n execFileSync('launchctl', ['bootout', `gui/${uid}`, plistPath], { encoding: 'utf-8' });\n this.log(`[OK] ${description} stopped`);\n return;\n } catch {\n // Fall through to legacy unload\n }\n try {\n execFileSync('launchctl', ['unload', plistPath], { encoding: 'utf-8' });\n this.log(`[OK] ${description} stopped (legacy)`);\n return;\n } catch {\n // Verify whether it's actually running. launchctl list returns the\n // current state; if there's no PID, the service wasn't running and\n // both bootout/unload no-op'd legitimately.\n try {\n const output = execSync(`launchctl list ${label} 2>&1`, { encoding: 'utf-8' });\n if (output.includes('PID')) {\n this.warn(`${description} appears to still be running. Try manually:`);\n this.warn(` launchctl bootout gui/${uid} \"${plistPath}\"`);\n } else {\n this.log(`[OK] ${description} was not running`);\n }\n } catch {\n this.log(`[OK] ${description} was not running`);\n }\n }\n }\n\n private removePlist(plistPath: string, description: string): void {\n try {\n unlinkSync(plistPath);\n this.log(`[OK] Removed ${plistPath}`);\n } catch (err) {\n this.warn(`Could not remove ${description} plist: ${(err as Error).message}. Try: rm \"${plistPath}\"`);\n }\n }\n\n private uninstallLinuxWorker(): void {\n if (!existsSync(WORKER_SYSTEMD_PATH)) {\n this.log('No RuleMetric worker is installed (no systemd unit found).');\n return;\n }\n try {\n execFileSync('systemctl', ['--user', 'disable', '--now', WORKER_SYSTEMD_UNIT], { encoding: 'utf-8' });\n this.log('[OK] Worker stopped + disabled (systemctl --user)');\n } catch (err) {\n this.warn(`Could not disable the worker unit: ${(err as Error).message}`);\n this.warn(` systemctl --user disable --now ${WORKER_SYSTEMD_UNIT}`);\n }\n try {\n unlinkSync(WORKER_SYSTEMD_PATH);\n this.log(`[OK] Removed ${WORKER_SYSTEMD_PATH}`);\n } catch (err) {\n this.warn(`Could not remove ${WORKER_SYSTEMD_PATH}: ${(err as Error).message}. Try: rm \"${WORKER_SYSTEMD_PATH}\"`);\n }\n try {\n execFileSync('systemctl', ['--user', 'daemon-reload'], { encoding: 'utf-8' });\n } catch {\n // best-effort\n }\n this.log('');\n this.log('RuleMetric worker removed. It will no longer start automatically.');\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
loadApiInsights,
|
|
19
19
|
loadLinkedInstructions,
|
|
20
20
|
loadLocalStats
|
|
21
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-46LMTDRE.js";
|
|
22
22
|
import {
|
|
23
23
|
LauncherOverlay
|
|
24
24
|
} from "../chunk-CROYS75W.js";
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
PERIODS,
|
|
34
34
|
PERIOD_LABELS
|
|
35
35
|
} from "../chunk-QJQS6TUN.js";
|
|
36
|
-
import "../chunk-
|
|
36
|
+
import "../chunk-4QGATD5Y.js";
|
|
37
37
|
import {
|
|
38
38
|
readActiveOrg
|
|
39
39
|
} from "../chunk-EKJ2CABV.js";
|
|
@@ -149,7 +149,7 @@ function InteractiveDashboard({ initialLocal, initialApi, initialInstructions, i
|
|
|
149
149
|
return;
|
|
150
150
|
}
|
|
151
151
|
if (input === "n") {
|
|
152
|
-
import("../dist-
|
|
152
|
+
import("../dist-NEECHU47.js").then(({ getLauncher }) => {
|
|
153
153
|
getLauncher("claude_code").launch({ projectPath: process.cwd(), mode: "interactive" });
|
|
154
154
|
});
|
|
155
155
|
return;
|
package/dist/dashboard/data.js
CHANGED
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
loadApiInsights,
|
|
3
3
|
loadLinkedInstructions,
|
|
4
4
|
loadLocalStats
|
|
5
|
-
} from "../chunk-
|
|
6
|
-
import "../chunk-
|
|
5
|
+
} from "../chunk-46LMTDRE.js";
|
|
6
|
+
import "../chunk-4QGATD5Y.js";
|
|
7
7
|
import "../chunk-NSBPE2FW.js";
|
|
8
8
|
export {
|
|
9
9
|
loadApiInsights,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ClaudeCodeAdapter,
|
|
3
|
+
CodexAdapter,
|
|
3
4
|
CopilotAgentAdapter,
|
|
4
5
|
CursorAdapter,
|
|
5
6
|
RuleMetricMcpAdapter,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
buildTmuxSendKeysArgs,
|
|
14
15
|
detectTool,
|
|
15
16
|
discoverAllSessions,
|
|
17
|
+
freshCodexState,
|
|
16
18
|
getAdapter,
|
|
17
19
|
getAllProviders,
|
|
18
20
|
getAvailableLaunchers,
|
|
@@ -22,10 +24,11 @@ import {
|
|
|
22
24
|
sanitizeBranch,
|
|
23
25
|
sanitizeTmuxName,
|
|
24
26
|
worktreePathFor
|
|
25
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-4QGATD5Y.js";
|
|
26
28
|
import "./chunk-NSBPE2FW.js";
|
|
27
29
|
export {
|
|
28
30
|
ClaudeCodeAdapter,
|
|
31
|
+
CodexAdapter,
|
|
29
32
|
CopilotAgentAdapter,
|
|
30
33
|
CursorAdapter,
|
|
31
34
|
RuleMetricMcpAdapter,
|
|
@@ -39,6 +42,7 @@ export {
|
|
|
39
42
|
buildTmuxSendKeysArgs,
|
|
40
43
|
detectTool,
|
|
41
44
|
discoverAllSessions,
|
|
45
|
+
freshCodexState,
|
|
42
46
|
getAdapter,
|
|
43
47
|
getAllProviders,
|
|
44
48
|
getAvailableLaunchers,
|
|
@@ -49,4 +53,4 @@ export {
|
|
|
49
53
|
sanitizeTmuxName,
|
|
50
54
|
worktreePathFor
|
|
51
55
|
};
|
|
52
|
-
//# sourceMappingURL=dist-
|
|
56
|
+
//# sourceMappingURL=dist-NEECHU47.js.map
|
package/dist/lib/agent-loop.js
CHANGED
|
@@ -3,35 +3,35 @@ import {
|
|
|
3
3
|
HANDLERS,
|
|
4
4
|
pollForWork,
|
|
5
5
|
runAgentLoop
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-LCT6MT2V.js";
|
|
7
7
|
import "../chunk-JX5UI6RW.js";
|
|
8
|
-
import "../chunk-
|
|
9
|
-
import "../chunk-
|
|
10
|
-
import "../chunk-
|
|
11
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-EHABPBGH.js";
|
|
9
|
+
import "../chunk-XMWHTOCC.js";
|
|
10
|
+
import "../chunk-Y2ARLMWZ.js";
|
|
11
|
+
import "../chunk-FUS5C4SB.js";
|
|
12
12
|
import "../chunk-W53GKIZQ.js";
|
|
13
|
-
import "../chunk-
|
|
13
|
+
import "../chunk-YEBBX4HQ.js";
|
|
14
14
|
import "../chunk-XZXS2W24.js";
|
|
15
|
-
import "../chunk-
|
|
15
|
+
import "../chunk-RQ2TMLKG.js";
|
|
16
|
+
import "../chunk-YBNW7ARH.js";
|
|
17
|
+
import "../chunk-FZKLLNDS.js";
|
|
18
|
+
import "../chunk-2BCPVUSF.js";
|
|
19
|
+
import "../chunk-K3BFZ7KG.js";
|
|
20
|
+
import "../chunk-QSN77T7C.js";
|
|
21
|
+
import "../chunk-42A3HEEH.js";
|
|
16
22
|
import "../chunk-BD7CY42U.js";
|
|
17
23
|
import "../chunk-QJXAULRC.js";
|
|
18
24
|
import "../chunk-DGHWRQXL.js";
|
|
19
25
|
import "../chunk-J7N3DLH6.js";
|
|
20
26
|
import "../chunk-52WIYRZH.js";
|
|
21
27
|
import "../chunk-IWYCRLEK.js";
|
|
22
|
-
import "../chunk-B5KNTGMJ.js";
|
|
23
|
-
import "../chunk-3TIMQ3O6.js";
|
|
24
|
-
import "../chunk-YNIH3KMU.js";
|
|
25
|
-
import "../chunk-QN34TGD3.js";
|
|
26
|
-
import "../chunk-RQ2TMLKG.js";
|
|
27
|
-
import "../chunk-YBNW7ARH.js";
|
|
28
|
-
import "../chunk-FZKLLNDS.js";
|
|
29
|
-
import "../chunk-K3BFZ7KG.js";
|
|
30
|
-
import "../chunk-NIDLJO6F.js";
|
|
31
|
-
import "../chunk-7FAA3YNB.js";
|
|
32
28
|
import "../chunk-EKP32DLN.js";
|
|
29
|
+
import "../chunk-4QGATD5Y.js";
|
|
33
30
|
import "../chunk-OQSQC7VB.js";
|
|
34
31
|
import "../chunk-KRBQLMOP.js";
|
|
32
|
+
import "../chunk-3TIMQ3O6.js";
|
|
33
|
+
import "../chunk-YNIH3KMU.js";
|
|
34
|
+
import "../chunk-QN34TGD3.js";
|
|
35
35
|
import "../chunk-EJOI2ZGU.js";
|
|
36
36
|
import "../chunk-KZRFUXGF.js";
|
|
37
37
|
import "../chunk-NSBPE2FW.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
processAnnouncement
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-2BCPVUSF.js";
|
|
4
|
+
import "../../chunk-QSN77T7C.js";
|
|
5
|
+
import "../../chunk-42A3HEEH.js";
|
|
4
6
|
import "../../chunk-DGHWRQXL.js";
|
|
5
|
-
import "../../chunk-NIDLJO6F.js";
|
|
6
|
-
import "../../chunk-7FAA3YNB.js";
|
|
7
7
|
import "../../chunk-NSBPE2FW.js";
|
|
8
8
|
export {
|
|
9
9
|
processAnnouncement
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
processChangelog
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-MTLAIJED.js";
|
|
4
4
|
import "../../chunk-K3BFZ7KG.js";
|
|
5
|
-
import "../../chunk-
|
|
6
|
-
import "../../chunk-
|
|
5
|
+
import "../../chunk-QSN77T7C.js";
|
|
6
|
+
import "../../chunk-42A3HEEH.js";
|
|
7
7
|
import "../../chunk-NSBPE2FW.js";
|
|
8
8
|
export {
|
|
9
9
|
processChangelog
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
processInsights
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-EHABPBGH.js";
|
|
4
4
|
import "../../chunk-XZXS2W24.js";
|
|
5
|
-
import "../../chunk-DGHWRQXL.js";
|
|
6
5
|
import "../../chunk-FZKLLNDS.js";
|
|
6
|
+
import "../../chunk-DGHWRQXL.js";
|
|
7
7
|
import "../../chunk-KRBQLMOP.js";
|
|
8
8
|
import "../../chunk-EJOI2ZGU.js";
|
|
9
9
|
import "../../chunk-KZRFUXGF.js";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
processLaunch,
|
|
3
3
|
resolveWorktreeForLaunch
|
|
4
|
-
} from "../../chunk-
|
|
5
|
-
import "../../chunk-
|
|
4
|
+
} from "../../chunk-XMWHTOCC.js";
|
|
5
|
+
import "../../chunk-FUS5C4SB.js";
|
|
6
6
|
import "../../chunk-DGHWRQXL.js";
|
|
7
|
-
import "../../chunk-
|
|
7
|
+
import "../../chunk-4QGATD5Y.js";
|
|
8
8
|
import "../../chunk-KRBQLMOP.js";
|
|
9
9
|
import "../../chunk-EJOI2ZGU.js";
|
|
10
10
|
import "../../chunk-KZRFUXGF.js";
|