@yiaany/ghostapi 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/CHANGELOG.md +48 -2
  2. package/CONTRIBUTING.md +119 -117
  3. package/LICENSE +21 -21
  4. package/README.md +208 -185
  5. package/SECURITY.md +53 -37
  6. package/dist/actions/gateway.d.ts +179 -0
  7. package/dist/actions/gateway.js +514 -0
  8. package/dist/actions/gateway.js.map +1 -0
  9. package/dist/actions/index.d.ts +2 -0
  10. package/dist/actions/index.js +2 -0
  11. package/dist/actions/index.js.map +1 -0
  12. package/dist/ai/aiClient.js +4 -4
  13. package/dist/ai/aiClient.js.map +1 -1
  14. package/dist/ai/aiGenerator.d.ts +2 -2
  15. package/dist/ai/aiGenerator.js +21 -72
  16. package/dist/ai/aiGenerator.js.map +1 -1
  17. package/dist/ai/prompts.js +51 -51
  18. package/dist/ai/prompts.js.map +1 -1
  19. package/dist/approvals/approvalInbox.d.ts +150 -0
  20. package/dist/approvals/approvalInbox.js +412 -0
  21. package/dist/approvals/approvalInbox.js.map +1 -0
  22. package/dist/approvals/index.d.ts +2 -0
  23. package/dist/approvals/index.js +2 -0
  24. package/dist/approvals/index.js.map +1 -0
  25. package/dist/behavior/behaviorStore.d.ts +2 -0
  26. package/dist/behavior/behaviorStore.js +50 -46
  27. package/dist/behavior/behaviorStore.js.map +1 -1
  28. package/dist/cache/index.d.ts +2 -0
  29. package/dist/cache/index.js +58 -14
  30. package/dist/cache/index.js.map +1 -1
  31. package/dist/cli/index.js +438 -58
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/openUrl.d.ts +10 -0
  34. package/dist/cli/openUrl.js +31 -0
  35. package/dist/cli/openUrl.js.map +1 -0
  36. package/dist/cli/parser.d.ts +155 -0
  37. package/dist/cli/parser.js +504 -2
  38. package/dist/cli/parser.js.map +1 -1
  39. package/dist/config/dataPaths.d.ts +31 -0
  40. package/dist/config/dataPaths.js +45 -0
  41. package/dist/config/dataPaths.js.map +1 -0
  42. package/dist/config/localConfig.d.ts +1 -2
  43. package/dist/config/localConfig.js +27 -21
  44. package/dist/config/localConfig.js.map +1 -1
  45. package/dist/config/serverConfig.d.ts +2 -0
  46. package/dist/config/serverConfig.js +8 -1
  47. package/dist/config/serverConfig.js.map +1 -1
  48. package/dist/contracts/contract.d.ts +76 -0
  49. package/dist/contracts/contract.js +603 -0
  50. package/dist/contracts/contract.js.map +1 -0
  51. package/dist/contracts/index.d.ts +2 -0
  52. package/dist/contracts/index.js +2 -0
  53. package/dist/contracts/index.js.map +1 -0
  54. package/dist/credentials/credentialBroker.d.ts +207 -0
  55. package/dist/credentials/credentialBroker.js +588 -0
  56. package/dist/credentials/credentialBroker.js.map +1 -0
  57. package/dist/credentials/index.d.ts +2 -0
  58. package/dist/credentials/index.js +2 -0
  59. package/dist/credentials/index.js.map +1 -0
  60. package/dist/dashboard/app.js +685 -615
  61. package/dist/dashboard/index.html +13 -7
  62. package/dist/dashboard/styles.css +349 -349
  63. package/dist/egress/capabilities.d.ts +32 -0
  64. package/dist/egress/capabilities.js +165 -0
  65. package/dist/egress/capabilities.js.map +1 -0
  66. package/dist/egress/linuxBootstrap.d.ts +1 -0
  67. package/dist/egress/linuxBootstrap.js +121 -0
  68. package/dist/egress/linuxBootstrap.js.map +1 -0
  69. package/dist/egress/run.d.ts +18 -0
  70. package/dist/egress/run.js +305 -0
  71. package/dist/egress/run.js.map +1 -0
  72. package/dist/errors/errorEngine.d.ts +2 -2
  73. package/dist/errors/errorEngine.js +3 -40
  74. package/dist/errors/errorEngine.js.map +1 -1
  75. package/dist/evals/evals.d.ts +126 -0
  76. package/dist/evals/evals.js +466 -0
  77. package/dist/evals/evals.js.map +1 -0
  78. package/dist/evals/index.d.ts +2 -0
  79. package/dist/evals/index.js +2 -0
  80. package/dist/evals/index.js.map +1 -0
  81. package/dist/evidence/index.d.ts +2 -0
  82. package/dist/evidence/index.js +2 -0
  83. package/dist/evidence/index.js.map +1 -0
  84. package/dist/evidence/report.d.ts +143 -0
  85. package/dist/evidence/report.js +556 -0
  86. package/dist/evidence/report.js.map +1 -0
  87. package/dist/fault/faultLab.d.ts +5 -4
  88. package/dist/fault/faultLab.js +66 -24
  89. package/dist/fault/faultLab.js.map +1 -1
  90. package/dist/index.d.ts +46 -0
  91. package/dist/index.js +23 -0
  92. package/dist/index.js.map +1 -1
  93. package/dist/inventory/analysis.d.ts +12 -0
  94. package/dist/inventory/analysis.js +457 -0
  95. package/dist/inventory/analysis.js.map +1 -0
  96. package/dist/inventory/index.d.ts +5 -0
  97. package/dist/inventory/index.js +4 -0
  98. package/dist/inventory/index.js.map +1 -0
  99. package/dist/inventory/inventoryController.d.ts +53 -0
  100. package/dist/inventory/inventoryController.js +1044 -0
  101. package/dist/inventory/inventoryController.js.map +1 -0
  102. package/dist/inventory/types.d.ts +579 -0
  103. package/dist/inventory/types.js +787 -0
  104. package/dist/inventory/types.js.map +1 -0
  105. package/dist/landing/assets/index-CxWsopzP.css +1 -0
  106. package/dist/landing/assets/index-DZlpyhqd.js +55 -0
  107. package/dist/landing/assets/index-Dk-Xezs3.js +55 -0
  108. package/dist/landing/index.html +15 -15
  109. package/dist/ledger/actionLedger.d.ts +125 -0
  110. package/dist/ledger/actionLedger.js +594 -0
  111. package/dist/ledger/actionLedger.js.map +1 -0
  112. package/dist/ledger/index.d.ts +2 -0
  113. package/dist/ledger/index.js +2 -0
  114. package/dist/ledger/index.js.map +1 -0
  115. package/dist/mcp/server.js +4 -3
  116. package/dist/mcp/server.js.map +1 -1
  117. package/dist/policy/index.d.ts +3 -0
  118. package/dist/policy/index.js +2 -0
  119. package/dist/policy/index.js.map +1 -0
  120. package/dist/policy/policy.d.ts +14 -0
  121. package/dist/policy/policy.js +282 -0
  122. package/dist/policy/policy.js.map +1 -0
  123. package/dist/policy/types.d.ts +52 -0
  124. package/dist/policy/types.js +2 -0
  125. package/dist/policy/types.js.map +1 -0
  126. package/dist/productTelemetry/index.d.ts +16 -0
  127. package/dist/productTelemetry/index.js +142 -0
  128. package/dist/productTelemetry/index.js.map +1 -0
  129. package/dist/providers/conformance.d.ts +7 -0
  130. package/dist/providers/conformance.js +48 -0
  131. package/dist/providers/conformance.js.map +1 -0
  132. package/dist/providers/index.d.ts +4 -2
  133. package/dist/providers/index.js +3 -1
  134. package/dist/providers/index.js.map +1 -1
  135. package/dist/providers/packs/resendPack.d.ts +2 -0
  136. package/dist/providers/packs/resendPack.js +147 -0
  137. package/dist/providers/packs/resendPack.js.map +1 -0
  138. package/dist/providers/packs/stripePack.d.ts +2 -0
  139. package/dist/providers/packs/stripePack.js +852 -0
  140. package/dist/providers/packs/stripePack.js.map +1 -0
  141. package/dist/providers/registry.d.ts +7 -3
  142. package/dist/providers/registry.js +55 -4
  143. package/dist/providers/registry.js.map +1 -1
  144. package/dist/providers/resend.js +2 -11
  145. package/dist/providers/resend.js.map +1 -1
  146. package/dist/providers/runtime.d.ts +8 -0
  147. package/dist/providers/runtime.js +48 -0
  148. package/dist/providers/runtime.js.map +1 -0
  149. package/dist/providers/stripeWebhook.d.ts +13 -0
  150. package/dist/providers/stripeWebhook.js +50 -0
  151. package/dist/providers/stripeWebhook.js.map +1 -0
  152. package/dist/providers/types.d.ts +153 -0
  153. package/dist/proxy/cacheKey.js +3 -3
  154. package/dist/proxy/cacheKey.js.map +1 -1
  155. package/dist/proxy/providerDetector.d.ts +2 -7
  156. package/dist/proxy/providerDetector.js +4 -4
  157. package/dist/proxy/providerDetector.js.map +1 -1
  158. package/dist/proxy/proxyHandler.js +116 -12
  159. package/dist/proxy/proxyHandler.js.map +1 -1
  160. package/dist/proxy/requestNormalizer.d.ts +0 -1
  161. package/dist/proxy/requestNormalizer.js +2 -5
  162. package/dist/proxy/requestNormalizer.js.map +1 -1
  163. package/dist/reliability/costGovernance.d.ts +175 -0
  164. package/dist/reliability/costGovernance.js +515 -0
  165. package/dist/reliability/costGovernance.js.map +1 -0
  166. package/dist/reliability/index.d.ts +8 -0
  167. package/dist/reliability/index.js +5 -0
  168. package/dist/reliability/index.js.map +1 -0
  169. package/dist/reliability/reconciliation.d.ts +157 -0
  170. package/dist/reliability/reconciliation.js +522 -0
  171. package/dist/reliability/reconciliation.js.map +1 -0
  172. package/dist/reliability/runtimeHealth.d.ts +60 -0
  173. package/dist/reliability/runtimeHealth.js +356 -0
  174. package/dist/reliability/runtimeHealth.js.map +1 -0
  175. package/dist/reliability/slo.d.ts +131 -0
  176. package/dist/reliability/slo.js +375 -0
  177. package/dist/reliability/slo.js.map +1 -0
  178. package/dist/report/safetyReport.js +7 -3
  179. package/dist/report/safetyReport.js.map +1 -1
  180. package/dist/safety/index.d.ts +2 -0
  181. package/dist/safety/index.js +2 -0
  182. package/dist/safety/index.js.map +1 -0
  183. package/dist/safety/safetyController.d.ts +187 -0
  184. package/dist/safety/safetyController.js +477 -0
  185. package/dist/safety/safetyController.js.map +1 -0
  186. package/dist/scenarios/scenarioBundle.d.ts +75 -0
  187. package/dist/scenarios/scenarioBundle.js +750 -0
  188. package/dist/scenarios/scenarioBundle.js.map +1 -0
  189. package/dist/scenarios/scenarioStore.d.ts +1 -0
  190. package/dist/scenarios/scenarioStore.js +47 -32
  191. package/dist/scenarios/scenarioStore.js.map +1 -1
  192. package/dist/security/headerSanitizer.d.ts +2 -0
  193. package/dist/security/headerSanitizer.js +36 -1
  194. package/dist/security/headerSanitizer.js.map +1 -1
  195. package/dist/security/secrets.js +24 -4
  196. package/dist/security/secrets.js.map +1 -1
  197. package/dist/server/accessControl.d.ts +5 -0
  198. package/dist/server/accessControl.js +100 -0
  199. package/dist/server/accessControl.js.map +1 -0
  200. package/dist/server/createServer.d.ts +0 -5
  201. package/dist/server/createServer.js +9 -29
  202. package/dist/server/createServer.js.map +1 -1
  203. package/dist/server/eventsStore.d.ts +6 -1
  204. package/dist/server/eventsStore.js +83 -15
  205. package/dist/server/eventsStore.js.map +1 -1
  206. package/dist/server/routes.js +36 -10
  207. package/dist/server/routes.js.map +1 -1
  208. package/dist/server/sse.d.ts +2 -1
  209. package/dist/server/sse.js +15 -3
  210. package/dist/server/sse.js.map +1 -1
  211. package/dist/setup/setupGenerator.js +53 -6
  212. package/dist/setup/setupGenerator.js.map +1 -1
  213. package/dist/state/stateStore.d.ts +5 -0
  214. package/dist/state/stateStore.js +48 -62
  215. package/dist/state/stateStore.js.map +1 -1
  216. package/dist/storage/fileStore.d.ts +10 -0
  217. package/dist/storage/fileStore.js +131 -0
  218. package/dist/storage/fileStore.js.map +1 -0
  219. package/dist/teamControl/controlPlane.d.ts +255 -0
  220. package/dist/teamControl/controlPlane.js +918 -0
  221. package/dist/teamControl/controlPlane.js.map +1 -0
  222. package/dist/teamControl/deployment.d.ts +33 -0
  223. package/dist/teamControl/deployment.js +67 -0
  224. package/dist/teamControl/deployment.js.map +1 -0
  225. package/dist/teamControl/index.d.ts +4 -0
  226. package/dist/teamControl/index.js +3 -0
  227. package/dist/teamControl/index.js.map +1 -0
  228. package/dist/tests/testGenerator.js +4 -1
  229. package/dist/tests/testGenerator.js.map +1 -1
  230. package/dist/trust/index.d.ts +2 -0
  231. package/dist/trust/index.js +2 -0
  232. package/dist/trust/index.js.map +1 -0
  233. package/dist/trust/trustLadder.d.ts +182 -0
  234. package/dist/trust/trustLadder.js +560 -0
  235. package/dist/trust/trustLadder.js.map +1 -0
  236. package/dist/worlds/index.d.ts +2 -0
  237. package/dist/worlds/index.js +2 -0
  238. package/dist/worlds/index.js.map +1 -0
  239. package/dist/worlds/worlds.d.ts +160 -0
  240. package/dist/worlds/worlds.js +491 -0
  241. package/dist/worlds/worlds.js.map +1 -0
  242. package/docs/assets/dashboard.png +0 -0
  243. package/docs/ci.md +49 -0
  244. package/docs/commercial/README.md +29 -0
  245. package/docs/commercial/data-inventory.md +36 -0
  246. package/docs/commercial/manual-invoicing.md +44 -0
  247. package/docs/commercial/metrics.md +51 -0
  248. package/docs/commercial/pricing.md +103 -0
  249. package/docs/commercial/privacy-policy-draft.md +41 -0
  250. package/docs/commercial/terms-topics-for-counsel.md +51 -0
  251. package/docs/design-partners/README.md +52 -0
  252. package/docs/design-partners/demo-narratives.md +33 -0
  253. package/docs/design-partners/design-partner-offer.md +39 -0
  254. package/docs/design-partners/discovery-questions.md +28 -0
  255. package/docs/design-partners/feedback-capture-template.md +48 -0
  256. package/docs/design-partners/icp-one-pager.md +43 -0
  257. package/docs/design-partners/onboarding-checklist.md +33 -0
  258. package/docs/design-partners/pilot-success-criteria.md +27 -0
  259. package/docs/design-partners/pricing-interview-script.md +18 -0
  260. package/docs/design-partners/telemetry-plan.md +48 -0
  261. package/docs/development/baseline.md +114 -0
  262. package/docs/development/onboarding-smoke.md +37 -0
  263. package/docs/development/verification-0.1.8.md +25 -0
  264. package/docs/enterprise-product-roadmap-ru.md +1879 -0
  265. package/docs/fundraising/README.md +87 -0
  266. package/docs/fundraising/data-room-checklist.md +72 -0
  267. package/docs/fundraising/demo-script.md +52 -0
  268. package/docs/fundraising/design-partners-50.md +74 -0
  269. package/docs/fundraising/launch-posts.md +37 -0
  270. package/docs/fundraising/metrics-and-evidence.md +30 -0
  271. package/docs/fundraising/roadmap-12-month.md +25 -0
  272. package/docs/fundraising/technical-due-diligence-index.md +37 -0
  273. package/docs/fundraising/yc-application.md +25 -0
  274. package/docs/github-actions.md +84 -0
  275. package/docs/hosted-pilot.md +111 -0
  276. package/docs/mcp.md +52 -50
  277. package/docs/operations/disaster-recovery-runbook.md +81 -0
  278. package/docs/operations/kill-switch-runbook.md +32 -0
  279. package/docs/policy.md +61 -0
  280. package/docs/providers/authoring-packs.md +175 -0
  281. package/docs/providers/stripe-core-pack.md +82 -0
  282. package/docs/release-checklist.md +83 -71
  283. package/docs/release-migration-and-rollback.md +63 -0
  284. package/docs/release-readiness.md +39 -0
  285. package/docs/security/action-gateway-threat-model.md +34 -0
  286. package/docs/security/action-ledger-incident-replay-threat-model.md +42 -0
  287. package/docs/security/approval-inbox-threat-model.md +35 -0
  288. package/docs/security/credential-broker-threat-model.md +52 -0
  289. package/docs/security/egress-threat-model.md +126 -0
  290. package/docs/security/inventory-threat-model.md +104 -0
  291. package/docs/security/kill-switch-budgets-threat-model.md +37 -0
  292. package/docs/security/reliability-threat-model.md +111 -0
  293. package/docs/security/trust-ladder-threat-model.md +33 -0
  294. package/docs/team-control-plane.md +72 -0
  295. package/docs/usage.md +571 -57
  296. package/examples/README.md +10 -0
  297. package/examples/agent-instructions/README.md +39 -39
  298. package/examples/ci-smoke/README.md +21 -0
  299. package/examples/ci-smoke/ghostapi.policy.yaml +22 -0
  300. package/examples/ci-smoke/package.json +9 -0
  301. package/examples/ci-smoke/production-egress.mjs +2 -0
  302. package/examples/ci-smoke/safe.mjs +17 -0
  303. package/examples/evals/README.md +24 -0
  304. package/examples/evals/retry-after.eval.json +52 -0
  305. package/examples/generic-rest/README.md +27 -27
  306. package/examples/github-fetch/README.md +35 -35
  307. package/examples/openai-streaming/README.md +16 -0
  308. package/examples/openai-streaming/streaming-tool-call.mjs +31 -0
  309. package/examples/policy/ghostapi.policy.yaml +27 -0
  310. package/examples/record-replay/README.md +19 -0
  311. package/examples/record-replay/replay-requests.json +22 -0
  312. package/examples/record-replay/stripe-checkout.har.json +30 -0
  313. package/examples/resend-node/README.md +22 -22
  314. package/examples/stripe-node/README.md +35 -29
  315. package/examples/stripe-node/checkout-flow.mjs +61 -0
  316. package/examples/twilio-node/README.md +21 -21
  317. package/examples/worlds/README.md +19 -0
  318. package/examples/worlds/subscription-recovery.mjs +12 -0
  319. package/package.json +26 -11
  320. package/dist/providers/stripe.d.ts +0 -2
  321. package/dist/providers/stripe.js +0 -15
  322. package/dist/providers/stripe.js.map +0 -1
  323. package/docs/assets/ghostapi-avatar.png +0 -0
