@yiaany/ghostapi 0.1.8 → 0.2.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.
Files changed (293) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/CONTRIBUTING.md +132 -119
  3. package/LICENSE +21 -21
  4. package/PROVENANCE.md +13 -0
  5. package/README.md +78 -80
  6. package/SECURITY.md +56 -53
  7. package/dist/actions/gateway.js +320 -72
  8. package/dist/actions/gateway.js.map +1 -1
  9. package/dist/actions/index.d.ts +2 -2
  10. package/dist/actions/index.js +1 -1
  11. package/dist/actions/index.js.map +1 -1
  12. package/dist/agents/agentPrompt.js +7 -5
  13. package/dist/agents/agentPrompt.js.map +1 -1
  14. package/dist/ai/aiClient.js +5 -5
  15. package/dist/ai/aiClient.js.map +1 -1
  16. package/dist/ai/aiGenerator.js +66 -26
  17. package/dist/ai/aiGenerator.js.map +1 -1
  18. package/dist/ai/genericInference.js +151 -36
  19. package/dist/ai/genericInference.js.map +1 -1
  20. package/dist/ai/prompts.js +55 -49
  21. package/dist/ai/prompts.js.map +1 -1
  22. package/dist/approvals/approvalInbox.js +649 -133
  23. package/dist/approvals/approvalInbox.js.map +1 -1
  24. package/dist/approvals/index.d.ts +2 -2
  25. package/dist/approvals/index.js +1 -1
  26. package/dist/approvals/index.js.map +1 -1
  27. package/dist/behavior/behaviorStore.js +34 -14
  28. package/dist/behavior/behaviorStore.js.map +1 -1
  29. package/dist/cache/index.js +14 -5
  30. package/dist/cache/index.js.map +1 -1
  31. package/dist/cli/index.js +226 -93
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/openUrl.js +17 -3
  34. package/dist/cli/openUrl.js.map +1 -1
  35. package/dist/cli/parser.js +79 -18
  36. package/dist/cli/parser.js.map +1 -1
  37. package/dist/config/dataPaths.js +4 -2
  38. package/dist/config/dataPaths.js.map +1 -1
  39. package/dist/config/localConfig.js +5 -2
  40. package/dist/config/localConfig.js.map +1 -1
  41. package/dist/config/serverConfig.js +11 -5
  42. package/dist/config/serverConfig.js.map +1 -1
  43. package/dist/contracts/contract.js +311 -83
  44. package/dist/contracts/contract.js.map +1 -1
  45. package/dist/contracts/index.d.ts +2 -2
  46. package/dist/contracts/index.js +1 -1
  47. package/dist/contracts/index.js.map +1 -1
  48. package/dist/credentials/credentialBroker.js +391 -79
  49. package/dist/credentials/credentialBroker.js.map +1 -1
  50. package/dist/credentials/index.d.ts +2 -2
  51. package/dist/credentials/index.js +1 -1
  52. package/dist/credentials/index.js.map +1 -1
  53. package/dist/dashboard/app.js +823 -685
  54. package/dist/dashboard/dashboard.js.map +1 -1
  55. package/dist/dashboard/index.html +704 -267
  56. package/dist/dashboard/styles.css +1040 -349
  57. package/dist/egress/capabilities.js +55 -25
  58. package/dist/egress/capabilities.js.map +1 -1
  59. package/dist/egress/linuxBootstrap.js +47 -11
  60. package/dist/egress/linuxBootstrap.js.map +1 -1
  61. package/dist/egress/run.js +170 -31
  62. package/dist/egress/run.js.map +1 -1
  63. package/dist/errors/errorEngine.js +15 -3
  64. package/dist/errors/errorEngine.js.map +1 -1
  65. package/dist/errors/providerErrors.js.map +1 -1
  66. package/dist/evals/evals.js +304 -78
  67. package/dist/evals/evals.js.map +1 -1
  68. package/dist/evals/index.d.ts +2 -2
  69. package/dist/evals/index.js +1 -1
  70. package/dist/evals/index.js.map +1 -1
  71. package/dist/evidence/index.d.ts +2 -2
  72. package/dist/evidence/index.js +1 -1
  73. package/dist/evidence/index.js.map +1 -1
  74. package/dist/evidence/report.d.ts +1 -1
  75. package/dist/evidence/report.js +263 -79
  76. package/dist/evidence/report.js.map +1 -1
  77. package/dist/fault/faultLab.js +24 -9
  78. package/dist/fault/faultLab.js.map +1 -1
  79. package/dist/index.d.ts +42 -42
  80. package/dist/index.js +21 -21
  81. package/dist/index.js.map +1 -1
  82. package/dist/inventory/analysis.d.ts +1 -1
  83. package/dist/inventory/analysis.js +174 -50
  84. package/dist/inventory/analysis.js.map +1 -1
  85. package/dist/inventory/index.d.ts +4 -4
  86. package/dist/inventory/index.js +3 -3
  87. package/dist/inventory/index.js.map +1 -1
  88. package/dist/inventory/inventoryController.js +362 -115
  89. package/dist/inventory/inventoryController.js.map +1 -1
  90. package/dist/inventory/types.js +680 -140
  91. package/dist/inventory/types.js.map +1 -1
  92. package/dist/ledger/actionLedger.js +355 -78
  93. package/dist/ledger/actionLedger.js.map +1 -1
  94. package/dist/ledger/index.d.ts +2 -2
  95. package/dist/ledger/index.js +1 -1
  96. package/dist/ledger/index.js.map +1 -1
  97. package/dist/mcp/server.js +26 -11
  98. package/dist/mcp/server.js.map +1 -1
  99. package/dist/policy/index.d.ts +2 -2
  100. package/dist/policy/index.js +1 -1
  101. package/dist/policy/index.js.map +1 -1
  102. package/dist/policy/policy.js +112 -32
  103. package/dist/policy/policy.js.map +1 -1
  104. package/dist/productTelemetry/index.js +71 -20
  105. package/dist/productTelemetry/index.js.map +1 -1
  106. package/dist/providers/conformance.js +10 -5
  107. package/dist/providers/conformance.js.map +1 -1
  108. package/dist/providers/discord.js +2 -2
  109. package/dist/providers/generic.js +3 -3
  110. package/dist/providers/github.js +2 -2
  111. package/dist/providers/index.d.ts +3 -3
  112. package/dist/providers/index.js +2 -2
  113. package/dist/providers/index.js.map +1 -1
  114. package/dist/providers/openai.js +3 -3
  115. package/dist/providers/packs/resendPack.js +51 -25
  116. package/dist/providers/packs/resendPack.js.map +1 -1
  117. package/dist/providers/packs/stripePack.js +590 -121
  118. package/dist/providers/packs/stripePack.js.map +1 -1
  119. package/dist/providers/registry.js +4 -4
  120. package/dist/providers/registry.js.map +1 -1
  121. package/dist/providers/runtime.js +7 -6
  122. package/dist/providers/runtime.js.map +1 -1
  123. package/dist/providers/stripeWebhook.js +39 -10
  124. package/dist/providers/stripeWebhook.js.map +1 -1
  125. package/dist/providers/twilio.js +2 -2
  126. package/dist/proxy/cacheKey.js +9 -2
  127. package/dist/proxy/cacheKey.js.map +1 -1
  128. package/dist/proxy/providerDetector.js +22 -9
  129. package/dist/proxy/providerDetector.js.map +1 -1
  130. package/dist/proxy/proxyHandler.js +57 -22
  131. package/dist/proxy/proxyHandler.js.map +1 -1
  132. package/dist/proxy/requestNormalizer.js +4 -2
  133. package/dist/proxy/requestNormalizer.js.map +1 -1
  134. package/dist/reliability/costGovernance.js +211 -49
  135. package/dist/reliability/costGovernance.js.map +1 -1
  136. package/dist/reliability/index.d.ts +8 -8
  137. package/dist/reliability/index.js +4 -4
  138. package/dist/reliability/index.js.map +1 -1
  139. package/dist/reliability/reconciliation.js +274 -65
  140. package/dist/reliability/reconciliation.js.map +1 -1
  141. package/dist/reliability/runtimeHealth.js +127 -35
  142. package/dist/reliability/runtimeHealth.js.map +1 -1
  143. package/dist/reliability/slo.js +257 -50
  144. package/dist/reliability/slo.js.map +1 -1
  145. package/dist/report/safetyReport.d.ts +6 -0
  146. package/dist/report/safetyReport.js +56 -12
  147. package/dist/report/safetyReport.js.map +1 -1
  148. package/dist/rules/aiRules.js +5 -5
  149. package/dist/rules/aiRules.js.map +1 -1
  150. package/dist/safety/index.d.ts +2 -2
  151. package/dist/safety/index.js +1 -1
  152. package/dist/safety/index.js.map +1 -1
  153. package/dist/safety/safetyController.js +675 -165
  154. package/dist/safety/safetyController.js.map +1 -1
  155. package/dist/scenarios/scenarioBundle.js +204 -71
  156. package/dist/scenarios/scenarioBundle.js.map +1 -1
  157. package/dist/scenarios/scenarioStore.js +88 -29
  158. package/dist/scenarios/scenarioStore.js.map +1 -1
  159. package/dist/security/headerSanitizer.js +52 -10
  160. package/dist/security/headerSanitizer.js.map +1 -1
  161. package/dist/security/secrets.js +10 -5
  162. package/dist/security/secrets.js.map +1 -1
  163. package/dist/server/accessControl.js +37 -10
  164. package/dist/server/accessControl.js.map +1 -1
  165. package/dist/server/createServer.js +1 -1
  166. package/dist/server/createServer.js.map +1 -1
  167. package/dist/server/eventsStore.js +15 -5
  168. package/dist/server/eventsStore.js.map +1 -1
  169. package/dist/server/routes.js +67 -19
  170. package/dist/server/routes.js.map +1 -1
  171. package/dist/server/sse.js +1 -1
  172. package/dist/server/sse.js.map +1 -1
  173. package/dist/setup/sdkDetector.js +7 -2
  174. package/dist/setup/sdkDetector.js.map +1 -1
  175. package/dist/setup/setupGenerator.js +136 -48
  176. package/dist/setup/setupGenerator.js.map +1 -1
  177. package/dist/state/stateExtractor.js +3 -1
  178. package/dist/state/stateExtractor.js.map +1 -1
  179. package/dist/state/stateResolver.js +10 -7
  180. package/dist/state/stateResolver.js.map +1 -1
  181. package/dist/state/stateStore.js +13 -4
  182. package/dist/state/stateStore.js.map +1 -1
  183. package/dist/storage/fileStore.js +12 -4
  184. package/dist/storage/fileStore.js.map +1 -1
  185. package/dist/teamControl/controlPlane.js +846 -174
  186. package/dist/teamControl/controlPlane.js.map +1 -1
  187. package/dist/teamControl/deployment.js +19 -5
  188. package/dist/teamControl/deployment.js.map +1 -1
  189. package/dist/teamControl/index.d.ts +4 -4
  190. package/dist/teamControl/index.js +2 -2
  191. package/dist/teamControl/index.js.map +1 -1
  192. package/dist/tests/testGenerator.js +15 -7
  193. package/dist/tests/testGenerator.js.map +1 -1
  194. package/dist/trust/index.d.ts +2 -2
  195. package/dist/trust/index.js +1 -1
  196. package/dist/trust/index.js.map +1 -1
  197. package/dist/trust/trustLadder.js +309 -63
  198. package/dist/trust/trustLadder.js.map +1 -1
  199. package/dist/worlds/index.d.ts +2 -2
  200. package/dist/worlds/index.js +1 -1
  201. package/dist/worlds/index.js.map +1 -1
  202. package/dist/worlds/worlds.js +364 -65
  203. package/dist/worlds/worlds.js.map +1 -1
  204. package/docs/assets/dashboard.png +0 -0
  205. package/docs/assets/ghostapi-avatar.png +0 -0
  206. package/docs/ci.md +49 -49
  207. package/docs/github-actions.md +84 -84
  208. package/docs/mcp.md +58 -52
  209. package/docs/policy.md +63 -61
  210. package/docs/providers/authoring-packs.md +180 -175
  211. package/docs/providers/stripe-core-pack.md +84 -82
  212. package/docs/releases/README.md +13 -0
  213. package/docs/security/action-gateway-threat-model.md +34 -34
  214. package/docs/security/action-ledger-incident-replay-threat-model.md +42 -42
  215. package/docs/security/approval-inbox-threat-model.md +35 -35
  216. package/docs/security/credential-broker-threat-model.md +52 -52
  217. package/docs/security/egress-threat-model.md +126 -126
  218. package/docs/security/inventory-threat-model.md +104 -104
  219. package/docs/security/kill-switch-budgets-threat-model.md +37 -37
  220. package/docs/security/reliability-threat-model.md +111 -111
  221. package/docs/security/trust-ladder-threat-model.md +33 -33
  222. package/docs/telemetry.md +14 -0
  223. package/docs/usage.md +680 -571
  224. package/examples/README.md +10 -10
  225. package/examples/agent-instructions/README.md +39 -39
  226. package/examples/ci-smoke/README.md +21 -21
  227. package/examples/ci-smoke/ghostapi.policy.yaml +22 -22
  228. package/examples/ci-smoke/package.json +9 -9
  229. package/examples/ci-smoke/production-egress.mjs +4 -2
  230. package/examples/ci-smoke/safe.mjs +21 -17
  231. package/examples/evals/README.md +24 -24
  232. package/examples/evals/retry-after.eval.json +71 -52
  233. package/examples/generic-rest/README.md +27 -27
  234. package/examples/github-fetch/README.md +38 -35
  235. package/examples/openai-streaming/README.md +16 -16
  236. package/examples/openai-streaming/streaming-tool-call.mjs +44 -31
  237. package/examples/policy/ghostapi.policy.yaml +27 -27
  238. package/examples/record-replay/README.md +19 -19
  239. package/examples/record-replay/replay-requests.json +22 -22
  240. package/examples/record-replay/stripe-checkout.har.json +30 -30
  241. package/examples/resend-node/README.md +22 -22
  242. package/examples/stripe-node/README.md +35 -35
  243. package/examples/stripe-node/checkout-flow.mjs +87 -61
  244. package/examples/twilio-node/README.md +21 -21
  245. package/examples/worlds/README.md +19 -19
  246. package/examples/worlds/subscription-recovery.mjs +21 -12
  247. package/package.json +28 -6
  248. package/dist/landing/assets/index-BXR41fAD.css +0 -1
  249. package/dist/landing/assets/index-CxWsopzP.css +0 -1
  250. package/dist/landing/assets/index-D8_ggMRE.js +0 -55
  251. package/dist/landing/assets/index-DZlpyhqd.js +0 -55
  252. package/dist/landing/assets/index-Dk-Xezs3.js +0 -55
  253. package/dist/landing/index.html +0 -15
  254. package/dist/landing/landing.d.ts +0 -3
  255. package/dist/landing/landing.js +0 -12
  256. package/dist/landing/landing.js.map +0 -1
  257. package/docs/commercial/README.md +0 -29
  258. package/docs/commercial/data-inventory.md +0 -36
  259. package/docs/commercial/manual-invoicing.md +0 -44
  260. package/docs/commercial/metrics.md +0 -51
  261. package/docs/commercial/pricing.md +0 -103
  262. package/docs/commercial/privacy-policy-draft.md +0 -41
  263. package/docs/commercial/terms-topics-for-counsel.md +0 -51
  264. package/docs/design-partners/README.md +0 -52
  265. package/docs/design-partners/demo-narratives.md +0 -33
  266. package/docs/design-partners/design-partner-offer.md +0 -39
  267. package/docs/design-partners/discovery-questions.md +0 -28
  268. package/docs/design-partners/feedback-capture-template.md +0 -48
  269. package/docs/design-partners/icp-one-pager.md +0 -43
  270. package/docs/design-partners/onboarding-checklist.md +0 -33
  271. package/docs/design-partners/pilot-success-criteria.md +0 -27
  272. package/docs/design-partners/pricing-interview-script.md +0 -18
  273. package/docs/design-partners/telemetry-plan.md +0 -48
  274. package/docs/development/baseline.md +0 -114
  275. package/docs/development/onboarding-smoke.md +0 -37
  276. package/docs/development/verification-0.1.8.md +0 -25
  277. package/docs/enterprise-product-roadmap-ru.md +0 -1879
  278. package/docs/fundraising/README.md +0 -87
  279. package/docs/fundraising/data-room-checklist.md +0 -72
  280. package/docs/fundraising/demo-script.md +0 -52
  281. package/docs/fundraising/design-partners-50.md +0 -74
  282. package/docs/fundraising/launch-posts.md +0 -37
  283. package/docs/fundraising/metrics-and-evidence.md +0 -30
  284. package/docs/fundraising/roadmap-12-month.md +0 -25
  285. package/docs/fundraising/technical-due-diligence-index.md +0 -37
  286. package/docs/fundraising/yc-application.md +0 -25
  287. package/docs/hosted-pilot.md +0 -111
  288. package/docs/operations/disaster-recovery-runbook.md +0 -81
  289. package/docs/operations/kill-switch-runbook.md +0 -32
  290. package/docs/release-checklist.md +0 -83
  291. package/docs/release-migration-and-rollback.md +0 -63
  292. package/docs/release-readiness.md +0 -39
  293. package/docs/team-control-plane.md +0 -72
