thevoidforge 21.0.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/scripts/vault-read.d.ts +11 -0
- package/dist/scripts/vault-read.js +89 -0
- package/dist/scripts/voidforge.d.ts +20 -0
- package/dist/scripts/voidforge.js +404 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/wizard/api/auth.d.ts +5 -0
- package/dist/wizard/api/auth.js +133 -0
- package/dist/wizard/api/blueprint.d.ts +45 -0
- package/dist/wizard/api/blueprint.js +184 -0
- package/dist/wizard/api/cloud-providers.d.ts +16 -0
- package/dist/wizard/api/cloud-providers.js +363 -0
- package/dist/wizard/api/credentials.d.ts +1 -0
- package/dist/wizard/api/credentials.js +258 -0
- package/dist/wizard/api/danger-room.d.ts +18 -0
- package/dist/wizard/api/danger-room.js +401 -0
- package/dist/wizard/api/deploy.d.ts +4 -0
- package/dist/wizard/api/deploy.js +164 -0
- package/dist/wizard/api/prd.d.ts +1 -0
- package/dist/wizard/api/prd.js +363 -0
- package/dist/wizard/api/project.d.ts +1 -0
- package/dist/wizard/api/project.js +239 -0
- package/dist/wizard/api/projects.d.ts +6 -0
- package/dist/wizard/api/projects.js +648 -0
- package/dist/wizard/api/provision.d.ts +4 -0
- package/dist/wizard/api/provision.js +535 -0
- package/dist/wizard/api/terminal.d.ts +25 -0
- package/dist/wizard/api/terminal.js +241 -0
- package/dist/wizard/api/users.d.ts +6 -0
- package/dist/wizard/api/users.js +244 -0
- package/dist/wizard/api/war-room.d.ts +14 -0
- package/dist/wizard/api/war-room.js +45 -0
- package/dist/wizard/lib/ad-platform-core.d.ts +6 -0
- package/dist/wizard/lib/ad-platform-core.js +1 -0
- package/dist/wizard/lib/adapters/index.d.ts +52 -0
- package/dist/wizard/lib/adapters/index.js +38 -0
- package/dist/wizard/lib/adapters/sandbox-bank.d.ts +17 -0
- package/dist/wizard/lib/adapters/sandbox-bank.js +77 -0
- package/dist/wizard/lib/adapters/sandbox.d.ts +39 -0
- package/dist/wizard/lib/adapters/sandbox.js +174 -0
- package/dist/wizard/lib/adapters/stripe.d.ts +19 -0
- package/dist/wizard/lib/adapters/stripe.js +143 -0
- package/dist/wizard/lib/adapters/types.d.ts +9 -0
- package/dist/wizard/lib/adapters/types.js +10 -0
- package/dist/wizard/lib/agent-memory.d.ts +36 -0
- package/dist/wizard/lib/agent-memory.js +114 -0
- package/dist/wizard/lib/anomaly-detection.d.ts +59 -0
- package/dist/wizard/lib/anomaly-detection.js +122 -0
- package/dist/wizard/lib/anthropic.d.ts +21 -0
- package/dist/wizard/lib/anthropic.js +105 -0
- package/dist/wizard/lib/asset-scanner.d.ts +23 -0
- package/dist/wizard/lib/asset-scanner.js +107 -0
- package/dist/wizard/lib/audit-log.d.ts +23 -0
- package/dist/wizard/lib/audit-log.js +70 -0
- package/dist/wizard/lib/autonomy-controller.d.ts +76 -0
- package/dist/wizard/lib/autonomy-controller.js +183 -0
- package/dist/wizard/lib/body-parser.d.ts +2 -0
- package/dist/wizard/lib/body-parser.js +36 -0
- package/dist/wizard/lib/build-analytics.d.ts +39 -0
- package/dist/wizard/lib/build-analytics.js +91 -0
- package/dist/wizard/lib/build-step.d.ts +21 -0
- package/dist/wizard/lib/build-step.js +104 -0
- package/dist/wizard/lib/campaign-proposer.d.ts +39 -0
- package/dist/wizard/lib/campaign-proposer.js +180 -0
- package/dist/wizard/lib/campaign-state-machine.d.ts +63 -0
- package/dist/wizard/lib/campaign-state-machine.js +114 -0
- package/dist/wizard/lib/ci-generator.d.ts +14 -0
- package/dist/wizard/lib/ci-generator.js +187 -0
- package/dist/wizard/lib/claude-merge.d.ts +38 -0
- package/dist/wizard/lib/claude-merge.js +115 -0
- package/dist/wizard/lib/codegen/erd-gen.d.ts +16 -0
- package/dist/wizard/lib/codegen/erd-gen.js +98 -0
- package/dist/wizard/lib/codegen/integrations.d.ts +18 -0
- package/dist/wizard/lib/codegen/integrations.js +189 -0
- package/dist/wizard/lib/codegen/openapi-gen.d.ts +15 -0
- package/dist/wizard/lib/codegen/openapi-gen.js +79 -0
- package/dist/wizard/lib/codegen/prisma-types.d.ts +15 -0
- package/dist/wizard/lib/codegen/prisma-types.js +44 -0
- package/dist/wizard/lib/codegen/seed-gen.d.ts +16 -0
- package/dist/wizard/lib/codegen/seed-gen.js +128 -0
- package/dist/wizard/lib/compliance.d.ts +51 -0
- package/dist/wizard/lib/compliance.js +112 -0
- package/dist/wizard/lib/correlation-engine.d.ts +59 -0
- package/dist/wizard/lib/correlation-engine.js +151 -0
- package/dist/wizard/lib/cost-estimator.d.ts +22 -0
- package/dist/wizard/lib/cost-estimator.js +72 -0
- package/dist/wizard/lib/cost-tracker.d.ts +27 -0
- package/dist/wizard/lib/cost-tracker.js +37 -0
- package/dist/wizard/lib/daemon-aggregator.d.ts +71 -0
- package/dist/wizard/lib/daemon-aggregator.js +204 -0
- package/dist/wizard/lib/daemon-core.d.ts +6 -0
- package/dist/wizard/lib/daemon-core.js +5 -0
- package/dist/wizard/lib/dashboard-data.d.ts +132 -0
- package/dist/wizard/lib/dashboard-data.js +336 -0
- package/dist/wizard/lib/dashboard-ws.d.ts +25 -0
- package/dist/wizard/lib/dashboard-ws.js +91 -0
- package/dist/wizard/lib/deep-current.d.ts +77 -0
- package/dist/wizard/lib/deep-current.js +234 -0
- package/dist/wizard/lib/deploy-coordinator.d.ts +40 -0
- package/dist/wizard/lib/deploy-coordinator.js +86 -0
- package/dist/wizard/lib/deploy-log.d.ts +28 -0
- package/dist/wizard/lib/deploy-log.js +52 -0
- package/dist/wizard/lib/desktop-notify.d.ts +27 -0
- package/dist/wizard/lib/desktop-notify.js +98 -0
- package/dist/wizard/lib/dns/cloudflare-dns.d.ts +35 -0
- package/dist/wizard/lib/dns/cloudflare-dns.js +216 -0
- package/dist/wizard/lib/dns/cloudflare-registrar.d.ts +31 -0
- package/dist/wizard/lib/dns/cloudflare-registrar.js +148 -0
- package/dist/wizard/lib/dns/types.d.ts +22 -0
- package/dist/wizard/lib/dns/types.js +4 -0
- package/dist/wizard/lib/document-discovery.d.ts +33 -0
- package/dist/wizard/lib/document-discovery.js +145 -0
- package/dist/wizard/lib/env-validator.d.ts +14 -0
- package/dist/wizard/lib/env-validator.js +205 -0
- package/dist/wizard/lib/env-writer.d.ts +13 -0
- package/dist/wizard/lib/env-writer.js +26 -0
- package/dist/wizard/lib/exec.d.ts +30 -0
- package/dist/wizard/lib/exec.js +52 -0
- package/dist/wizard/lib/experiment.d.ts +70 -0
- package/dist/wizard/lib/experiment.js +169 -0
- package/dist/wizard/lib/extensions.d.ts +20 -0
- package/dist/wizard/lib/extensions.js +183 -0
- package/dist/wizard/lib/financial/adapter-factory.d.ts +47 -0
- package/dist/wizard/lib/financial/adapter-factory.js +225 -0
- package/dist/wizard/lib/financial/billing/base.d.ts +6 -0
- package/dist/wizard/lib/financial/billing/base.js +1 -0
- package/dist/wizard/lib/financial/billing/google-billing.d.ts +56 -0
- package/dist/wizard/lib/financial/billing/google-billing.js +298 -0
- package/dist/wizard/lib/financial/billing/meta-billing.d.ts +54 -0
- package/dist/wizard/lib/financial/billing/meta-billing.js +243 -0
- package/dist/wizard/lib/financial/billing/tiktok-billing.d.ts +54 -0
- package/dist/wizard/lib/financial/billing/tiktok-billing.js +260 -0
- package/dist/wizard/lib/financial/campaign/base.d.ts +13 -0
- package/dist/wizard/lib/financial/campaign/base.js +1 -0
- package/dist/wizard/lib/financial/campaign/google-campaign.d.ts +42 -0
- package/dist/wizard/lib/financial/campaign/google-campaign.js +388 -0
- package/dist/wizard/lib/financial/campaign/meta-campaign.d.ts +41 -0
- package/dist/wizard/lib/financial/campaign/meta-campaign.js +311 -0
- package/dist/wizard/lib/financial/campaign/sandbox-campaign.d.ts +45 -0
- package/dist/wizard/lib/financial/campaign/sandbox-campaign.js +261 -0
- package/dist/wizard/lib/financial/campaign/tiktok-campaign.d.ts +40 -0
- package/dist/wizard/lib/financial/campaign/tiktok-campaign.js +350 -0
- package/dist/wizard/lib/financial/funding-auto.d.ts +44 -0
- package/dist/wizard/lib/financial/funding-auto.js +52 -0
- package/dist/wizard/lib/financial/funding-policy.d.ts +60 -0
- package/dist/wizard/lib/financial/funding-policy.js +179 -0
- package/dist/wizard/lib/financial/platform-planner.d.ts +47 -0
- package/dist/wizard/lib/financial/platform-planner.js +134 -0
- package/dist/wizard/lib/financial/reconciliation-engine.d.ts +78 -0
- package/dist/wizard/lib/financial/reconciliation-engine.js +193 -0
- package/dist/wizard/lib/financial/registry.d.ts +22 -0
- package/dist/wizard/lib/financial/registry.js +26 -0
- package/dist/wizard/lib/financial/reporting.d.ts +96 -0
- package/dist/wizard/lib/financial/reporting.js +198 -0
- package/dist/wizard/lib/financial/stablecoin/base.d.ts +6 -0
- package/dist/wizard/lib/financial/stablecoin/base.js +1 -0
- package/dist/wizard/lib/financial/stablecoin/circle.d.ts +54 -0
- package/dist/wizard/lib/financial/stablecoin/circle.js +367 -0
- package/dist/wizard/lib/financial/stablecoin/mercury.d.ts +24 -0
- package/dist/wizard/lib/financial/stablecoin/mercury.js +171 -0
- package/dist/wizard/lib/financial/stablecoin/sandbox-stablecoin.d.ts +47 -0
- package/dist/wizard/lib/financial/stablecoin/sandbox-stablecoin.js +202 -0
- package/dist/wizard/lib/financial/treasury-planner.d.ts +52 -0
- package/dist/wizard/lib/financial/treasury-planner.js +128 -0
- package/dist/wizard/lib/financial-core.d.ts +6 -0
- package/dist/wizard/lib/financial-core.js +5 -0
- package/dist/wizard/lib/financial-vault.d.ts +34 -0
- package/dist/wizard/lib/financial-vault.js +199 -0
- package/dist/wizard/lib/frontmatter.d.ts +30 -0
- package/dist/wizard/lib/frontmatter.js +96 -0
- package/dist/wizard/lib/gap-analysis.d.ts +37 -0
- package/dist/wizard/lib/gap-analysis.js +218 -0
- package/dist/wizard/lib/github.d.ts +22 -0
- package/dist/wizard/lib/github.js +261 -0
- package/dist/wizard/lib/headless-deploy.d.ts +14 -0
- package/dist/wizard/lib/headless-deploy.js +452 -0
- package/dist/wizard/lib/health-monitor.d.ts +15 -0
- package/dist/wizard/lib/health-monitor.js +91 -0
- package/dist/wizard/lib/health-poller.d.ts +9 -0
- package/dist/wizard/lib/health-poller.js +123 -0
- package/dist/wizard/lib/heartbeat.d.ts +15 -0
- package/dist/wizard/lib/heartbeat.js +827 -0
- package/dist/wizard/lib/http-helpers.d.ts +9 -0
- package/dist/wizard/lib/http-helpers.js +24 -0
- package/dist/wizard/lib/image-gen.d.ts +56 -0
- package/dist/wizard/lib/image-gen.js +159 -0
- package/dist/wizard/lib/instance-sizing.d.ts +26 -0
- package/dist/wizard/lib/instance-sizing.js +51 -0
- package/dist/wizard/lib/kongo/analytics.d.ts +29 -0
- package/dist/wizard/lib/kongo/analytics.js +179 -0
- package/dist/wizard/lib/kongo/campaigns.d.ts +52 -0
- package/dist/wizard/lib/kongo/campaigns.js +91 -0
- package/dist/wizard/lib/kongo/client.d.ts +58 -0
- package/dist/wizard/lib/kongo/client.js +221 -0
- package/dist/wizard/lib/kongo/jobs.d.ts +57 -0
- package/dist/wizard/lib/kongo/jobs.js +122 -0
- package/dist/wizard/lib/kongo/pages.d.ts +60 -0
- package/dist/wizard/lib/kongo/pages.js +150 -0
- package/dist/wizard/lib/kongo/provisioner.d.ts +64 -0
- package/dist/wizard/lib/kongo/provisioner.js +116 -0
- package/dist/wizard/lib/kongo/seed.d.ts +49 -0
- package/dist/wizard/lib/kongo/seed.js +237 -0
- package/dist/wizard/lib/kongo/types.d.ts +323 -0
- package/dist/wizard/lib/kongo/types.js +11 -0
- package/dist/wizard/lib/kongo/variants.d.ts +57 -0
- package/dist/wizard/lib/kongo/variants.js +88 -0
- package/dist/wizard/lib/kongo/webhooks.d.ts +41 -0
- package/dist/wizard/lib/kongo/webhooks.js +112 -0
- package/dist/wizard/lib/marker.d.ts +28 -0
- package/dist/wizard/lib/marker.js +79 -0
- package/dist/wizard/lib/migrator.d.ts +35 -0
- package/dist/wizard/lib/migrator.js +190 -0
- package/dist/wizard/lib/natural-language-deploy.d.ts +30 -0
- package/dist/wizard/lib/natural-language-deploy.js +186 -0
- package/dist/wizard/lib/network.d.ts +22 -0
- package/dist/wizard/lib/network.js +72 -0
- package/dist/wizard/lib/oauth-core.d.ts +6 -0
- package/dist/wizard/lib/oauth-core.js +5 -0
- package/dist/wizard/lib/open-browser.d.ts +1 -0
- package/dist/wizard/lib/open-browser.js +26 -0
- package/dist/wizard/lib/patterns/ad-billing-adapter.d.ts +209 -0
- package/dist/wizard/lib/patterns/ad-billing-adapter.js +269 -0
- package/dist/wizard/lib/patterns/ad-platform-adapter.d.ts +200 -0
- package/dist/wizard/lib/patterns/ad-platform-adapter.js +212 -0
- package/dist/wizard/lib/patterns/daemon-process.d.ts +88 -0
- package/dist/wizard/lib/patterns/daemon-process.js +271 -0
- package/dist/wizard/lib/patterns/financial-transaction.d.ts +161 -0
- package/dist/wizard/lib/patterns/financial-transaction.js +132 -0
- package/dist/wizard/lib/patterns/funding-plan.d.ts +136 -0
- package/dist/wizard/lib/patterns/funding-plan.js +200 -0
- package/dist/wizard/lib/patterns/oauth-token-lifecycle.d.ts +94 -0
- package/dist/wizard/lib/patterns/oauth-token-lifecycle.js +139 -0
- package/dist/wizard/lib/patterns/outbound-rate-limiter.d.ts +67 -0
- package/dist/wizard/lib/patterns/outbound-rate-limiter.js +216 -0
- package/dist/wizard/lib/patterns/revenue-source-adapter.d.ts +96 -0
- package/dist/wizard/lib/patterns/revenue-source-adapter.js +182 -0
- package/dist/wizard/lib/patterns/stablecoin-adapter.d.ts +218 -0
- package/dist/wizard/lib/patterns/stablecoin-adapter.js +264 -0
- package/dist/wizard/lib/prd-validator.d.ts +39 -0
- package/dist/wizard/lib/prd-validator.js +137 -0
- package/dist/wizard/lib/project-init.d.ts +24 -0
- package/dist/wizard/lib/project-init.js +193 -0
- package/dist/wizard/lib/project-registry.d.ts +86 -0
- package/dist/wizard/lib/project-registry.js +359 -0
- package/dist/wizard/lib/provision-manifest.d.ts +44 -0
- package/dist/wizard/lib/provision-manifest.js +164 -0
- package/dist/wizard/lib/provisioner-registry.d.ts +15 -0
- package/dist/wizard/lib/provisioner-registry.js +34 -0
- package/dist/wizard/lib/provisioners/aws-vps.d.ts +6 -0
- package/dist/wizard/lib/provisioners/aws-vps.js +643 -0
- package/dist/wizard/lib/provisioners/cloudflare.d.ts +6 -0
- package/dist/wizard/lib/provisioners/cloudflare.js +300 -0
- package/dist/wizard/lib/provisioners/docker.d.ts +6 -0
- package/dist/wizard/lib/provisioners/docker.js +75 -0
- package/dist/wizard/lib/provisioners/http-client.d.ts +20 -0
- package/dist/wizard/lib/provisioners/http-client.js +79 -0
- package/dist/wizard/lib/provisioners/railway.d.ts +6 -0
- package/dist/wizard/lib/provisioners/railway.js +413 -0
- package/dist/wizard/lib/provisioners/scripts/caddyfile.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/caddyfile.js +54 -0
- package/dist/wizard/lib/provisioners/scripts/deploy-vps.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/deploy-vps.js +112 -0
- package/dist/wizard/lib/provisioners/scripts/docker-compose.d.ts +11 -0
- package/dist/wizard/lib/provisioners/scripts/docker-compose.js +91 -0
- package/dist/wizard/lib/provisioners/scripts/dockerfile.d.ts +5 -0
- package/dist/wizard/lib/provisioners/scripts/dockerfile.js +185 -0
- package/dist/wizard/lib/provisioners/scripts/ecosystem-config.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/ecosystem-config.js +36 -0
- package/dist/wizard/lib/provisioners/scripts/provision-vps.d.ts +14 -0
- package/dist/wizard/lib/provisioners/scripts/provision-vps.js +202 -0
- package/dist/wizard/lib/provisioners/scripts/rollback-vps.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/rollback-vps.js +67 -0
- package/dist/wizard/lib/provisioners/self-deploy.d.ts +41 -0
- package/dist/wizard/lib/provisioners/self-deploy.js +185 -0
- package/dist/wizard/lib/provisioners/static-s3.d.ts +6 -0
- package/dist/wizard/lib/provisioners/static-s3.js +235 -0
- package/dist/wizard/lib/provisioners/types.d.ts +40 -0
- package/dist/wizard/lib/provisioners/types.js +4 -0
- package/dist/wizard/lib/provisioners/vercel.d.ts +6 -0
- package/dist/wizard/lib/provisioners/vercel.js +287 -0
- package/dist/wizard/lib/pty-manager.d.ts +42 -0
- package/dist/wizard/lib/pty-manager.js +231 -0
- package/dist/wizard/lib/rate-limiter-core.d.ts +5 -0
- package/dist/wizard/lib/rate-limiter-core.js +5 -0
- package/dist/wizard/lib/reconciliation.d.ts +43 -0
- package/dist/wizard/lib/reconciliation.js +173 -0
- package/dist/wizard/lib/revenue-types.d.ts +5 -0
- package/dist/wizard/lib/revenue-types.js +1 -0
- package/dist/wizard/lib/route-optimizer.d.ts +28 -0
- package/dist/wizard/lib/route-optimizer.js +93 -0
- package/dist/wizard/lib/s3-deploy.d.ts +19 -0
- package/dist/wizard/lib/s3-deploy.js +156 -0
- package/dist/wizard/lib/safety-tiers.d.ts +76 -0
- package/dist/wizard/lib/safety-tiers.js +134 -0
- package/dist/wizard/lib/sentry-generator.d.ts +15 -0
- package/dist/wizard/lib/sentry-generator.js +116 -0
- package/dist/wizard/lib/server-config.d.ts +13 -0
- package/dist/wizard/lib/server-config.js +23 -0
- package/dist/wizard/lib/service-install.d.ts +18 -0
- package/dist/wizard/lib/service-install.js +182 -0
- package/dist/wizard/lib/site-scanner.d.ts +80 -0
- package/dist/wizard/lib/site-scanner.js +262 -0
- package/dist/wizard/lib/ssh-deploy.d.ts +25 -0
- package/dist/wizard/lib/ssh-deploy.js +225 -0
- package/dist/wizard/lib/templates.d.ts +24 -0
- package/dist/wizard/lib/templates.js +219 -0
- package/dist/wizard/lib/totp.d.ts +35 -0
- package/dist/wizard/lib/totp.js +276 -0
- package/dist/wizard/lib/tower-auth.d.ts +43 -0
- package/dist/wizard/lib/tower-auth.js +352 -0
- package/dist/wizard/lib/tower-rate-limit.d.ts +14 -0
- package/dist/wizard/lib/tower-rate-limit.js +61 -0
- package/dist/wizard/lib/tower-session.d.ts +28 -0
- package/dist/wizard/lib/tower-session.js +119 -0
- package/dist/wizard/lib/treasury-backup.d.ts +23 -0
- package/dist/wizard/lib/treasury-backup.js +126 -0
- package/dist/wizard/lib/treasury-heartbeat.d.ts +82 -0
- package/dist/wizard/lib/treasury-heartbeat.js +1104 -0
- package/dist/wizard/lib/updater.d.ts +29 -0
- package/dist/wizard/lib/updater.js +190 -0
- package/dist/wizard/lib/user-manager.d.ts +39 -0
- package/dist/wizard/lib/user-manager.js +182 -0
- package/dist/wizard/lib/vault.d.ts +26 -0
- package/dist/wizard/lib/vault.js +161 -0
- package/dist/wizard/router.d.ts +5 -0
- package/dist/wizard/router.js +15 -0
- package/dist/wizard/server.d.ts +18 -0
- package/dist/wizard/server.js +436 -0
- package/package.json +59 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Danger Room API — Real-time data feeds for the mission control dashboard.
|
|
3
|
+
*
|
|
4
|
+
* Shared parsers and WebSocket infra live in wizard/lib/dashboard-*.ts.
|
|
5
|
+
* This file registers Danger Room routes and any Danger Room-specific endpoints
|
|
6
|
+
* (heartbeat, freeze, Deep Current).
|
|
7
|
+
*/
|
|
8
|
+
import { readFile, readdir, stat, open } from 'node:fs/promises';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { watch, existsSync } from 'node:fs';
|
|
12
|
+
import { addRoute } from '../router.js';
|
|
13
|
+
import { sendJson, readFileOrNull } from '../lib/http-helpers.js';
|
|
14
|
+
import { parseCampaignState, parseBuildState, parseFindings, readDeployLog, readVersion, readContextStats, readTestResults, readGitStatus, detectDeployDrift, PROJECT_ROOT, } from '../lib/dashboard-data.js';
|
|
15
|
+
import { createDashboardWs } from '../lib/dashboard-ws.js';
|
|
16
|
+
// ── WebSocket ───────────────────────────────────
|
|
17
|
+
const ws = createDashboardWs('Danger Room');
|
|
18
|
+
/** Broadcast a message to all connected Danger Room clients. */
|
|
19
|
+
export const broadcastDangerRoom = ws.broadcast;
|
|
20
|
+
/** Close all Danger Room WebSocket connections, activity watcher, and shut down. */
|
|
21
|
+
export function closeDangerRoom() {
|
|
22
|
+
ws.close();
|
|
23
|
+
// Clean up activity watcher resources (Infinity Gauntlet ARCH-002)
|
|
24
|
+
if (activityPollInterval)
|
|
25
|
+
clearInterval(activityPollInterval);
|
|
26
|
+
if (activityWatcher) {
|
|
27
|
+
try {
|
|
28
|
+
activityWatcher.close();
|
|
29
|
+
}
|
|
30
|
+
catch { /* ignore */ }
|
|
31
|
+
activityWatcher = null;
|
|
32
|
+
}
|
|
33
|
+
if (activityDebounce)
|
|
34
|
+
clearTimeout(activityDebounce);
|
|
35
|
+
}
|
|
36
|
+
/** Handle WebSocket upgrade for /ws/danger-room. */
|
|
37
|
+
export const handleDangerRoomUpgrade = (req, socket, head) => ws.handleUpgrade(req, socket, head);
|
|
38
|
+
// ── Agent Activity Watcher (methodology-driven JSONL) ──
|
|
39
|
+
const ACTIVITY_FILE = join(PROJECT_ROOT, 'logs', 'agent-activity.jsonl');
|
|
40
|
+
let lastActivitySize = 0;
|
|
41
|
+
let activityDebounce = null;
|
|
42
|
+
let activityCheckInProgress = false;
|
|
43
|
+
/** Read new lines from agent-activity.jsonl and broadcast via WebSocket. */
|
|
44
|
+
async function checkAgentActivity() {
|
|
45
|
+
if (activityCheckInProgress)
|
|
46
|
+
return; // prevent concurrent reads (Gauntlet DR-08)
|
|
47
|
+
activityCheckInProgress = true;
|
|
48
|
+
try {
|
|
49
|
+
await _checkAgentActivity();
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
activityCheckInProgress = false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function _checkAgentActivity() {
|
|
56
|
+
try {
|
|
57
|
+
const st = await stat(ACTIVITY_FILE);
|
|
58
|
+
if (st.size <= lastActivitySize)
|
|
59
|
+
return;
|
|
60
|
+
// Read only the new bytes appended since last check (Gauntlet DR-05: no line estimation)
|
|
61
|
+
const fd = await open(ACTIVITY_FILE, 'r');
|
|
62
|
+
const buf = Buffer.alloc(st.size - lastActivitySize);
|
|
63
|
+
await fd.read(buf, 0, buf.length, lastActivitySize);
|
|
64
|
+
await fd.close();
|
|
65
|
+
const newContent = buf.toString('utf-8');
|
|
66
|
+
const newLines = newContent.trim().split('\n').filter(Boolean);
|
|
67
|
+
lastActivitySize = st.size;
|
|
68
|
+
for (const line of newLines) {
|
|
69
|
+
try {
|
|
70
|
+
const entry = JSON.parse(line);
|
|
71
|
+
if (entry.agent) {
|
|
72
|
+
// Server-side sanitization: cap field lengths (Gauntlet Kenobi DR-05)
|
|
73
|
+
const agent = String(entry.agent).slice(0, 50);
|
|
74
|
+
const action = String(entry.task || entry.status || 'dispatched').slice(0, 200);
|
|
75
|
+
ws.broadcast({ type: 'agent-activity', agent, action });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch { /* skip malformed lines */ }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch { /* file doesn't exist yet — normal before first agent dispatch */ }
|
|
82
|
+
}
|
|
83
|
+
// Hybrid approach: fs.watch for immediate + poll fallback (fs.watch is unreliable on some OSes)
|
|
84
|
+
let activityWatcher = null;
|
|
85
|
+
function setupActivityWatch() {
|
|
86
|
+
if (activityWatcher)
|
|
87
|
+
return;
|
|
88
|
+
try {
|
|
89
|
+
activityWatcher = watch(ACTIVITY_FILE, { persistent: false }, () => {
|
|
90
|
+
if (activityDebounce)
|
|
91
|
+
clearTimeout(activityDebounce);
|
|
92
|
+
activityDebounce = setTimeout(checkAgentActivity, 200);
|
|
93
|
+
});
|
|
94
|
+
activityWatcher.on('error', () => { activityWatcher = null; }); // re-establish on next poll
|
|
95
|
+
}
|
|
96
|
+
catch { /* file doesn't exist yet — poll will re-try */ }
|
|
97
|
+
}
|
|
98
|
+
setupActivityWatch();
|
|
99
|
+
// Poll fallback — catches events fs.watch misses AND re-establishes watch if file appeared (DR-06)
|
|
100
|
+
const activityPollInterval = setInterval(() => {
|
|
101
|
+
if (!activityWatcher)
|
|
102
|
+
setupActivityWatch();
|
|
103
|
+
checkAgentActivity();
|
|
104
|
+
}, 3000);
|
|
105
|
+
// ── Shared REST endpoints ────────────────────────
|
|
106
|
+
addRoute('GET', '/api/danger-room/campaign', async (_req, res) => {
|
|
107
|
+
sendJson(res, 200, await parseCampaignState());
|
|
108
|
+
});
|
|
109
|
+
addRoute('GET', '/api/danger-room/build', async (_req, res) => {
|
|
110
|
+
sendJson(res, 200, await parseBuildState());
|
|
111
|
+
});
|
|
112
|
+
addRoute('GET', '/api/danger-room/findings', async (_req, res) => {
|
|
113
|
+
sendJson(res, 200, await parseFindings());
|
|
114
|
+
});
|
|
115
|
+
addRoute('GET', '/api/danger-room/version', async (_req, res) => {
|
|
116
|
+
sendJson(res, 200, await readVersion());
|
|
117
|
+
});
|
|
118
|
+
addRoute('GET', '/api/danger-room/deploy', async (_req, res) => {
|
|
119
|
+
sendJson(res, 200, await readDeployLog());
|
|
120
|
+
});
|
|
121
|
+
addRoute('GET', '/api/danger-room/context', async (_req, res) => {
|
|
122
|
+
const stats = await readContextStats();
|
|
123
|
+
sendJson(res, 200, stats);
|
|
124
|
+
});
|
|
125
|
+
addRoute('GET', '/api/danger-room/experiments', async (_req, res) => {
|
|
126
|
+
try {
|
|
127
|
+
const { listExperiments } = await import('../lib/experiment.js');
|
|
128
|
+
const experiments = await listExperiments();
|
|
129
|
+
sendJson(res, 200, { experiments, total: experiments.length });
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
sendJson(res, 200, { experiments: [], total: 0 });
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
addRoute('GET', '/api/danger-room/tests', async (_req, res) => {
|
|
136
|
+
sendJson(res, 200, await readTestResults());
|
|
137
|
+
});
|
|
138
|
+
addRoute('GET', '/api/danger-room/git-status', async (_req, res) => {
|
|
139
|
+
sendJson(res, 200, await readGitStatus());
|
|
140
|
+
});
|
|
141
|
+
addRoute('GET', '/api/danger-room/drift', async (_req, res) => {
|
|
142
|
+
sendJson(res, 200, await detectDeployDrift());
|
|
143
|
+
});
|
|
144
|
+
// ── Danger Room-specific endpoints ───────────────
|
|
145
|
+
addRoute('GET', '/api/danger-room/heartbeat', async (_req, res) => {
|
|
146
|
+
const voidforgeDir = join(homedir(), '.voidforge');
|
|
147
|
+
const treasuryDir = join(voidforgeDir, 'treasury');
|
|
148
|
+
const treasuryVaultPath = join(treasuryDir, 'vault.enc');
|
|
149
|
+
const heartbeatJsonPath = join(voidforgeDir, 'heartbeat.json');
|
|
150
|
+
let cultivationInstalled = false;
|
|
151
|
+
let heartbeatData = null;
|
|
152
|
+
let campaigns = [];
|
|
153
|
+
let treasury = {
|
|
154
|
+
revenue: 0, spend: 0, net: 0, roas: 0, budgetRemaining: 0,
|
|
155
|
+
stablecoinBalance: null, pendingOfframps: 0,
|
|
156
|
+
bankAvailable: null, bankReserved: null,
|
|
157
|
+
runwayDays: null, fundingState: null,
|
|
158
|
+
nextTreasuryEvent: null, unsettledInvoices: 0,
|
|
159
|
+
reconciliationStatus: null,
|
|
160
|
+
};
|
|
161
|
+
try {
|
|
162
|
+
// existsSync imported statically at top (Infinity Gauntlet ARCH-009)
|
|
163
|
+
cultivationInstalled = existsSync(treasuryVaultPath);
|
|
164
|
+
const raw = await readFileOrNull(heartbeatJsonPath);
|
|
165
|
+
if (raw)
|
|
166
|
+
heartbeatData = JSON.parse(raw);
|
|
167
|
+
}
|
|
168
|
+
catch { /* no heartbeat data */ }
|
|
169
|
+
// Read campaigns from treasury/campaigns directory (mirrors heartbeat.ts readCampaigns)
|
|
170
|
+
try {
|
|
171
|
+
const campaignsDir = join(treasuryDir, 'campaigns');
|
|
172
|
+
if (existsSync(campaignsDir)) {
|
|
173
|
+
const files = await readdir(campaignsDir);
|
|
174
|
+
for (const file of files) {
|
|
175
|
+
if (!file.endsWith('.json'))
|
|
176
|
+
continue;
|
|
177
|
+
try {
|
|
178
|
+
const content = await readFile(join(campaignsDir, file), 'utf-8');
|
|
179
|
+
campaigns.push(JSON.parse(content));
|
|
180
|
+
}
|
|
181
|
+
catch { /* skip malformed campaign files */ }
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch { /* no campaigns directory */ }
|
|
186
|
+
// Read treasury summary from spend/revenue logs (mirrors heartbeat.ts readTreasurySummary)
|
|
187
|
+
try {
|
|
188
|
+
const spendLog = join(treasuryDir, 'spend-log.jsonl');
|
|
189
|
+
const revenueLog = join(treasuryDir, 'revenue-log.jsonl');
|
|
190
|
+
let totalSpendCents = 0;
|
|
191
|
+
let totalRevenueCents = 0;
|
|
192
|
+
if (existsSync(spendLog)) {
|
|
193
|
+
const lines = (await readFile(spendLog, 'utf-8')).trim().split('\n').filter(Boolean);
|
|
194
|
+
for (const line of lines) {
|
|
195
|
+
try {
|
|
196
|
+
const entry = JSON.parse(line);
|
|
197
|
+
// Clamp negative values — spend should never be negative
|
|
198
|
+
totalSpendCents += Math.max(0, entry.amountCents ?? 0);
|
|
199
|
+
}
|
|
200
|
+
catch { /* skip malformed lines */ }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (existsSync(revenueLog)) {
|
|
204
|
+
const lines = (await readFile(revenueLog, 'utf-8')).trim().split('\n').filter(Boolean);
|
|
205
|
+
for (const line of lines) {
|
|
206
|
+
try {
|
|
207
|
+
const entry = JSON.parse(line);
|
|
208
|
+
totalRevenueCents += entry.amountCents ?? 0;
|
|
209
|
+
}
|
|
210
|
+
catch { /* skip malformed lines */ }
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const net = totalRevenueCents - totalSpendCents;
|
|
214
|
+
const roas = totalSpendCents > 0 ? totalRevenueCents / totalSpendCents : 0;
|
|
215
|
+
// Read budget if available
|
|
216
|
+
let budgetRemaining = 0;
|
|
217
|
+
const budgetsFile = join(treasuryDir, 'budgets.json');
|
|
218
|
+
if (existsSync(budgetsFile)) {
|
|
219
|
+
try {
|
|
220
|
+
const budgetData = JSON.parse(await readFile(budgetsFile, 'utf-8'));
|
|
221
|
+
budgetRemaining = (budgetData.totalBudgetCents ?? 0) - totalSpendCents;
|
|
222
|
+
}
|
|
223
|
+
catch { /* skip malformed budgets */ }
|
|
224
|
+
}
|
|
225
|
+
// ── Stablecoin funding data (v19.0 — read from treasury JSONL logs) ──
|
|
226
|
+
let stablecoinBalance = null;
|
|
227
|
+
let pendingOfframps = 0;
|
|
228
|
+
let bankAvailable = null;
|
|
229
|
+
let bankReserved = null;
|
|
230
|
+
let runwayDays = null;
|
|
231
|
+
let fundingState = null;
|
|
232
|
+
let nextTreasuryEvent = null;
|
|
233
|
+
let unsettledInvoices = 0;
|
|
234
|
+
let reconciliationStatus = null;
|
|
235
|
+
// Read funding config for stablecoin balance and bank state
|
|
236
|
+
const fundingConfigPath = join(treasuryDir, 'funding-config.json.enc');
|
|
237
|
+
if (existsSync(fundingConfigPath)) {
|
|
238
|
+
// If funding config exists, try to read bank and stablecoin state from heartbeat data
|
|
239
|
+
// (heartbeat.json is the live state written by the daemon; funding-config is encrypted)
|
|
240
|
+
if (heartbeatData) {
|
|
241
|
+
const hb = heartbeatData;
|
|
242
|
+
if (typeof hb.stablecoinBalanceCents === 'number')
|
|
243
|
+
stablecoinBalance = hb.stablecoinBalanceCents;
|
|
244
|
+
if (typeof hb.bankAvailableCents === 'number')
|
|
245
|
+
bankAvailable = hb.bankAvailableCents;
|
|
246
|
+
if (typeof hb.bankReservedCents === 'number')
|
|
247
|
+
bankReserved = hb.bankReservedCents;
|
|
248
|
+
if (typeof hb.runwayDays === 'number')
|
|
249
|
+
runwayDays = hb.runwayDays;
|
|
250
|
+
if (typeof hb.fundingState === 'string')
|
|
251
|
+
fundingState = hb.fundingState;
|
|
252
|
+
if (typeof hb.nextTreasuryEvent === 'string')
|
|
253
|
+
nextTreasuryEvent = hb.nextTreasuryEvent;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Read funding plans for pending off-ramps and unsettled invoices
|
|
257
|
+
const fundingPlansLog = join(treasuryDir, 'funding-plans.jsonl');
|
|
258
|
+
if (existsSync(fundingPlansLog)) {
|
|
259
|
+
try {
|
|
260
|
+
const lines = (await readFile(fundingPlansLog, 'utf-8')).trim().split('\n').filter(Boolean);
|
|
261
|
+
for (const line of lines) {
|
|
262
|
+
try {
|
|
263
|
+
const plan = JSON.parse(line);
|
|
264
|
+
if (plan.status === 'PENDING_SETTLEMENT' || plan.status === 'APPROVED') {
|
|
265
|
+
unsettledInvoices++;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch { /* skip malformed lines */ }
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch { /* skip read errors */ }
|
|
272
|
+
}
|
|
273
|
+
// Read transfers for pending off-ramp count
|
|
274
|
+
const transfersLog = join(treasuryDir, 'transfers.jsonl');
|
|
275
|
+
if (existsSync(transfersLog)) {
|
|
276
|
+
try {
|
|
277
|
+
const lines = (await readFile(transfersLog, 'utf-8')).trim().split('\n').filter(Boolean);
|
|
278
|
+
for (const line of lines) {
|
|
279
|
+
try {
|
|
280
|
+
const transfer = JSON.parse(line);
|
|
281
|
+
if ((transfer.status === 'pending' || transfer.status === 'processing')
|
|
282
|
+
&& transfer.direction === 'crypto_to_fiat') {
|
|
283
|
+
pendingOfframps++;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
catch { /* skip malformed lines */ }
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch { /* skip read errors */ }
|
|
290
|
+
}
|
|
291
|
+
// Read latest reconciliation status
|
|
292
|
+
const reconciliationLog = join(treasuryDir, 'reconciliation.jsonl');
|
|
293
|
+
if (existsSync(reconciliationLog)) {
|
|
294
|
+
try {
|
|
295
|
+
const lines = (await readFile(reconciliationLog, 'utf-8')).trim().split('\n').filter(Boolean);
|
|
296
|
+
if (lines.length > 0) {
|
|
297
|
+
// Use last reconciliation entry as current status
|
|
298
|
+
const last = JSON.parse(lines[lines.length - 1]);
|
|
299
|
+
if (last.result === 'MATCHED' || last.result === 'WITHIN_THRESHOLD') {
|
|
300
|
+
reconciliationStatus = 'matched';
|
|
301
|
+
}
|
|
302
|
+
else if (last.result === 'MISMATCH') {
|
|
303
|
+
reconciliationStatus = 'mismatch';
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch { /* skip read errors */ }
|
|
308
|
+
}
|
|
309
|
+
// Calculate funding state from data if not provided by heartbeat daemon
|
|
310
|
+
if (fundingState === null && (stablecoinBalance !== null || bankAvailable !== null)) {
|
|
311
|
+
if (runwayDays !== null && runwayDays < 3)
|
|
312
|
+
fundingState = 'frozen';
|
|
313
|
+
else if (runwayDays !== null && runwayDays < 7)
|
|
314
|
+
fundingState = 'degraded';
|
|
315
|
+
else
|
|
316
|
+
fundingState = 'healthy';
|
|
317
|
+
}
|
|
318
|
+
treasury = {
|
|
319
|
+
revenue: totalRevenueCents, spend: totalSpendCents, net, roas, budgetRemaining,
|
|
320
|
+
stablecoinBalance, pendingOfframps,
|
|
321
|
+
bankAvailable, bankReserved,
|
|
322
|
+
runwayDays, fundingState,
|
|
323
|
+
nextTreasuryEvent, unsettledInvoices,
|
|
324
|
+
reconciliationStatus,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
catch { /* no treasury data */ }
|
|
328
|
+
sendJson(res, 200, { cultivationInstalled, heartbeat: heartbeatData, campaigns, treasury });
|
|
329
|
+
});
|
|
330
|
+
addRoute('POST', '/api/danger-room/freeze', async (_req, res) => {
|
|
331
|
+
// RBAC enforced by ROUTE_ROLES in server.ts (deployer+ required).
|
|
332
|
+
// Previous implementation checked client-supplied X-VoidForge-Role header (SEC-R1-001 — privilege escalation).
|
|
333
|
+
// v18.0: Removed client-header check. Session-based role verification happens in server middleware.
|
|
334
|
+
// v17.0: Wire to daemon Unix socket with auth token.
|
|
335
|
+
try {
|
|
336
|
+
const net = await import('node:net');
|
|
337
|
+
const { readFile: fsReadFile } = await import('node:fs/promises');
|
|
338
|
+
const { SOCKET_PATH, TOKEN_FILE } = await import('../lib/daemon-core.js');
|
|
339
|
+
const { existsSync } = await import('node:fs');
|
|
340
|
+
if (!existsSync(SOCKET_PATH)) {
|
|
341
|
+
sendJson(res, 503, { ok: false, error: 'Heartbeat daemon not running. Start with: voidforge heartbeat start' });
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
// Read auth token from TOKEN_FILE
|
|
345
|
+
let authToken = '';
|
|
346
|
+
try {
|
|
347
|
+
authToken = (await fsReadFile(TOKEN_FILE, 'utf-8')).trim();
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
sendJson(res, 503, { ok: false, error: 'Cannot read daemon auth token — heartbeat may not be running' });
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const response = await new Promise((resolve, reject) => {
|
|
354
|
+
const socket = net.connect(SOCKET_PATH);
|
|
355
|
+
let data = '';
|
|
356
|
+
socket.on('data', (chunk) => { data += chunk.toString(); });
|
|
357
|
+
socket.on('end', () => resolve(data));
|
|
358
|
+
socket.on('error', (err) => reject(err));
|
|
359
|
+
socket.setTimeout(5000, () => { socket.destroy(); reject(new Error('Daemon socket timeout')); });
|
|
360
|
+
socket.write(`POST /freeze HTTP/1.0\r\nAuthorization: Bearer ${authToken}\r\nContent-Length: 0\r\n\r\n`);
|
|
361
|
+
});
|
|
362
|
+
// Parse daemon response — expects JSON body after HTTP headers
|
|
363
|
+
const bodyStart = response.indexOf('\r\n\r\n');
|
|
364
|
+
const body = bodyStart >= 0 ? response.slice(bodyStart + 4) : response;
|
|
365
|
+
const parsed = JSON.parse(body);
|
|
366
|
+
sendJson(res, parsed.ok ? 200 : 500, parsed);
|
|
367
|
+
}
|
|
368
|
+
catch (err) {
|
|
369
|
+
const message = err instanceof Error ? err.message : 'Failed to contact daemon';
|
|
370
|
+
sendJson(res, 503, { ok: false, error: `Daemon communication failed: ${message}` });
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
// ── Deep Current endpoints (v12.x) ─────────────────
|
|
374
|
+
addRoute('GET', '/api/danger-room/current', async (_req, res) => {
|
|
375
|
+
const situationPath = join(PROJECT_ROOT, 'logs', 'deep-current', 'situation.json');
|
|
376
|
+
const content = await readFileOrNull(situationPath);
|
|
377
|
+
if (!content) {
|
|
378
|
+
sendJson(res, 200, { initialized: false });
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
try {
|
|
382
|
+
const situation = JSON.parse(content);
|
|
383
|
+
const proposalsDir = join(PROJECT_ROOT, 'logs', 'deep-current', 'proposals');
|
|
384
|
+
let latestProposal = null;
|
|
385
|
+
try {
|
|
386
|
+
// existsSync imported statically at top (Infinity Gauntlet ARCH-009)
|
|
387
|
+
if (existsSync(proposalsDir)) {
|
|
388
|
+
const files = await readdir(proposalsDir);
|
|
389
|
+
const mdFiles = files.filter(f => f.endsWith('.md')).sort().reverse();
|
|
390
|
+
if (mdFiles.length > 0) {
|
|
391
|
+
latestProposal = await readFileOrNull(join(proposalsDir, mdFiles[0]));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
catch { /* no proposals dir */ }
|
|
396
|
+
sendJson(res, 200, { initialized: true, situation, latestProposal });
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
sendJson(res, 200, { initialized: false, error: 'Failed to parse situation model' });
|
|
400
|
+
}
|
|
401
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deploy wizard API routes — project scanning for Haku.
|
|
3
|
+
*/
|
|
4
|
+
import { readFile, access, realpath } from 'node:fs/promises';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { addRoute } from '../router.js';
|
|
7
|
+
import { parseJsonBody } from '../lib/body-parser.js';
|
|
8
|
+
import { parseFrontmatter } from '../lib/frontmatter.js';
|
|
9
|
+
import { recommendInstanceType } from '../lib/instance-sizing.js';
|
|
10
|
+
import { sendJson } from '../lib/http-helpers.js';
|
|
11
|
+
// POST /api/deploy/scan — scan a project directory for deploy info
|
|
12
|
+
addRoute('POST', '/api/deploy/scan', async (req, res) => {
|
|
13
|
+
const body = await parseJsonBody(req);
|
|
14
|
+
if (!body.directory) {
|
|
15
|
+
sendJson(res, 400, { error: 'directory is required' });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
// SEC-010: Validate path — absolute, no traversal
|
|
19
|
+
if (!body.directory.startsWith('/') || body.directory.includes('..')) {
|
|
20
|
+
sendJson(res, 400, { error: 'directory must be an absolute path with no ".." segments' });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
let dir = body.directory;
|
|
24
|
+
// Check directory exists and resolve symlinks (IG-R4: use real path for all operations)
|
|
25
|
+
try {
|
|
26
|
+
await access(dir);
|
|
27
|
+
dir = await realpath(dir);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
sendJson(res, 400, { error: `Directory not found: ${dir}` });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Check it's a VoidForge project (has CLAUDE.md)
|
|
34
|
+
try {
|
|
35
|
+
await access(join(dir, 'CLAUDE.md'));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
sendJson(res, 400, { error: 'Not a VoidForge project — no CLAUDE.md found' });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// Read project name from CLAUDE.md
|
|
42
|
+
let name = 'Unknown';
|
|
43
|
+
try {
|
|
44
|
+
const claudeMd = await readFile(join(dir, 'CLAUDE.md'), 'utf-8');
|
|
45
|
+
const nameMatch = claudeMd.match(/\*\*Name:\*\*\s*(.+)/);
|
|
46
|
+
if (nameMatch) {
|
|
47
|
+
const extracted = nameMatch[1].trim();
|
|
48
|
+
if (!extracted.startsWith('['))
|
|
49
|
+
name = extracted;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch { /* use default */ }
|
|
53
|
+
// Read deploy target from .env
|
|
54
|
+
let deploy = '';
|
|
55
|
+
try {
|
|
56
|
+
const envContent = await readFile(join(dir, '.env'), 'utf-8');
|
|
57
|
+
const deployMatch = envContent.match(/DEPLOY_TARGET=(.+)/);
|
|
58
|
+
if (deployMatch) {
|
|
59
|
+
deploy = deployMatch[1].trim().replace(/^["']|["']$/g, '').split('#')[0].trim();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch { /* no .env yet */ }
|
|
63
|
+
// Read framework/database/cache from PRD frontmatter
|
|
64
|
+
let framework = '';
|
|
65
|
+
let database = 'none';
|
|
66
|
+
let cache = 'none';
|
|
67
|
+
let instanceType = '';
|
|
68
|
+
let hostname = '';
|
|
69
|
+
let prdFrontmatter = {};
|
|
70
|
+
try {
|
|
71
|
+
const prd = await readFile(join(dir, 'docs', 'PRD.md'), 'utf-8');
|
|
72
|
+
const { frontmatter } = parseFrontmatter(prd);
|
|
73
|
+
prdFrontmatter = frontmatter;
|
|
74
|
+
if (frontmatter.framework)
|
|
75
|
+
framework = frontmatter.framework;
|
|
76
|
+
if (frontmatter.database)
|
|
77
|
+
database = frontmatter.database;
|
|
78
|
+
if (frontmatter.cache)
|
|
79
|
+
cache = frontmatter.cache;
|
|
80
|
+
if (frontmatter.deploy && !deploy)
|
|
81
|
+
deploy = frontmatter.deploy;
|
|
82
|
+
if (frontmatter.instance_type)
|
|
83
|
+
instanceType = frontmatter.instance_type;
|
|
84
|
+
if (frontmatter.hostname)
|
|
85
|
+
hostname = frontmatter.hostname;
|
|
86
|
+
}
|
|
87
|
+
catch { /* no PRD or no frontmatter */ }
|
|
88
|
+
// Also check .env for hostname if not in PRD
|
|
89
|
+
if (!hostname) {
|
|
90
|
+
try {
|
|
91
|
+
const envContent = await readFile(join(dir, '.env'), 'utf-8');
|
|
92
|
+
const hostnameMatch = envContent.match(/HOSTNAME=(.+)/);
|
|
93
|
+
if (hostnameMatch) {
|
|
94
|
+
hostname = hostnameMatch[1].trim().replace(/^["']|["']$/g, '').split('#')[0].trim();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch { /* no .env */ }
|
|
98
|
+
}
|
|
99
|
+
// Auto-detect framework from files if not in PRD
|
|
100
|
+
if (!framework) {
|
|
101
|
+
try {
|
|
102
|
+
const pkg = await readFile(join(dir, 'package.json'), 'utf-8');
|
|
103
|
+
const pkgData = JSON.parse(pkg);
|
|
104
|
+
const deps = pkgData.dependencies || {};
|
|
105
|
+
if (deps['next'])
|
|
106
|
+
framework = 'next.js';
|
|
107
|
+
else if (deps['express'])
|
|
108
|
+
framework = 'express';
|
|
109
|
+
}
|
|
110
|
+
catch { /* not a Node project */ }
|
|
111
|
+
if (!framework) {
|
|
112
|
+
try {
|
|
113
|
+
const reqs = await readFile(join(dir, 'requirements.txt'), 'utf-8');
|
|
114
|
+
const reqsLower = reqs.toLowerCase();
|
|
115
|
+
if (reqsLower.includes('django'))
|
|
116
|
+
framework = 'django';
|
|
117
|
+
else
|
|
118
|
+
framework = 'python';
|
|
119
|
+
}
|
|
120
|
+
catch { /* not Python */ }
|
|
121
|
+
}
|
|
122
|
+
if (!framework) {
|
|
123
|
+
try {
|
|
124
|
+
await access(join(dir, 'Gemfile'));
|
|
125
|
+
framework = 'rails';
|
|
126
|
+
}
|
|
127
|
+
catch { /* not Ruby */ }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Detect PostgreSQL extensions from Prisma schema
|
|
131
|
+
let extensions = [];
|
|
132
|
+
if (database === 'postgres') {
|
|
133
|
+
try {
|
|
134
|
+
const prismaSchema = await readFile(join(dir, 'prisma', 'schema.prisma'), 'utf-8');
|
|
135
|
+
const extMatch = prismaSchema.match(/extensions\s*=\s*\[([^\]]+)\]/);
|
|
136
|
+
if (extMatch) {
|
|
137
|
+
extensions = extMatch[1].split(',').map((e) => e.trim().replace(/["']/g, '')).filter(Boolean);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch { /* no Prisma schema or no extensions */ }
|
|
141
|
+
}
|
|
142
|
+
// Auto-recommend instance type from PRD scope if not explicitly set
|
|
143
|
+
if (!instanceType && (deploy === 'vps' || !deploy)) {
|
|
144
|
+
instanceType = recommendInstanceType({
|
|
145
|
+
type: prdFrontmatter.type,
|
|
146
|
+
framework,
|
|
147
|
+
database,
|
|
148
|
+
cache,
|
|
149
|
+
workers: prdFrontmatter.workers,
|
|
150
|
+
payments: prdFrontmatter.payments,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
sendJson(res, 200, {
|
|
154
|
+
valid: true,
|
|
155
|
+
name,
|
|
156
|
+
deploy: deploy || 'docker',
|
|
157
|
+
framework,
|
|
158
|
+
database,
|
|
159
|
+
cache,
|
|
160
|
+
instanceType: instanceType || 't3.micro',
|
|
161
|
+
hostname: hostname || '',
|
|
162
|
+
extensions,
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|