package/dist/cli/index.js CHANGED
@@ -2,22 +2,35 @@
2
2
  import http from "node:http";
3
3
  import https from "node:https";
4
4
  import net from "node:net";
5
- import { access, mkdir } from "node:fs/promises";
5
+ import { access, lstat, mkdir, readFile, realpath } from "node:fs/promises";
6
+ import { isAbsolute, relative, resolve, sep } from "node:path";
6
7
  import { constants } from "node:fs";
7
- import { spawn } from "node:child_process";
8
8
  import { generate } from "selfsigned";
9
9
  import { createServer } from "../server/createServer.js";
10
10
  import { clearCache } from "../cache/index.js";
11
11
  import { clearState } from "../state/stateStore.js";
12
12
  import { clearEvents } from "../server/eventsStore.js";
13
13
  import { DEFAULT_MODEL, loadServerConfig } from "../config/serverConfig.js";
14
- import { CONFIG_PATH, GHOSTAPI_DIR, initializeLocalConfig, readLocalConfig, writeLocalConfig } from "../config/localConfig.js";
15
- import { isRegisteredProvider, providerRegistry } from "../providers/registry.js";
14
+ import { initializeLocalConfig, readLocalConfig, writeLocalConfig } from "../config/localConfig.js";
15
+ import { getDataPaths } from "../config/dataPaths.js";
16
+ import { detectEgressCapabilities, formatEgressCapabilityReport } from "../egress/capabilities.js";
17
+ import { EgressRunError, runEgressCommand } from "../egress/run.js";
18
+ import { PolicyValidationError, evaluatePolicy, formatPolicyDecision, loadPolicyFile } from "../policy/index.js";
19
+ import { isLoopbackHost } from "../server/accessControl.js";
20
+ import { getProviderManifests, isRegisteredProvider, providerRegistry } from "../providers/registry.js";
16
21
  import { parseCliArgs } from "./parser.js";