package/dist/cli/index.js CHANGED
@@ -10,26 +10,26 @@ 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
- import { DEFAULT_MODEL, loadServerConfig } from "../config/serverConfig.js";
14
- import { initializeLocalConfig, readLocalConfig, writeLocalConfig } from "../config/localConfig.js";
13
+ import { DEFAULT_MODEL, loadServerConfig, } from "../config/serverConfig.js";
14
+ import { initializeLocalConfig, readLocalConfig, writeLocalConfig, } from "../config/localConfig.js";
15
15
  import { getDataPaths } from "../config/dataPaths.js";
16
- import { detectEgressCapabilities, formatEgressCapabilityReport } from "../egress/capabilities.js";
16
+ import { detectEgressCapabilities, formatEgressCapabilityReport, } from "../egress/capabilities.js";
17
17
  import { EgressRunError, runEgressCommand } from "../egress/run.js";
18
- import { PolicyValidationError, evaluatePolicy, formatPolicyDecision, loadPolicyFile } from "../policy/index.js";
18
+ import { PolicyValidationError, evaluatePolicy, formatPolicyDecision, loadPolicyFile, } from "../policy/index.js";
19
19
  import { isLoopbackHost } from "../server/accessControl.js";
20
- import { getProviderManifests, isRegisteredProvider, providerRegistry } from "../providers/registry.js";
20
+ import { getProviderManifests, isRegisteredProvider, providerRegistry, } from "../providers/registry.js";
21
21
  import { parseCliArgs } from "./parser.js";
22
22
  import { CliError } from "./errors.js";
23
23
  import { startMcpServer } from "../mcp/server.js";
24
24
  import { generateRepoSetup, writeRepoSetup } from "../setup/setupGenerator.js";
25
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";
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
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";
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
33
  import { openUrl } from "./openUrl.js";
34
34
  async function main() {
35
35
  const command = parseCliArgs(process.argv.slice(2));
@@ -161,7 +161,9 @@ async function startServer(config, open = false) {
161
161
  console.warn("Warning: GhostAPI is bound beyond loopback. Use HTTPS or a secure tunnel because the dashboard token does not encrypt traffic.");
162
162
  }
163
163
  if (open)
164
- openUrl(remote && config.authToken ? `${dashboardUrl}?token=${encodeURIComponent(config.authToken)}` : dashboardUrl);
164
+ openUrl(remote && config.authToken
165
+ ? `${dashboardUrl}?token=${encodeURIComponent(config.authToken)}`
166
+ : dashboardUrl);
165
167
  });
166
168
  }
167
169
  async function createDevTlsOptions(host) {
@@ -172,8 +174,14 @@ async function createDevTlsOptions(host) {
172
174
  { name: "basicConstraints", cA: false },
173
175
  { name: "keyUsage", digitalSignature: true, keyEncipherment: true },
174
176
  { name: "extKeyUsage", serverAuth: true },
175
- { name: "subjectAltName", altNames: [{ type: 2, value: "localhost" }, { type: 7, ip: "127.0.0.1" }] }
176
- ]
177
+ {
178
+ name: "subjectAltName",
179
+ altNames: [
180
+ { type: 2, value: "localhost" },
181
+ { type: 7, ip: "127.0.0.1" },
182
+ ],
183
+ },
184
+ ],
177
185
  });