17
22
  import { CliError } from "./errors.js";
18
23
  import { startMcpServer } from "../mcp/server.js";
19
24
  import { generateRepoSetup, writeRepoSetup } from "../setup/setupGenerator.js";
20
25
  import { generateSafetyReport } from "../report/safetyReport.js";
26
+ import { compareEvidenceReports, formatEvidenceCompare, formatEvidenceReport, generateEvidenceReport, loadEvidenceReport, EvidenceReportError } from "../evidence/index.js";
27
+ import { createScenarioReplayer, formatScenarioSanitizationSummary, loadScenarioBundle, prepareScenarioRecordingFromFile, ScenarioBundleError, writeScenarioBundle } from "../scenarios/scenarioBundle.js";
28
+ import { ContractError, diffContracts, formatContractDiff, importHarContractFromFile, importOpenApiContractFromFile, loadContract, writeContract } from "../contracts/index.js";
29
+ import { EvalError, formatEvalReport, runEval } from "../evals/index.js";
30
+ import { SyntheticWorldError, createWorld, forkWorld, formatWorld, inspectWorld, resetWorld } from "../worlds/index.js";
31
+ import { formatProductTelemetry, readProductTelemetry, recordProductTelemetry, setProductTelemetryEnabled } from "../productTelemetry/index.js";
32
+ import { ActionGatewayError, createLocalActionGateway } from "../actions/index.js";
33
+ import { openUrl } from "./openUrl.js";
21
34
  async function main() {
22
35
  const command = parseCliArgs(process.argv.slice(2));
23
36
  switch (command.name) {
@@ -42,7 +55,64 @@ async function main() {
42
55
  inspectProvider(command.provider);
43
56
  return;
44
57
  case "doctor":
45
- await runDoctor(command.options.port);
58
+ await runDoctor(command.options);
59
+ return;
60
+ case "run": {
61
+ const result = await runEgressCommand(command.options);
62
+ await recordLocalProductTelemetry("enforced_run_completed");
63
+ process.exitCode = result.exitCode;
64
+ return;
65
+ }
66
+ case "policy-validate":
67
+ await validatePolicy(command.file);
68
+ return;
69
+ case "policy-explain":
70
+ await explainPolicy(command.file, command.event);
71
+ return;
72
+ case "evidence-generate":
73
+ await generateEvidence(command.options);
74
+ return;
75
+ case "evidence-view":
76
+ await viewEvidence(command.options);
77
+ return;
78
+ case "evidence-compare":
79
+ await compareEvidence(command.options);
80
+ return;
81
+ case "record":
82
+ await recordScenario(command.options);
83
+ return;
84
+ case "replay":
85
+ await replayBundle(command.options);
86
+ return;
87
+ case "contract-import-openapi":
88
+ await importOpenApi(command.options);
89
+ return;
90
+ case "contract-import-har":
91
+ await importHar(command.options);
92
+ return;
93
+ case "contract-diff":
94
+ await diffContractFiles(command.options);
95
+ return;
96
+ case "eval":
97
+ await runEvalCommand(command.options);
98
+ return;
99
+ case "world-create":
100
+ await createWorldCommand(command.options);
101
+ return;
102
+ case "world-inspect":
103
+ await inspectWorldCommand(command.id, command.json === true);
104
+ return;
105
+ case "world-reset":
106
+ await resetWorldCommand(command.id, command.json === true);
107
+ return;
108
+ case "world-fork":
109
+ await forkWorldCommand(command.sourceId, command.options);
110
+ return;
111
+ case "action-inspect":
112
+ await inspectActionCommand(command.actionId, command.json === true);
113
+ return;
114
+ case "telemetry":
115
+ await runTelemetryCommand(command.action, command.json === true);
46
116
  return;
47
117
  case "init":
48
118
  await initProject();
@@ -73,7 +143,9 @@ async function startServer(config, open = false) {
73
143
  server.on("error", (error) => {
74
144
  if (error.code === "EADDRINUSE") {
75
145
  console.error(`Error: Port ${config.port} is already in use.`);
76
- console.error(`Hint: Run ghostapi start --port <free-port> or stop the process using ${config.port}.`);
146
+ console.error(`Reason: Another process is already listening on ${config.host}:${config.port}.`);
147
+ console.error(`Remediation: Run ghostapi start --port <free-port> or stop the process using ${config.port}.`);
148
+ console.error("Docs: README.md#quickstart and docs/usage.md");
77
149
  process.exit(1);
78
150
  }
79
151
  console.error(`Error: ${error.message}`);
@@ -81,11 +153,15 @@ async function startServer(config, open = false) {
81
153
  });
82
154
  server.listen(config.port, config.host, () => {
83
155
  const dashboardUrl = `${protocol}://${config.host}:${config.port}/dashboard`;
156
+ const remote = !isLoopbackHost(config.host);
84
157
  console.log(`GhostAPI listening on ${protocol}://${config.host}:${config.port}`);
85
158
  console.log(`Dashboard: ${dashboardUrl}`);
86
- console.log(`Model: ${config.model}${config.offline ? " (offline)" : ""}`);
159
+ console.log(`Model: ${config.model}${config.offline ? " (offline)" : config.allowExternalLlm ? " (external LLM opt-in enabled)" : " (local only)"}`);
160
+ if (remote) {
161
+ console.warn("Warning: GhostAPI is bound beyond loopback. Use HTTPS or a secure tunnel because the dashboard token does not encrypt traffic.");
162
+ }
87
163
  if (open)
88
- openUrl(dashboardUrl);
164
+ openUrl(remote && config.authToken ? `${dashboardUrl}?token=${encodeURIComponent(config.authToken)}` : dashboardUrl);
89
165
  });
90
166
  }
91
167
  async function createDevTlsOptions(host) {
@@ -112,8 +188,16 @@ async function clearTarget(target) {
112
188
  }
113
189
  async function initProject() {
114
190
  const result = await initializeLocalConfig();
115
- console.log(result.created ? `Created ${CONFIG_PATH}.` : `${CONFIG_PATH} already exists.`);
191
+ const setup = await writeRepoSetup();
192
+ const configPath = getDataPaths().config;
193
+ console.log(result.created ? `Created ${configPath}.` : `${configPath} already exists.`);
116
194
  console.log(`Model: ${result.config.model ?? DEFAULT_MODEL}`);
195
+ console.log(`Created setup files: ${setup.result.created.length > 0 ? setup.result.created.join(", ") : "none"}.`);
196
+ console.log(`Skipped existing setup files: ${setup.result.skipped.length > 0 ? setup.result.skipped.join(", ") : "none"}.`);
197
+ console.log("Next: ghostapi doctor");
198
+ console.log("Then on supported Linux hosts: ghostapi run -- npm test");
199
+ console.log("Otherwise: ghostapi start --open and point SDKs at http://127.0.0.1:8080.");
200
+ await recordLocalProductTelemetry("init_completed");
117
201
  }
118
202
  async function printRepoSetup(write) {
119
203
  const written = write ? await writeRepoSetup() : null;
@@ -146,12 +230,6 @@ function openDashboard(options) {
146
230
  openUrl(url);
147
231
  console.log(`Opened ${url}`);
148
232
  }
149
- function openUrl(url) {
150
- const command = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
151
- const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
152
- const child = spawn(command, args, { detached: true, stdio: "ignore" });
153
- child.unref();
154
- }
155
233
  async function printSafetyReport() {
156
234
  const report = await generateSafetyReport();
157
235
  console.log(`GhostAPI safety report for ${report.projectRoot}`);
@@ -181,8 +259,9 @@ async function setModel(model) {
181
259
  console.log(`Model set to ${model}.`);
182
260
  }
183
261
  function listProviders() {
184
- for (const adapter of Object.values(providerRegistry)) {
185
- console.log(`${adapter.name.padEnd(8)} ${adapter.displayName}`);
262
+ for (const manifest of getProviderManifests()) {
263
+ const version = manifest.packVersion === null ? manifest.implementation : `pack ${manifest.packVersion}`;
264
+ console.log(`${manifest.name.padEnd(8)} ${manifest.displayName} (${version})`);
186
265
  }
187
266
  }
188
267
  function inspectProvider(provider) {
@@ -190,47 +269,301 @@ function inspectProvider(provider) {
190
269
  throw new CliError(`Unknown provider: ${provider}`, `Run ghostapi providers list to see supported providers.`);
191
270
  }
192
271
  const adapter = providerRegistry[provider];
272
+ const manifest = getProviderManifests().find((candidate) => candidate.name === provider);
273
+ if (manifest === undefined)
274
+ throw new CliError(`Provider manifest missing: ${provider}`);
193
275
  console.log(`${adapter.displayName}`);
194
276
  console.log(`Name: ${adapter.name}`);
195
- console.log(`Mode: ${adapter.name === "generic" ? "fallback" : "built-in adapter"}`);
277
+ console.log(`Mode: ${manifest.implementation}`);
278
+ console.log(`Pack version: ${manifest.packVersion ?? "n/a"}`);
279
+ console.log(`API versions: ${manifest.apiVersions?.supported.join(", ") ?? "legacy"}`);
280
+ console.log(`Capabilities: ${Object.entries(manifest.capabilities).filter(([, enabled]) => enabled).map(([name]) => name).join(", ")}`);
196
281
  console.log("Error formatting: supported");
197
282
  }
198
- async function runDoctor(portOverride) {
199
- const config = loadServerConfig(process.env, [], portOverride ? { port: portOverride } : {});
283
+ async function runDoctor(options) {
284
+ if (options.egress) {
285
+ const report = detectEgressCapabilities();
286
+ console.log(options.json ? JSON.stringify(report, null, 2) : formatEgressCapabilityReport(report));
287
+ return;
288
+ }
200
289
  const checks = [];
290
+ let config = null;
291
+ try {
292
+ config = loadServerConfig(process.env, [], options.port ? { port: options.port } : {});
293
+ checks.push({ label: "Config", status: "ok", detail: "loaded", docs: "docs/usage.md#configuration" });
294
+ }
295
+ catch (error) {
296
+ checks.push({ label: "Config", status: "fail", detail: error instanceof Error ? error.message : "invalid configuration", remediation: "Fix GHOSTAPI_* environment values or .ghostapi/config.json, then re-run ghostapi doctor.", docs: "docs/usage.md#configuration" });
297
+ }
201
298
  const major = Number(process.versions.node.split(".")[0]);
202
- checks.push({ label: "Node version", ok: major >= 20, detail: process.versions.node, hint: "Install Node.js 20 or newer." });
299
+ checks.push({ label: "Node version", status: major >= 20 ? "ok" : "fail", detail: process.versions.node, remediation: "Install Node.js 20 or newer.", docs: "README.md#quickstart" });
203
300
  const writeAccess = await canWriteGhostApiDir();
204
- checks.push({ label: ".ghostapi write access", ok: writeAccess, detail: GHOSTAPI_DIR, hint: "Check directory permissions in the project root." });
205
- const portAvailable = await isPortAvailable(config.host, config.port);
206
- checks.push({ label: "Port availability", ok: portAvailable, detail: `${config.host}:${config.port}`, hint: `Run ghostapi start --port <free-port>.` });
207
- checks.push({ label: "Model config", ok: config.model.trim() !== "", detail: config.model, hint: "Run ghostapi model set <model>." });
208
- const hasApiKey = Boolean(config.apiKey);
301
+ checks.push({ label: "GhostAPI data write access", status: writeAccess ? "ok" : "fail", detail: getDataPaths().root, remediation: "Check GHOSTAPI_DATA_DIR and directory permissions.", docs: "README.md#local-files" });
302
+ if (config !== null) {
303
+ const portAvailable = await isPortAvailable(config.host, config.port);
304
+ checks.push({ label: "Port availability", status: portAvailable ? "ok" : "fail", detail: `${config.host}:${config.port}`, remediation: `Run ghostapi start --port <free-port> or stop the process using ${config.port}.`, docs: "README.md#quickstart" });
305
+ checks.push({ label: "Model config", status: config.model.trim() !== "" ? "ok" : "fail", detail: config.model, remediation: "Run ghostapi model set <model>.", docs: "docs/usage.md#configuration" });
306
+ const hasApiKey = Boolean(config.apiKey);
307
+ checks.push({
308
+ label: "LLM API key",
309
+ status: config.allowExternalLlm && !hasApiKey ? "fail" : "ok",
310
+ detail: config.offline ? "offline mode" : config.allowExternalLlm ? hasApiKey ? "explicitly enabled" : "enabled without GHOSTAPI_LLM_API_KEY" : "disabled; deterministic provider mocks enabled",
311
+ remediation: config.allowExternalLlm && !hasApiKey ? "Set GHOSTAPI_LLM_API_KEY or remove the external LLM opt-in." : undefined,
312
+ docs: "README.md#safety-model"
313
+ });
314
+ const remote = !isLoopbackHost(config.host);
315
+ checks.push({
316
+ label: "Remote bind auth",
317
+ status: !remote || (config.authToken !== undefined && config.authToken.length >= 24) ? "ok" : "fail",
318
+ detail: remote ? "non-loopback host configured" : "loopback only",
319
+ remediation: remote ? "Set a strong GHOSTAPI_AUTH_TOKEN and use HTTPS or a secure tunnel." : undefined,
320
+ docs: "README.md#safety-model"
321
+ });
322
+ }
323
+ const tlsBypass = process.env.NODE_TLS_REJECT_UNAUTHORIZED === "0";
324
+ checks.push({ label: "TLS safety", status: !tlsBypass ? "ok" : "fail", detail: tlsBypass ? "NODE_TLS_REJECT_UNAUTHORIZED=0" : "safe", remediation: "Unset NODE_TLS_REJECT_UNAUTHORIZED.", docs: "SECURITY.md" });
325
+ const egress = detectEgressCapabilities();
326
+ const linuxNamespace = egress.capabilities.find((capability) => capability.id === "linux-network-namespace");
327
+ const enforcementAvailable = process.platform === "linux" && linuxNamespace !== undefined;
209
328
  checks.push({
210
- label: "LLM API key",
211
- ok: true,
212
- detail: config.offline ? "offline mode" : hasApiKey ? "present" : "not required; deterministic provider mocks enabled",
213
- hint: hasApiKey || config.offline ? undefined : "Set GHOSTAPI_LLM_API_KEY only if you want AI-generated mocks."
329
+ label: "Enforcement capability",
330
+ status: enforcementAvailable ? "warn" : "warn",
331
+ detail: enforcementAvailable ? linuxNamespace.detail : `ghostapi run process isolation is unsupported/experimental on ${process.platform}`,
332
+ remediation: enforcementAvailable ? "Run ghostapi run to execute the real preflight; doctor does not launch the target." : "Use ghostapi start --open for local provider simulation, or run enforced tests on Linux.",
333
+ docs: "docs/security/egress-threat-model.md"
214
334
  });
215
- const tlsBypass = process.env.NODE_TLS_REJECT_UNAUTHORIZED === "0";
216
- checks.push({ label: "TLS safety", ok: !tlsBypass, detail: tlsBypass ? "NODE_TLS_REJECT_UNAUTHORIZED=0" : "safe", hint: "Unset NODE_TLS_REJECT_UNAUTHORIZED." });
217
- let failed = 0;
218
- for (const check of checks) {
219
- const marker = check.ok ? "ok" : "fail";
220
- console.log(`${marker.padEnd(4)} ${check.label}: ${check.detail}`);
221
- if (!check.ok) {
222
- failed += 1;
223
- if (check.hint)
224
- console.log(` Hint: ${check.hint}`);
335
+ if (options.json) {
336
+ console.log(JSON.stringify({ schemaVersion: 1, checks, egress }, null, 2));
337
+ }
338
+ else {
339
+ for (const check of checks) {
340
+ console.log(`${check.status.padEnd(4)} ${check.label}: ${check.detail}`);
341
+ if (check.status !== "ok" && check.remediation)
342
+ console.log(` Remediation: ${check.remediation}`);
343
+ if (check.status !== "ok" && check.docs)
344
+ console.log(` Docs: ${check.docs}`);
225
345
  }
226
346
  }
347
+ const failed = checks.filter((check) => check.status === "fail").length;
227
348
  if (failed > 0)
228
- throw new CliError(`Doctor found ${failed} issue${failed === 1 ? "" : "s"}.`);
349
+ throw new CliError(`Doctor found ${failed} issue${failed === 1 ? "" : "s"}.`, "Fix the failed checks above, then re-run ghostapi doctor.");
350
+ }
351
+ async function validatePolicy(file) {
352
+ const loaded = await loadPolicyFile(file, process.cwd(), true);
353
+ if (loaded === null)
354
+ throw new CliError("Policy file was not found.");
355
+ console.log(`Policy valid: ${loaded.path}`);
356
+ console.log(`Schema version: ${loaded.policy.version}`);
357
+ console.log(`SHA-256: ${loaded.hash}`);
358
+ }
359
+ async function explainPolicy(file, event) {
360
+ const loaded = await loadPolicyFile(file, process.cwd(), true);
361
+ if (loaded === null)
362
+ throw new CliError("Policy file was not found.");
363
+ console.log(formatPolicyDecision(evaluatePolicy(loaded.policy, event)));
364
+ }
365
+ async function generateEvidence(options) {
366
+ const { report, path } = await generateEvidenceReport({ policyPath: options.policyPath, runPath: options.runPath, outPath: options.outPath, contractBaselinePath: options.contractBaselinePath, contractCandidatePath: options.contractCandidatePath });
367
+ if (options.json)
368
+ console.log(JSON.stringify({ path, report }, null, 2));
369
+ else {
370
+ console.log(formatEvidenceReport(report));
371
+ console.log(`Artifact: ${path}`);
372
+ }
373
+ if (options.ci && !report.summary.passed)
374
+ process.exitCode = 2;
375
+ await recordLocalProductTelemetry("evidence_generated");
376
+ }
377
+ async function viewEvidence(options) {
378
+ const report = await loadEvidenceReport(options.path);
379
+ console.log(options.json ? JSON.stringify(report, null, 2) : formatEvidenceReport(report));
380
+ }
381
+ async function compareEvidence(options) {
382
+ const left = await loadEvidenceReport(options.leftPath);
383
+ const right = await loadEvidenceReport(options.rightPath);
384
+ const result = compareEvidenceReports(left, right);
385
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatEvidenceCompare(result));
386
+ if (!result.equal)
387
+ process.exitCode = 1;
388
+ }
389
+ async function recordScenario(options) {
390
+ const bundle = await prepareScenarioRecordingFromFile(options.inputPath, {
391
+ title: options.title,
392
+ allowedSandboxHosts: options.allowedSandboxHosts,
393
+ pii: parsePiiRules(options.pii)
394
+ });
395
+ console.log(formatScenarioSanitizationSummary(bundle.sanitization));
396
+ if (bundle.sanitization.requiresApproval && !options.approve) {
397
+ throw new CliError("Recording was not saved because sanitization found potentially sensitive traffic.", "Review the summary, then re-run the exact command with --approve to save the sanitized bundle.");
398
+ }
399
+ const path = await writeScenarioBundle(bundle, options.outPath);
400
+ console.log(`Bundle: ${path}`);
401
+ }
402
+ async function replayBundle(options) {
403
+ const bundle = await loadScenarioBundle(options.bundlePath);
404
+ const requests = await loadReplayRequests(options.requestsPath);
405
+ const replayer = createScenarioReplayer(bundle);
406
+ const responses = requests.map((request) => replayer.replay(request));
407
+ if (replayer.remaining !== 0)
408
+ throw new CliError(`Replay ended with ${replayer.remaining} interaction${replayer.remaining === 1 ? "" : "s"} remaining.`, "Provide the complete ordered request sequence; GhostAPI never guesses a later match.");
409
+ if (options.json)
410
+ console.log(JSON.stringify({ responses }, null, 2));
411
+ else {
412
+ console.log(`Replay matched ${responses.length} interaction${responses.length === 1 ? "" : "s"}.`);
413
+ for (const response of responses)
414
+ console.log(` ${response.index}: ${response.status}`);
415
+ }
416
+ }
417
+ async function importOpenApi(options) {
418
+ const contract = await importOpenApiContractFromFile(options.inputPath, { title: options.title });
419
+ const path = await writeContract(contract, options.outPath);
420
+ console.log(`Contract: ${path}`);
421
+ console.log(`Operations: ${contract.operations.length}`);
422
+ }
423
+ async function importHar(options) {
424
+ const { bundle, contract } = await importHarContractFromFile(options.inputPath, {
425
+ title: options.title,
426
+ allowedSandboxHosts: options.allowedSandboxHosts,
427
+ pii: parsePiiRules(options.pii)
428
+ });
429
+ console.log(formatScenarioSanitizationSummary(bundle.sanitization));
430
+ if (bundle.sanitization.requiresApproval && !options.approve) {
431
+ throw new CliError("HAR import was not saved because sanitization found potentially sensitive traffic.", "Review the summary, then re-run the exact command with --approve to save the sanitized bundle and contract.");
432
+ }
433
+ const bundlePath = await writeScenarioBundle(bundle, options.outPath);
434
+ const contractPath = await writeContract(contract, options.contractOutPath);
435
+ console.log(`Bundle: ${bundlePath}`);
436
+ console.log(`Contract: ${contractPath}`);
437
+ }
438
+ async function diffContractFiles(options) {
439
+ const [baseline, candidate, loadedPolicy] = await Promise.all([
440
+ loadContract(options.baselinePath),
441
+ loadContract(options.candidatePath),
442
+ options.policyPath === undefined ? Promise.resolve(null) : loadPolicyFile(options.policyPath, process.cwd(), true)
443
+ ]);
444
+ const diff = diffContracts(baseline, candidate);
445
+ const decision = loadedPolicy === null ? null : evaluatePolicy(loadedPolicy.policy, { type: "report", productionEgressAttempts: 0, forbiddenCredentialMatches: 0, breakingContractChanges: diff.summary.breaking });
446
+ if (options.json)
447
+ console.log(JSON.stringify({ diff, policy: decision }, null, 2));
448
+ else {
449
+ console.log(formatContractDiff(diff));
450
+ if (decision !== null)
451
+ console.log(`Policy: ${decision.allowed ? "PASS" : "FAIL"} - ${decision.reason}`);
452
+ }
453
+ if (options.ci && (decision === null ? diff.summary.breaking > 0 : !decision.allowed))
454
+ process.exitCode = 2;
455
+ }
456
+ async function runEvalCommand(options) {
457
+ const { report, path } = await runEval({ specPath: options.specPath, template: options.template, evidencePath: options.evidencePath, outPath: options.outPath });
458
+ if (options.json)
459
+ console.log(JSON.stringify({ path, report }, null, 2));
460
+ else {
461
+ console.log(formatEvalReport(report));
462
+ console.log(`Artifact: ${path}`);
463
+ }
464
+ if (options.ci && !report.score.passed)
465
+ process.exitCode = 2;
466
+ await recordLocalProductTelemetry("eval_completed");
467
+ }
468
+ async function createWorldCommand(options) {
469
+ const world = await createWorld(options);
470
+ console.log(options.json ? JSON.stringify(world, null, 2) : `${formatWorld(world)}\nPath: ${getDataPaths().worlds}`);
471
+ }
472
+ async function inspectWorldCommand(id, json) {
473
+ const world = await inspectWorld(id);
474
+ console.log(json ? JSON.stringify(world, null, 2) : formatWorld(world));
475
+ }
476
+ async function resetWorldCommand(id, json) {
477
+ const world = await resetWorld(id);
478
+ console.log(json ? JSON.stringify(world, null, 2) : `${formatWorld(world)}\nReset: restored baseline state.`);
479
+ }
480
+ async function forkWorldCommand(sourceId, options) {
481
+ const world = await forkWorld(sourceId, options);
482
+ console.log(options.json ? JSON.stringify(world, null, 2) : `${formatWorld(world)}\nForked from: ${sourceId}`);
483
+ }
484
+ async function inspectActionCommand(actionId, json) {
485
+ const record = await createLocalActionGateway().inspect(actionId);
486
+ console.log(json ? JSON.stringify(record, null, 2) : `Action: ${record.envelope.actionId}\nHash: ${record.actionHash}\nReceipts: ${record.receipts.map((receipt) => receipt.status).join(", ")}`);
487
+ }
488
+ async function runTelemetryCommand(action, json) {
489
+ const snapshot = action === "enable"
490
+ ? await setProductTelemetryEnabled(true)
491
+ : action === "disable"
492
+ ? await setProductTelemetryEnabled(false)
493
+ : await readProductTelemetry();
494
+ if (json) {
495
+ console.log(JSON.stringify(snapshot, null, 2));
496
+ return;
497
+ }
498
+ console.log(formatProductTelemetry(snapshot));
499
+ if (action === "disable")
500
+ console.log("Existing local counters were deleted.");
501
+ if (action === "enable")
502
+ console.log("Only aggregate event counts and up to eight ISO weeks are recorded locally after this command; no network export exists.");
503
+ }
504
+ async function recordLocalProductTelemetry(event) {
505
+ try {
506
+ await recordProductTelemetry(event);
507
+ }
508
+ catch {
509
+ console.error("Warning: local opt-in product telemetry was not recorded; no telemetry was sent.");
510
+ }
511
+ }
512
+ function parsePiiRules(value) {
513
+ if (value === undefined)
514
+ return undefined;
515
+ const selected = new Set(value === "none" ? [] : value.split(","));
516
+ return { emails: selected.has("emails"), phones: selected.has("phones"), addresses: selected.has("addresses") };
517
+ }
518
+ async function loadReplayRequests(path) {
519
+ const target = isAbsolute(path) ? resolve(path) : resolve(process.cwd(), path);
520
+ if (!isInsideAllowedRoots(target))
521
+ throw new CliError("Replay requests path traversal outside the project root or GHOSTAPI_DATA_DIR is not allowed.");
522
+ const info = await lstat(target);
523
+ if (!info.isFile() || info.isSymbolicLink())
524
+ throw new CliError("Replay requests input must be a regular non-symlink file.");
525
+ if (!await isRealPathInsideAllowedRoots(target))
526
+ throw new CliError("Replay requests input resolves outside the project root or GHOSTAPI_DATA_DIR through a symlink.");
527
+ const source = await readFile(target, "utf8");
528
+ if (Buffer.byteLength(source, "utf8") > 1024 * 1024)
529
+ throw new CliError("Replay requests input exceeds 1048576 bytes.");
530
+ let parsed;
531
+ try {
532
+ parsed = JSON.parse(source);
533
+ }
534
+ catch {
535
+ throw new CliError("Replay requests input is not valid JSON.");
536
+ }
537
+ const requests = Array.isArray(parsed) ? parsed : parsed !== null && typeof parsed === "object" && Array.isArray(parsed.requests) ? parsed.requests : null;
538
+ if (requests === null || requests.length === 0 || requests.length > 100)
539
+ throw new CliError("Replay requests input must contain 1-100 requests.");
540
+ return requests.map((request) => {
541
+ if (request === null || typeof request !== "object" || Array.isArray(request))
542
+ throw new CliError("Replay request must be an object.");
543
+ const candidate = request;
544
+ if (typeof candidate.method !== "string" || typeof candidate.path !== "string")
545
+ throw new CliError("Replay request requires string method and path.");
546
+ return { method: candidate.method, path: candidate.path, headers: candidate.headers, body: candidate.body };
547
+ });
548
+ }
549
+ function isInsideAllowedRoots(target) {
550
+ return isInside(process.cwd(), target) || isInside(getDataPaths().root, target);
551
+ }
552
+ function isInside(root, target) {
553
+ const relativePath = relative(resolve(root), target);
554
+ return relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath);
555
+ }
556
+ async function isRealPathInsideAllowedRoots(target) {
557
+ const realTarget = await realpath(target);
558
+ const realProjectRoot = await realpath(process.cwd());
559
+ const dataRoot = await realpath(getDataPaths().root).catch(() => null);
560
+ return isInside(realProjectRoot, realTarget) || (dataRoot !== null && isInside(dataRoot, realTarget));
229
561
  }
230
562
  async function canWriteGhostApiDir() {
563
+ const dataDir = getDataPaths().root;
231
564
  try {
232
- await mkdir(GHOSTAPI_DIR, { recursive: true });
233
- await access(GHOSTAPI_DIR, constants.W_OK);
565
+ await mkdir(dataDir, { recursive: true });
566
+ await access(dataDir, constants.W_OK);
234
567
  return true;
235
568
  }
236
569
  catch {
@@ -246,29 +579,76 @@ function isPortAvailable(host, port) {
246
579
  });
247
580
  }
248
581
  function printHelp() {
249
- console.log(`GhostAPI CLI
250
-
251
- Usage:
252
- ghostapi start [--host 127.0.0.1] [--port 8080] [--model gpt-4o-mini] [--offline] [--https] [--open]
253
- ghostapi open [--host 127.0.0.1] [--port 8080] [--https]
254
- ghostapi clear cache|state|events|all
255
- ghostapi model get
256
- ghostapi model set <model>
257
- ghostapi providers list
258
- ghostapi providers inspect <provider>
259
- ghostapi setup [--write]
260
- ghostapi report
261
- ghostapi mcp
262
- ghostapi doctor [--port 8080]
582
+ console.log(`GhostAPI CLI
583
+
584
+ Usage:
585
+ ghostapi start [--host 127.0.0.1] [--port 8080] [--model gpt-4o-mini] [--offline] [--https] [--allow-external-llm] [--open]
586
+ ghostapi open [--host 127.0.0.1] [--port 8080] [--https]
587
+ ghostapi clear cache|state|events|all
588
+ ghostapi model get
589
+ ghostapi model set <model>
590
+ ghostapi providers list
591
+ ghostapi providers inspect <provider>
592
+ ghostapi setup [--write]
593
+ ghostapi report
594
+ ghostapi mcp
595
+ ghostapi doctor [--port 8080] [--json]
596
+ ghostapi doctor --egress [--json]
597
+ ghostapi run [--port 8080] [--allow-host localhost] [--policy ghostapi.policy.yaml] -- <command> [args...]
598
+ ghostapi policy validate [--file ghostapi.policy.yaml]
599
+ ghostapi policy explain <scenario-id>|network <host>|credential <value>|enforcement <mode>|report <production-attempts> <credential-matches> [--file ghostapi.policy.yaml]
600
+ ghostapi evidence generate [--policy ghostapi.policy.yaml] [--run .ghostapi/runs/<id>/run.json] [--out .ghostapi/reports/report.json] [--contract-baseline base.contract.json --contract-candidate head.contract.json] [--ci] [--json]
601
+ ghostapi evidence view <report.json> [--json]
602
+ ghostapi evidence compare <left.json> <right.json> [--json]
603
+ ghostapi record --input <capture.json|har.json> --allow-sandbox-host <host> [--out bundle.json] [--title title] [--pii emails,phones,addresses] [--approve]
604
+ ghostapi replay <bundle.json> --requests <requests.json> [--json]
605
+ ghostapi contract import-openapi --input <openapi.json> [--out contract.json] [--title title]
606
+ ghostapi contract import-har --input <capture.har> --allow-sandbox-host <host> [--out bundle.json] [--contract-out contract.json] [--title title] [--pii emails,phones,addresses] [--approve]
607
+ ghostapi contract diff --baseline <contract.json> --candidate <contract.json> [--policy ghostapi.policy.yaml] [--ci] [--json]
608
+ ghostapi eval --spec <eval.json>|--template <name> [--evidence report.json] [--out report.eval.json] [--ci] [--json]
609
+ ghostapi world create --id <world-id> --seed <seed> [--title title] [--json]
610
+ ghostapi world inspect <world-id> [--json]
611
+ ghostapi world reset <world-id> [--json]
612
+ ghostapi world fork <source-world-id> --id <fork-world-id> [--title title] [--json]
613
+ ghostapi action inspect <action-id> [--json]
614
+ ghostapi telemetry status|enable|disable|export [--json]
263
615
  ghostapi init`);
264
616
  }
265
617
  main().catch((error) => {
266
- if (error instanceof CliError) {
618
+ if (error instanceof CliError || error instanceof EgressRunError) {
267
619
  console.error(`Error: ${error.message}`);
268
620
  if (error.hint)
269
621
  console.error(`Hint: ${error.hint}`);
270
622
  process.exit(1);
271
623
  }
624
+ if (error instanceof PolicyValidationError) {
625
+ console.error(`Error: ${error.message}`);
626
+ process.exit(1);
627
+ }
628
+ if (error instanceof EvidenceReportError) {
629
+ console.error(`Error: ${error.message}`);
630
+ process.exit(1);
631
+ }
632
+ if (error instanceof ScenarioBundleError) {
633
+ console.error(`Error: ${error.message}`);
634
+ process.exit(1);
635
+ }
636
+ if (error instanceof ContractError) {
637
+ console.error(`Error: ${error.message}`);
638
+ process.exit(1);
639
+ }
640
+ if (error instanceof EvalError) {
641
+ console.error(`Error: ${error.message}`);
642
+ process.exit(1);
643
+ }
644
+ if (error instanceof SyntheticWorldError) {
645
+ console.error(`Error: ${error.message}`);
646
+ process.exit(1);
647
+ }
648
+ if (error instanceof ActionGatewayError) {
649
+ console.error(`Error: ${error.message}`);
650
+ process.exit(1);
651
+ }
272
652
  const message = error instanceof Error ? error.message : "Unknown CLI error";
273
653
  console.error(`Error: ${message}`);
274
654
  console.error("Hint: Run ghostapi doctor for environment diagnostics.");