178
186
  return { key: pems.private, cert: pems.cert };
179
187
  }
@@ -201,7 +209,7 @@ async function initProject() {
201
209
  }
202
210
  async function printRepoSetup(write) {
203
211
  const written = write ? await writeRepoSetup() : null;
204
- const setup = written?.setup ?? await generateRepoSetup();
212
+ const setup = written?.setup ?? (await generateRepoSetup());
205
213
  console.log(setup.summary);
206
214
  console.log("");
207
215
  console.log("Commands:");
@@ -232,11 +240,13 @@ function openDashboard(options) {
232
240
  }
233
241
  async function printSafetyReport() {
234
242
  const report = await generateSafetyReport();
235
- console.log(`GhostAPI safety report for ${report.projectRoot}`);
243
+ console.log(`GhostAPI provider-risk heuristic scan for ${report.projectRoot}`);
236
244
  console.log(`Detected SDKs: ${report.detected.length > 0 ? report.detected.join(", ") : "none"}`);
245
+ console.log(`Scanned files: ${report.limits.scannedFiles}/${report.limits.maxFiles} (files over ${report.limits.maxFileBytes} bytes are skipped)`);
237
246
  console.log("");
238
247
  if (report.findings.length === 0) {
239
- console.log("No high-risk provider usage found.");
248
+ console.log("No matches found in the bounded heuristic scan.");
249
+ console.log("This is not proof that the repository contains no production endpoints, credentials, or egress paths.");
240
250
  }
241
251
  else {
242
252
  console.log("Findings:");
@@ -260,7 +270,9 @@ async function setModel(model) {
260
270
  }
261
271
  function listProviders() {
262
272
  for (const manifest of getProviderManifests()) {
263
- const version = manifest.packVersion === null ? manifest.implementation : `pack ${manifest.packVersion}`;
273
+ const version = manifest.packVersion === null
274
+ ? manifest.implementation
275
+ : `pack ${manifest.packVersion}`;
264
276
  console.log(`${manifest.name.padEnd(8)} ${manifest.displayName} (${version})`);
265
277
  }
266
278
  }
@@ -277,60 +289,123 @@ function inspectProvider(provider) {
277
289
  console.log(`Mode: ${manifest.implementation}`);
278
290
  console.log(`Pack version: ${manifest.packVersion ?? "n/a"}`);
279
291
  console.log(`API versions: ${manifest.apiVersions?.supported.join(", ") ?? "legacy"}`);
280
- console.log(`Capabilities: ${Object.entries(manifest.capabilities).filter(([, enabled]) => enabled).map(([name]) => name).join(", ")}`);
292
+ console.log(`Capabilities: ${Object.entries(manifest.capabilities)
293
+ .filter(([, enabled]) => enabled)
294
+ .map(([name]) => name)
295
+ .join(", ")}`);
281
296
  console.log("Error formatting: supported");
282
297
  }
283
298
  async function runDoctor(options) {
284
299
  if (options.egress) {
285
300
  const report = detectEgressCapabilities();
286
- console.log(options.json ? JSON.stringify(report, null, 2) : formatEgressCapabilityReport(report));
301
+ console.log(options.json
302
+ ? JSON.stringify(report, null, 2)
303
+ : formatEgressCapabilityReport(report));
287
304
  return;
288
305
  }
289
306
  const checks = [];
290
307
  let config = null;
291
308
  try {
292
309
  config = loadServerConfig(process.env, [], options.port ? { port: options.port } : {});
293
- checks.push({ label: "Config", status: "ok", detail: "loaded", docs: "docs/usage.md#configuration" });
310
+ checks.push({
311
+ label: "Config",
312
+ status: "ok",
313
+ detail: "loaded",
314
+ docs: "docs/usage.md#configuration",
315
+ });
294
316
  }
295
317
  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" });
318
+ checks.push({
319
+ label: "Config",
320
+ status: "fail",
321
+ detail: error instanceof Error ? error.message : "invalid configuration",
322
+ remediation: "Fix GHOSTAPI_* environment values or .ghostapi/config.json, then re-run ghostapi doctor.",
323
+ docs: "docs/usage.md#configuration",
324
+ });
297
325
  }
298
326
  const major = Number(process.versions.node.split(".")[0]);
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" });
327
+ checks.push({
328
+ label: "Node version",
329
+ status: major >= 20 ? "ok" : "fail",
330
+ detail: process.versions.node,
331
+ remediation: "Install Node.js 20 or newer.",
332
+ docs: "README.md#quickstart",
333
+ });
300
334
  const writeAccess = await canWriteGhostApiDir();
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" });
335
+ checks.push({
336
+ label: "GhostAPI data write access",
337
+ status: writeAccess ? "ok" : "fail",
338
+ detail: getDataPaths().root,
339
+ remediation: "Check GHOSTAPI_DATA_DIR and directory permissions.",
340
+ docs: "docs/usage.md#local-data-and-retention",
341
+ });
302
342
  if (config !== null) {
303
343
  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" });
344
+ checks.push({
345
+ label: "Port availability",
346
+ status: portAvailable ? "ok" : "fail",
347
+ detail: `${config.host}:${config.port}`,
348
+ remediation: `Run ghostapi start --port <free-port> or stop the process using ${config.port}.`,
349
+ docs: "README.md#quickstart",
350
+ });
351
+ checks.push({
352
+ label: "Model config",
353
+ status: config.model.trim() !== "" ? "ok" : "fail",
354
+ detail: config.model,
355
+ remediation: "Run ghostapi model set <model>.",
356
+ docs: "docs/usage.md#configuration",
357
+ });
306
358
  const hasApiKey = Boolean(config.apiKey);
307
359
  checks.push({
308
360
  label: "LLM API key",
309
361
  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"
362
+ detail: config.offline
363
+ ? "offline mode"
364
+ : config.allowExternalLlm
365
+ ? hasApiKey
366
+ ? "explicitly enabled"
367
+ : "enabled without GHOSTAPI_LLM_API_KEY"
368
+ : "disabled; deterministic provider mocks enabled",
369
+ remediation: config.allowExternalLlm && !hasApiKey
370
+ ? "Set GHOSTAPI_LLM_API_KEY or remove the external LLM opt-in."
371
+ : undefined,
372
+ docs: "README.md#security-boundaries",
313
373
  });
314
374
  const remote = !isLoopbackHost(config.host);
315
375
  checks.push({
316
376
  label: "Remote bind auth",
317
- status: !remote || (config.authToken !== undefined && config.authToken.length >= 24) ? "ok" : "fail",
377
+ status: !remote ||
378
+ (config.authToken !== undefined && config.authToken.length >= 24)
379
+ ? "ok"
380
+ : "fail",
318
381
  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"
382
+ remediation: remote
383
+ ? "Set a strong GHOSTAPI_AUTH_TOKEN and use HTTPS or a secure tunnel."
384
+ : undefined,
385
+ docs: "README.md#security-boundaries",
321
386
  });
322
387
  }
323
388
  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" });
389
+ checks.push({
390
+ label: "TLS safety",
391
+ status: !tlsBypass ? "ok" : "fail",
392
+ detail: tlsBypass ? "NODE_TLS_REJECT_UNAUTHORIZED=0" : "safe",
393
+ remediation: "Unset NODE_TLS_REJECT_UNAUTHORIZED.",
394
+ docs: "SECURITY.md",
395
+ });
325
396
  const egress = detectEgressCapabilities();
326
397
  const linuxNamespace = egress.capabilities.find((capability) => capability.id === "linux-network-namespace");
327
398
  const enforcementAvailable = process.platform === "linux" && linuxNamespace !== undefined;
328
399
  checks.push({
329
400
  label: "Enforcement capability",
330
401
  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"
402
+ detail: enforcementAvailable
403
+ ? linuxNamespace.detail
404
+ : `ghostapi run process isolation is unsupported/experimental on ${process.platform}`,
405
+ remediation: enforcementAvailable
406
+ ? "Run ghostapi run to execute the real preflight; doctor does not launch the target."
407
+ : "Use ghostapi start --open for local provider simulation, or run enforced tests on Linux.",
408
+ docs: "docs/security/egress-threat-model.md",
334
409
  });
335
410
  if (options.json) {
336
411
  console.log(JSON.stringify({ schemaVersion: 1, checks, egress }, null, 2));
@@ -363,7 +438,13 @@ async function explainPolicy(file, event) {
363
438
  console.log(formatPolicyDecision(evaluatePolicy(loaded.policy, event)));
364
439
  }
365
440
  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 });
441
+ const { report, path } = await generateEvidenceReport({
442
+ policyPath: options.policyPath,
443
+ runPath: options.runPath,
444
+ outPath: options.outPath,
445
+ contractBaselinePath: options.contractBaselinePath,
446
+ contractCandidatePath: options.contractCandidatePath,
447
+ });
367
448
  if (options.json)
368
449
  console.log(JSON.stringify({ path, report }, null, 2));
369
450
  else {
@@ -376,13 +457,17 @@ async function generateEvidence(options) {
376
457
  }
377
458
  async function viewEvidence(options) {
378
459
  const report = await loadEvidenceReport(options.path);
379
- console.log(options.json ? JSON.stringify(report, null, 2) : formatEvidenceReport(report));
460
+ console.log(options.json
461
+ ? JSON.stringify(report, null, 2)
462
+ : formatEvidenceReport(report));
380
463
  }
381
464
  async function compareEvidence(options) {
382
465
  const left = await loadEvidenceReport(options.leftPath);
383
466
  const right = await loadEvidenceReport(options.rightPath);
384
467
  const result = compareEvidenceReports(left, right);
385
- console.log(options.json ? JSON.stringify(result, null, 2) : formatEvidenceCompare(result));
468
+ console.log(options.json
469
+ ? JSON.stringify(result, null, 2)
470
+ : formatEvidenceCompare(result));
386
471
  if (!result.equal)
387
472
  process.exitCode = 1;
388
473
  }
@@ -390,7 +475,7 @@ async function recordScenario(options) {
390
475
  const bundle = await prepareScenarioRecordingFromFile(options.inputPath, {
391
476
  title: options.title,
392
477
  allowedSandboxHosts: options.allowedSandboxHosts,
393
- pii: parsePiiRules(options.pii)
478
+ pii: parsePiiRules(options.pii),
394
479
  });
395
480
  console.log(formatScenarioSanitizationSummary(bundle.sanitization));
396
481
  if (bundle.sanitization.requiresApproval && !options.approve) {
@@ -415,7 +500,9 @@ async function replayBundle(options) {
415
500
  }
416
501
  }
417
502
  async function importOpenApi(options) {
418
- const contract = await importOpenApiContractFromFile(options.inputPath, { title: options.title });
503
+ const contract = await importOpenApiContractFromFile(options.inputPath, {
504
+ title: options.title,
505
+ });
419
506
  const path = await writeContract(contract, options.outPath);
420
507
  console.log(`Contract: ${path}`);
421
508
  console.log(`Operations: ${contract.operations.length}`);
@@ -424,7 +511,7 @@ async function importHar(options) {
424
511
  const { bundle, contract } = await importHarContractFromFile(options.inputPath, {
425
512
  title: options.title,
426
513
  allowedSandboxHosts: options.allowedSandboxHosts,
427
- pii: parsePiiRules(options.pii)
514
+ pii: parsePiiRules(options.pii),
428
515
  });
429
516
  console.log(formatScenarioSanitizationSummary(bundle.sanitization));
430
517
  if (bundle.sanitization.requiresApproval && !options.approve) {
@@ -439,10 +526,19 @@ async function diffContractFiles(options) {
439
526
  const [baseline, candidate, loadedPolicy] = await Promise.all([
440
527
  loadContract(options.baselinePath),
441
528
  loadContract(options.candidatePath),
442
- options.policyPath === undefined ? Promise.resolve(null) : loadPolicyFile(options.policyPath, process.cwd(), true)
529
+ options.policyPath === undefined
530
+ ? Promise.resolve(null)
531
+ : loadPolicyFile(options.policyPath, process.cwd(), true),
443
532
  ]);
444
533
  const diff = diffContracts(baseline, candidate);
445
- const decision = loadedPolicy === null ? null : evaluatePolicy(loadedPolicy.policy, { type: "report", productionEgressAttempts: 0, forbiddenCredentialMatches: 0, breakingContractChanges: diff.summary.breaking });
534
+ const decision = loadedPolicy === null
535
+ ? null
536
+ : evaluatePolicy(loadedPolicy.policy, {
537
+ type: "report",
538
+ productionEgressAttempts: 0,
539
+ forbiddenCredentialMatches: 0,
540
+ breakingContractChanges: diff.summary.breaking,
541
+ });
446
542
  if (options.json)
447
543
  console.log(JSON.stringify({ diff, policy: decision }, null, 2));
448
544
  else {
@@ -450,11 +546,17 @@ async function diffContractFiles(options) {
450
546
  if (decision !== null)
451
547
  console.log(`Policy: ${decision.allowed ? "PASS" : "FAIL"} - ${decision.reason}`);
452
548
  }
453
- if (options.ci && (decision === null ? diff.summary.breaking > 0 : !decision.allowed))
549
+ if (options.ci &&
550
+ (decision === null ? diff.summary.breaking > 0 : !decision.allowed))
454
551
  process.exitCode = 2;
455
552
  }
456
553
  async function runEvalCommand(options) {
457
- const { report, path } = await runEval({ specPath: options.specPath, template: options.template, evidencePath: options.evidencePath, outPath: options.outPath });
554
+ const { report, path } = await runEval({
555
+ specPath: options.specPath,
556
+ template: options.template,
557
+ evidencePath: options.evidencePath,
558
+ outPath: options.outPath,
559
+ });
458
560
  if (options.json)
459
561
  console.log(JSON.stringify({ path, report }, null, 2));
460
562
  else {
@@ -467,7 +569,9 @@ async function runEvalCommand(options) {
467
569
  }
468
570
  async function createWorldCommand(options) {
469
571
  const world = await createWorld(options);
470
- console.log(options.json ? JSON.stringify(world, null, 2) : `${formatWorld(world)}\nPath: ${getDataPaths().worlds}`);
572
+ console.log(options.json
573
+ ? JSON.stringify(world, null, 2)
574
+ : `${formatWorld(world)}\nPath: ${getDataPaths().worlds}`);
471
575
  }
472
576
  async function inspectWorldCommand(id, json) {
473
577
  const world = await inspectWorld(id);
@@ -475,15 +579,21 @@ async function inspectWorldCommand(id, json) {
475
579
  }
476
580
  async function resetWorldCommand(id, json) {
477
581
  const world = await resetWorld(id);
478
- console.log(json ? JSON.stringify(world, null, 2) : `${formatWorld(world)}\nReset: restored baseline state.`);
582
+ console.log(json
583
+ ? JSON.stringify(world, null, 2)
584
+ : `${formatWorld(world)}\nReset: restored baseline state.`);
479
585
  }
480
586
  async function forkWorldCommand(sourceId, options) {
481
587
  const world = await forkWorld(sourceId, options);
482
- console.log(options.json ? JSON.stringify(world, null, 2) : `${formatWorld(world)}\nForked from: ${sourceId}`);
588
+ console.log(options.json
589
+ ? JSON.stringify(world, null, 2)
590
+ : `${formatWorld(world)}\nForked from: ${sourceId}`);
483
591
  }
484
592
  async function inspectActionCommand(actionId, json) {
485
593
  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(", ")}`);
594
+ console.log(json
595
+ ? JSON.stringify(record, null, 2)
596
+ : `Action: ${record.envelope.actionId}\nHash: ${record.actionHash}\nReceipts: ${record.receipts.map((receipt) => receipt.status).join(", ")}`);
487
597
  }
488
598
  async function runTelemetryCommand(action, json) {
489
599
  const snapshot = action === "enable"
@@ -513,16 +623,22 @@ function parsePiiRules(value) {
513
623
  if (value === undefined)
514
624
  return undefined;
515
625
  const selected = new Set(value === "none" ? [] : value.split(","));
516
- return { emails: selected.has("emails"), phones: selected.has("phones"), addresses: selected.has("addresses") };
626
+ return {
627
+ emails: selected.has("emails"),
628
+ phones: selected.has("phones"),
629
+ addresses: selected.has("addresses"),
630
+ };
517
631
  }
518
632
  async function loadReplayRequests(path) {
519
- const target = isAbsolute(path) ? resolve(path) : resolve(process.cwd(), path);
633
+ const target = isAbsolute(path)
634
+ ? resolve(path)
635
+ : resolve(process.cwd(), path);
520
636
  if (!isInsideAllowedRoots(target))
521
637
  throw new CliError("Replay requests path traversal outside the project root or GHOSTAPI_DATA_DIR is not allowed.");
522
638
  const info = await lstat(target);
523
639
  if (!info.isFile() || info.isSymbolicLink())
524
640
  throw new CliError("Replay requests input must be a regular non-symlink file.");
525
- if (!await isRealPathInsideAllowedRoots(target))
641
+ if (!(await isRealPathInsideAllowedRoots(target)))
526
642
  throw new CliError("Replay requests input resolves outside the project root or GHOSTAPI_DATA_DIR through a symlink.");
527
643
  const source = await readFile(target, "utf8");
528
644
  if (Buffer.byteLength(source, "utf8") > 1024 * 1024)
@@ -534,30 +650,47 @@ async function loadReplayRequests(path) {
534
650
  catch {
535
651
  throw new CliError("Replay requests input is not valid JSON.");
536
652
  }
537
- const requests = Array.isArray(parsed) ? parsed : parsed !== null && typeof parsed === "object" && Array.isArray(parsed.requests) ? parsed.requests : null;
653
+ const requests = Array.isArray(parsed)
654
+ ? parsed
655
+ : parsed !== null &&
656
+ typeof parsed === "object" &&
657
+ Array.isArray(parsed.requests)
658
+ ? parsed.requests
659
+ : null;
538
660
  if (requests === null || requests.length === 0 || requests.length > 100)
539
661
  throw new CliError("Replay requests input must contain 1-100 requests.");
540
662
  return requests.map((request) => {
541
- if (request === null || typeof request !== "object" || Array.isArray(request))
663
+ if (request === null ||
664
+ typeof request !== "object" ||
665
+ Array.isArray(request))
542
666
  throw new CliError("Replay request must be an object.");
543
667
  const candidate = request;
544
- if (typeof candidate.method !== "string" || typeof candidate.path !== "string")
668
+ if (typeof candidate.method !== "string" ||
669
+ typeof candidate.path !== "string")
545
670
  throw new CliError("Replay request requires string method and path.");
546
- return { method: candidate.method, path: candidate.path, headers: candidate.headers, body: candidate.body };
671
+ return {
672
+ method: candidate.method,
673
+ path: candidate.path,
674
+ headers: candidate.headers,
675
+ body: candidate.body,
676
+ };
547
677
  });
548
678
  }
549
679
  function isInsideAllowedRoots(target) {
550
- return isInside(process.cwd(), target) || isInside(getDataPaths().root, target);
680
+ return (isInside(process.cwd(), target) || isInside(getDataPaths().root, target));
551
681
  }
552
682
  function isInside(root, target) {
553
683
  const relativePath = relative(resolve(root), target);
554
- return relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath);
684
+ return (relativePath !== ".." &&
685
+ !relativePath.startsWith(`..${sep}`) &&
686
+ !isAbsolute(relativePath));
555
687
  }
556
688
  async function isRealPathInsideAllowedRoots(target) {
557
689
  const realTarget = await realpath(target);
558
690
  const realProjectRoot = await realpath(process.cwd());
559
691
  const dataRoot = await realpath(getDataPaths().root).catch(() => null);
560
- return isInside(realProjectRoot, realTarget) || (dataRoot !== null && isInside(dataRoot, realTarget));
692
+ return (isInside(realProjectRoot, realTarget) ||
693
+ (dataRoot !== null && isInside(dataRoot, realTarget)));
561
694
  }
562
695
  async function canWriteGhostApiDir() {
563
696
  const dataDir = getDataPaths().root;
@@ -579,39 +712,39 @@ function isPortAvailable(host, port) {
579
712
  });
580
713
  }
581
714
  function printHelp() {
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]
715
+ console.log(`GhostAPI CLI
716
+
717
+ Usage:
718
+ ghostapi start [--host 127.0.0.1] [--port 8080] [--model gpt-4o-mini] [--offline] [--https] [--allow-external-llm] [--open]
719
+ ghostapi open [--host 127.0.0.1] [--port 8080] [--https]
720
+ ghostapi clear cache|state|events|all
721
+ ghostapi model get
722
+ ghostapi model set <model>
723
+ ghostapi providers list
724
+ ghostapi providers inspect <provider>
725
+ ghostapi setup [--write]
726
+ ghostapi report
727
+ ghostapi mcp
728
+ ghostapi doctor [--port 8080] [--json]
729
+ ghostapi doctor --egress [--json]
730
+ ghostapi run [--port 8080] [--allow-host localhost] [--policy ghostapi.policy.yaml] -- <command> [args...]
731
+ ghostapi policy validate [--file ghostapi.policy.yaml]
732
+ ghostapi policy explain <scenario-id>|network <host>|credential <value>|enforcement <mode>|report <production-attempts> <credential-matches> [--file ghostapi.policy.yaml]
733
+ 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]
734
+ ghostapi evidence view <report.json> [--json]
735
+ ghostapi evidence compare <left.json> <right.json> [--json]
736
+ ghostapi record --input <capture.json|har.json> --allow-sandbox-host <host> [--out bundle.json] [--title title] [--pii emails,phones,addresses] [--approve]
737
+ ghostapi replay <bundle.json> --requests <requests.json> [--json]
738
+ ghostapi contract import-openapi --input <openapi.json> [--out contract.json] [--title title]
739
+ 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]
740
+ ghostapi contract diff --baseline <contract.json> --candidate <contract.json> [--policy ghostapi.policy.yaml] [--ci] [--json]
741
+ ghostapi eval --spec <eval.json>|--template <name> [--evidence report.json] [--out report.eval.json] [--ci] [--json]
742
+ ghostapi world create --id <world-id> --seed <seed> [--title title] [--json]
743
+ ghostapi world inspect <world-id> [--json]
744
+ ghostapi world reset <world-id> [--json]
745
+ ghostapi world fork <source-world-id> --id <fork-world-id> [--title title] [--json]
746
+ ghostapi action inspect <action-id> [--json]
747
+ ghostapi telemetry status|enable|disable|export [--json]
615
748
  ghostapi init`);
616
749
  }
617
750
  main().catch((error) => {