firedrill-run 2.2.5 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/README.md +178 -22
  2. package/bin/firedrill.mjs +29 -5
  3. package/dist/assembly.json +13 -0
  4. package/dist/cli/runtime/candidate_copy.py +395 -0
  5. package/dist/cli/runtime/source_snapshot.py +391 -0
  6. package/dist/cli/src/administration.js +295 -0
  7. package/dist/cli/src/agent-changes.js +496 -0
  8. package/dist/cli/src/assessment-requests.js +67 -0
  9. package/dist/cli/src/auth.js +465 -0
  10. package/dist/cli/src/billing-commerce-contracts.js +1 -0
  11. package/dist/cli/src/billing-usage.js +227 -0
  12. package/dist/cli/src/billing-wallet-contracts.js +1 -0
  13. package/dist/cli/src/billing.js +170 -0
  14. package/dist/cli/src/bootstrap-process-lock.js +136 -0
  15. package/dist/cli/src/browser-links.js +14 -0
  16. package/dist/cli/src/campaign-controls.js +122 -0
  17. package/dist/cli/src/campaign-diagnoses.js +118 -0
  18. package/dist/cli/src/campaign-reports.js +1215 -0
  19. package/dist/cli/src/campaign-world.js +156 -0
  20. package/dist/cli/src/candidate-copy.js +376 -0
  21. package/dist/cli/src/catalog-contracts.js +1 -0
  22. package/dist/cli/src/ci-access-contracts.js +1 -0
  23. package/dist/cli/src/ci-configuration-contracts.js +1 -0
  24. package/dist/cli/src/ci-coverage.js +64 -0
  25. package/dist/cli/src/ci-delivery-contracts.js +1 -0
  26. package/dist/cli/src/ci-result-contracts.js +1 -0
  27. package/dist/cli/src/ci-run-contracts.js +1 -0
  28. package/dist/cli/src/ci-setup-proposals.js +52 -0
  29. package/dist/cli/src/company-policies.js +229 -0
  30. package/dist/cli/src/completion.js +79 -0
  31. package/dist/cli/src/configuration-intents.js +277 -0
  32. package/dist/cli/src/configuration.js +369 -0
  33. package/dist/cli/src/continuous-simulation.js +277 -0
  34. package/dist/cli/src/coordinated-checkpoints.js +133 -0
  35. package/dist/cli/src/credential-mode.js +139 -0
  36. package/dist/cli/src/credentials.js +331 -0
  37. package/dist/cli/src/credit-format.js +8 -0
  38. package/dist/cli/src/dataset-contracts.js +1 -0
  39. package/dist/cli/src/dataset-export.js +154 -0
  40. package/dist/cli/src/dataset-reads.js +37 -0
  41. package/dist/cli/src/dependency-preparation.js +239 -0
  42. package/dist/cli/src/director-tool-setup.js +104 -0
  43. package/dist/cli/src/driver-review.js +225 -0
  44. package/dist/cli/src/evaluation-authoring.js +315 -0
  45. package/dist/cli/src/evidence-bundles.js +367 -0
  46. package/dist/cli/src/execution-proposals.js +36 -0
  47. package/dist/cli/src/http.js +302 -0
  48. package/dist/cli/src/local-container-boundary.js +38 -0
  49. package/dist/cli/src/local-container-policy.js +201 -0
  50. package/dist/cli/src/local-execution-setup.js +81 -0
  51. package/dist/cli/src/local-source-basis.js +73 -0
  52. package/dist/cli/src/local-source.js +66 -0
  53. package/dist/cli/src/local-value-store.js +91 -0
  54. package/dist/cli/src/local-values.js +163 -0
  55. package/dist/cli/src/login-organizations.js +70 -0
  56. package/dist/cli/src/main.js +1622 -0
  57. package/dist/cli/src/mcp-connection.js +109 -0
  58. package/dist/cli/src/native/bindings.js +137 -0
  59. package/dist/cli/src/native/canonical.js +370 -0
  60. package/dist/cli/src/native/cloud-resources.js +942 -0
  61. package/dist/cli/src/native/companion-channel.js +421 -0
  62. package/dist/cli/src/native/companion-ipc.js +473 -0
  63. package/dist/cli/src/native/contracts.js +218 -0
  64. package/dist/cli/src/native/control-plane.js +1354 -0
  65. package/dist/cli/src/native/credit-approval.js +15 -0
  66. package/dist/cli/src/native/dependency-proxy.js +1006 -0
  67. package/dist/cli/src/native/drill-cleanup.js +115 -0
  68. package/dist/cli/src/native/drill-runner.js +1562 -0
  69. package/dist/cli/src/native/drivers/action-trace.js +210 -0
  70. package/dist/cli/src/native/drivers/function-wrapper.js +152 -0
  71. package/dist/cli/src/native/drivers/http.js +442 -0
  72. package/dist/cli/src/native/drivers/index.js +47 -0
  73. package/dist/cli/src/native/drivers/jsonl-process.js +538 -0
  74. package/dist/cli/src/native/drivers/playwright.js +600 -0
  75. package/dist/cli/src/native/drivers/signed-event.js +244 -0
  76. package/dist/cli/src/native/drivers/types.js +578 -0
  77. package/dist/cli/src/native/evidence-filter.js +255 -0
  78. package/dist/cli/src/native/isolated-access.js +241 -0
  79. package/dist/cli/src/native/library-sharing.js +220 -0
  80. package/dist/cli/src/native/local-run.js +667 -0
  81. package/dist/cli/src/native/local-secrets.js +19 -0
  82. package/dist/cli/src/native/mcp-config.js +123 -0
  83. package/dist/cli/src/native/metadata-requests.js +120 -0
  84. package/dist/cli/src/native/observations.js +1159 -0
  85. package/dist/cli/src/native/owned-groups.js +46 -0
  86. package/dist/cli/src/native/plan.js +594 -0
  87. package/dist/cli/src/native/png.js +159 -0
  88. package/dist/cli/src/native/ports.js +43 -0
  89. package/dist/cli/src/native/readiness.js +139 -0
  90. package/dist/cli/src/native/reassessments.js +206 -0
  91. package/dist/cli/src/native/redaction.js +235 -0
  92. package/dist/cli/src/native/registration.js +155 -0
  93. package/dist/cli/src/native/roots.js +110 -0
  94. package/dist/cli/src/native/run-cleanup.js +199 -0
  95. package/dist/cli/src/native/scratch.js +117 -0
  96. package/dist/cli/src/native/screenshots.js +60 -0
  97. package/dist/cli/src/native/session.js +556 -0
  98. package/dist/cli/src/native/standalone-proxy.js +580 -0
  99. package/dist/cli/src/native/standalone-renewals.js +183 -0
  100. package/dist/cli/src/native/standalone-state.js +193 -0
  101. package/dist/cli/src/native/standalone-tools.js +187 -0
  102. package/dist/cli/src/native/supervisor.js +831 -0
  103. package/dist/cli/src/native/target-auth.js +161 -0
  104. package/dist/cli/src/native/target-state.js +330 -0
  105. package/dist/cli/src/native/tool-reuse.js +364 -0
  106. package/dist/cli/src/native/tool-setups.js +336 -0
  107. package/dist/cli/src/native/world-reuse.js +288 -0
  108. package/dist/cli/src/onboarding.js +23 -0
  109. package/dist/cli/src/priority-requests.js +70 -0
  110. package/dist/cli/src/priority.js +223 -0
  111. package/dist/cli/src/prompts.js +184 -0
  112. package/dist/cli/src/real-operations.js +221 -0
  113. package/dist/cli/src/recipe-intents.js +58 -0
  114. package/dist/cli/src/recorded-context-intents.js +59 -0
  115. package/dist/cli/src/recorded-context.js +248 -0
  116. package/dist/cli/src/recordings.js +69 -0
  117. package/dist/cli/src/registration-guide.js +39 -0
  118. package/dist/cli/src/report-exports.js +213 -0
  119. package/dist/cli/src/request-input.js +47 -0
  120. package/dist/cli/src/resource-lifecycle.js +141 -0
  121. package/dist/cli/src/revision-requests.js +55 -0
  122. package/dist/cli/src/runner-abandon.js +53 -0
  123. package/dist/cli/src/runner-campaign-controls.js +262 -0
  124. package/dist/cli/src/runner-claim-reads.js +36 -0
  125. package/dist/cli/src/runner-completion.js +65 -0
  126. package/dist/cli/src/runner-configuration.js +546 -0
  127. package/dist/cli/src/runner-container.js +794 -0
  128. package/dist/cli/src/runner-evidence-erasure.js +351 -0
  129. package/dist/cli/src/runner-evidence-privacy.js +37 -0
  130. package/dist/cli/src/runner-evidence-retention.js +161 -0
  131. package/dist/cli/src/runner-evidence.js +326 -0
  132. package/dist/cli/src/runner-execute.js +92 -0
  133. package/dist/cli/src/runner-execution-deadline.js +25 -0
  134. package/dist/cli/src/runner-grants.js +307 -0
  135. package/dist/cli/src/runner-identity.js +615 -0
  136. package/dist/cli/src/runner-inputs.js +308 -0
  137. package/dist/cli/src/runner-jobs.js +49 -0
  138. package/dist/cli/src/runner-model-transport.js +392 -0
  139. package/dist/cli/src/runner-ownership.js +332 -0
  140. package/dist/cli/src/runner-process-lock.js +175 -0
  141. package/dist/cli/src/runner-runtime.js +396 -0
  142. package/dist/cli/src/runner-session-channel.js +377 -0
  143. package/dist/cli/src/runner-trial-completion.js +34 -0
  144. package/dist/cli/src/runner-trial-journal.js +358 -0
  145. package/dist/cli/src/runner-trial-live.js +393 -0
  146. package/dist/cli/src/runner-trial-protocol.js +289 -0
  147. package/dist/cli/src/runner-trial-sync.js +110 -0
  148. package/dist/cli/src/runner-trial-transfer.js +107 -0
  149. package/dist/cli/src/runner-voice.js +40 -0
  150. package/dist/cli/src/runner-world-claim.js +21 -0
  151. package/dist/cli/src/runner-world-collector.js +159 -0
  152. package/dist/cli/src/runner-world-command-evidence.js +487 -0
  153. package/dist/cli/src/runner-world-commands.js +268 -0
  154. package/dist/cli/src/runner-world-completion.js +76 -0
  155. package/dist/cli/src/runner-world-erasure.js +95 -0
  156. package/dist/cli/src/runner-world-evidence-sync.js +91 -0
  157. package/dist/cli/src/runner-world-journal.js +510 -0
  158. package/dist/cli/src/runner-world-native-filters.js +134 -0
  159. package/dist/cli/src/runner-world-observations.js +50 -0
  160. package/dist/cli/src/runner-world-session-protocol.js +134 -0
  161. package/dist/cli/src/saved-records.js +40 -0
  162. package/dist/cli/src/scenario-authoring.js +212 -0
  163. package/dist/cli/src/scenario-discovery.js +216 -0
  164. package/dist/cli/src/scenario-paths.js +68 -0
  165. package/dist/cli/src/scenario-requests.js +4 -0
  166. package/dist/cli/src/service-receipts.js +105 -0
  167. package/dist/cli/src/simple/authoring.js +270 -0
  168. package/dist/cli/src/simple/credit-approval.js +16 -0
  169. package/dist/cli/src/simple/help.js +98 -0
  170. package/dist/cli/src/simple/init.js +61 -0
  171. package/dist/cli/src/simple/library-sharing.js +414 -0
  172. package/dist/cli/src/simple/metadata-requests.js +46 -0
  173. package/dist/cli/src/simple/native-report.js +841 -0
  174. package/dist/cli/src/simple/native-tools.js +780 -0
  175. package/dist/cli/src/simple/plan-apply.js +506 -0
  176. package/dist/cli/src/simple/reassessment.js +323 -0
  177. package/dist/cli/src/simple/register.js +123 -0
  178. package/dist/cli/src/simple/run.js +122 -0
  179. package/dist/cli/src/simple/status.js +49 -0
  180. package/dist/cli/src/simple/tool-renewal.js +308 -0
  181. package/dist/cli/src/simple/tool-reuse.js +526 -0
  182. package/dist/cli/src/simple/tool-start-catalog.js +100 -0
  183. package/dist/cli/src/simple/tool-state.js +426 -0
  184. package/dist/cli/src/simple/tools-author.js +222 -0
  185. package/dist/cli/src/simple/watch.js +28 -0
  186. package/dist/cli/src/simple/world-reuse.js +586 -0
  187. package/dist/cli/src/simulation-intents.js +406 -0
  188. package/dist/cli/src/simulation-run.js +1058 -0
  189. package/dist/cli/src/source-batch.js +322 -0
  190. package/dist/cli/src/source-connection.js +451 -0
  191. package/dist/cli/src/source-runtime.js +346 -0
  192. package/dist/cli/src/source-selection-intents.js +75 -0
  193. package/dist/cli/src/source-sets.js +214 -0
  194. package/dist/cli/src/target-models.js +109 -0
  195. package/dist/cli/src/tool-catalog.js +224 -0
  196. package/dist/cli/src/tool-command-contracts.js +1 -0
  197. package/dist/cli/src/tool-command-export.js +245 -0
  198. package/dist/cli/src/tool-connection-contracts.js +1 -0
  199. package/dist/cli/src/tool-connection-reads.js +23 -0
  200. package/dist/cli/src/tool-contributions.js +58 -0
  201. package/dist/cli/src/tool-data.js +116 -0
  202. package/dist/cli/src/tool-datasets.js +176 -0
  203. package/dist/cli/src/tool-group-contracts.js +1 -0
  204. package/dist/cli/src/tool-group-reads.js +26 -0
  205. package/dist/cli/src/tool-group-state.js +147 -0
  206. package/dist/cli/src/tool-groups.js +311 -0
  207. package/dist/cli/src/tool-instances.js +186 -0
  208. package/dist/cli/src/tool-lifecycle-contracts.js +1 -0
  209. package/dist/cli/src/tool-lifecycle-reads.js +30 -0
  210. package/dist/cli/src/tool-lifecycle.js +323 -0
  211. package/dist/cli/src/tool-package-contracts.js +1 -0
  212. package/dist/cli/src/tool-package-reads.js +41 -0
  213. package/dist/cli/src/tool-records.js +53 -0
  214. package/dist/cli/src/tool-runtime.js +96 -0
  215. package/dist/cli/src/tool-state-contracts.js +1 -0
  216. package/dist/cli/src/tool-state-reads.js +31 -0
  217. package/dist/cli/src/tools.js +306 -0
  218. package/dist/cli/src/trial-actions.js +93 -0
  219. package/dist/cli/src/trial-checkpoints.js +133 -0
  220. package/dist/cli/src/trial-checks.js +70 -0
  221. package/dist/cli/src/trial-state.js +197 -0
  222. package/dist/cli/src/vault.js +266 -0
  223. package/dist/cli/src/workspace-registration.js +383 -0
  224. package/dist/cli/src/workspace-team-contracts.js +1 -0
  225. package/dist/cli/src/world-access.js +231 -0
  226. package/dist/cli/src/world-agent.js +4 -0
  227. package/dist/cli/src/world-checkpoint-scopes.js +15 -0
  228. package/dist/cli/src/world-contracts.js +6 -0
  229. package/dist/cli/src/world-creation.js +201 -0
  230. package/dist/cli/src/world-datasets.js +207 -0
  231. package/dist/cli/src/world-inspection.js +257 -0
  232. package/dist/cli/src/world-recipes.js +103 -0
  233. package/dist/cli/src/world-requests.js +241 -0
  234. package/dist/cli/src/world-session-checkpoints.js +196 -0
  235. package/dist/cli/src/world-session-lifecycle.js +207 -0
  236. package/dist/cli/src/world-session-requests.js +215 -0
  237. package/dist/cli/src/world-session-state.js +145 -0
  238. package/dist/cli/src/world-session-voice.js +66 -0
  239. package/dist/cli/src/world-sessions.js +288 -0
  240. package/dist/cli/src/world-stream.js +99 -0
  241. package/dist/cli/src/world-time-report.js +58 -0
  242. package/dist/contracts/agent-change-contracts.js +75 -0
  243. package/dist/contracts/agent-execution-contracts.js +38 -0
  244. package/dist/contracts/application-state-contracts.js +26 -0
  245. package/dist/contracts/assessment-aggregate-contracts.js +15 -0
  246. package/dist/contracts/assessment-contracts.js +74 -0
  247. package/dist/contracts/billing-commerce-contracts.js +107 -0
  248. package/dist/contracts/billing-wallet-contracts.js +43 -0
  249. package/dist/contracts/campaign-lifecycle.js +1 -0
  250. package/dist/contracts/campaign-world-contracts.js +80 -0
  251. package/dist/contracts/catalog-contracts.js +73 -0
  252. package/dist/contracts/ci-access-contracts.js +68 -0
  253. package/dist/contracts/ci-configuration-contracts.js +88 -0
  254. package/dist/contracts/ci-coverage-contracts.js +65 -0
  255. package/dist/contracts/ci-coverage-labels.js +25 -0
  256. package/dist/contracts/ci-delivery-contracts.js +61 -0
  257. package/dist/contracts/ci-result-contracts.js +103 -0
  258. package/dist/contracts/ci-run-contracts.js +51 -0
  259. package/dist/contracts/ci-setup-contracts.js +88 -0
  260. package/dist/contracts/clip-contracts.js +32 -0
  261. package/dist/contracts/comparison-contracts.js +53 -0
  262. package/dist/contracts/composite-contracts.js +34 -0
  263. package/dist/contracts/context-contracts.js +76 -0
  264. package/dist/contracts/continuation-report-contracts.js +1 -0
  265. package/dist/contracts/continuity-requirements.js +8 -0
  266. package/dist/contracts/coordinated-checkpoint-contracts.js +34 -0
  267. package/dist/contracts/dataset-contracts.js +88 -0
  268. package/dist/contracts/dependency-decision-contracts.js +36 -0
  269. package/dist/contracts/dependency-protocol-contracts.js +43 -0
  270. package/dist/contracts/director-execution.js +51 -0
  271. package/dist/contracts/driver-review-contracts.js +99 -0
  272. package/dist/contracts/evidence-contracts.js +65 -0
  273. package/dist/contracts/evidence-coverage-contracts.js +10 -0
  274. package/dist/contracts/export-contracts.js +44 -0
  275. package/dist/contracts/github-directory-contracts.js +96 -0
  276. package/dist/contracts/insight-contracts.js +11 -0
  277. package/dist/contracts/inspection-contracts.js +1 -0
  278. package/dist/contracts/local-claim-contracts.js +39 -0
  279. package/dist/contracts/local-configuration-contracts.js +7 -0
  280. package/dist/contracts/local-evidence-contracts.js +32 -0
  281. package/dist/contracts/local-grant-contracts.js +83 -0
  282. package/dist/contracts/local-input-contracts.js +19 -0
  283. package/dist/contracts/local-runner-contracts.js +42 -0
  284. package/dist/contracts/local-trial-live-contracts.js +16 -0
  285. package/dist/contracts/local-value-source-contracts.js +92 -0
  286. package/dist/contracts/local-world-native-contracts.js +8 -0
  287. package/dist/contracts/mcp-contracts.js +42 -0
  288. package/dist/contracts/media-contracts.js +28 -0
  289. package/dist/contracts/native-check-contracts.js +31 -0
  290. package/dist/contracts/native-identity-contracts.js +52 -0
  291. package/dist/contracts/onboarding-contracts.js +32 -0
  292. package/dist/contracts/performance-contracts.js +85 -0
  293. package/dist/contracts/planning-contracts.js +81 -0
  294. package/dist/contracts/real-operation-contracts.js +30 -0
  295. package/dist/contracts/recommendation-contracts.js +54 -0
  296. package/dist/contracts/report-contracts.js +1 -0
  297. package/dist/contracts/resource-lifecycle-contracts.js +57 -0
  298. package/dist/contracts/revision-contracts.js +12 -0
  299. package/dist/contracts/revision-request-contracts.js +39 -0
  300. package/dist/contracts/saved-record-contracts.js +76 -0
  301. package/dist/contracts/scenario-draft-comparison.js +91 -0
  302. package/dist/contracts/scenario-world-contracts.js +14 -0
  303. package/dist/contracts/service-contracts.js +1 -0
  304. package/dist/contracts/session-command-contracts.js +56 -0
  305. package/dist/contracts/session-contracts.js +65 -0
  306. package/dist/contracts/session-state-contracts.js +67 -0
  307. package/dist/contracts/session-voice-contracts.js +45 -0
  308. package/dist/contracts/source-set-contracts.js +26 -0
  309. package/dist/contracts/source-upload-contracts.js +22 -0
  310. package/dist/contracts/tool-command-contracts.js +57 -0
  311. package/dist/contracts/tool-connection-contracts.js +87 -0
  312. package/dist/contracts/tool-contribution-contracts.js +35 -0
  313. package/dist/contracts/tool-group-contracts.js +53 -0
  314. package/dist/contracts/tool-group-state-contracts.js +63 -0
  315. package/dist/contracts/tool-lifecycle-contracts.js +94 -0
  316. package/dist/contracts/tool-package-contracts.js +91 -0
  317. package/dist/contracts/tool-protocol-contracts.js +71 -0
  318. package/dist/contracts/tool-runtime-billing-contracts.js +28 -0
  319. package/dist/contracts/tool-setup-proposal.js +59 -0
  320. package/dist/contracts/tool-state-contracts.js +79 -0
  321. package/dist/contracts/trial-state-contracts.js +30 -0
  322. package/dist/contracts/typescript/agent-change.js +1 -0
  323. package/dist/contracts/typescript/assessment-aggregate.js +1 -0
  324. package/dist/contracts/typescript/assessment.js +1 -0
  325. package/dist/contracts/typescript/billing-commerce.js +1 -0
  326. package/dist/contracts/typescript/campaigns.js +1 -0
  327. package/dist/contracts/typescript/catalog.js +1 -0
  328. package/dist/contracts/typescript/ci-access.js +1 -0
  329. package/dist/contracts/typescript/ci-configuration.js +1 -0
  330. package/dist/contracts/typescript/ci-delivery.js +1 -0
  331. package/dist/contracts/typescript/ci-result.js +1 -0
  332. package/dist/contracts/typescript/ci-runs.js +1 -0
  333. package/dist/contracts/typescript/clips.js +1 -0
  334. package/dist/contracts/typescript/comparison.js +1 -0
  335. package/dist/contracts/typescript/composite.js +1 -0
  336. package/dist/contracts/typescript/connections.js +1 -0
  337. package/dist/contracts/typescript/context.js +1 -0
  338. package/dist/contracts/typescript/datasets.js +1 -0
  339. package/dist/contracts/typescript/evidence-coverage.js +1 -0
  340. package/dist/contracts/typescript/export.js +1 -0
  341. package/dist/contracts/typescript/github-directory.js +1 -0
  342. package/dist/contracts/typescript/insight.js +1 -0
  343. package/dist/contracts/typescript/inspection.js +1 -0
  344. package/dist/contracts/typescript/intake.js +1 -0
  345. package/dist/contracts/typescript/local-claim.js +1 -0
  346. package/dist/contracts/typescript/local-configuration.js +1 -0
  347. package/dist/contracts/typescript/local-evidence.js +1 -0
  348. package/dist/contracts/typescript/local-grant.js +1 -0
  349. package/dist/contracts/typescript/local-input.js +1 -0
  350. package/dist/contracts/typescript/local-runner.js +1 -0
  351. package/dist/contracts/typescript/local-trial-live.js +1 -0
  352. package/dist/contracts/typescript/local-value-source.js +1 -0
  353. package/dist/contracts/typescript/local-world-native.js +1 -0
  354. package/dist/contracts/typescript/mcp.js +1 -0
  355. package/dist/contracts/typescript/media.js +1 -0
  356. package/dist/contracts/typescript/native-check.js +1 -0
  357. package/dist/contracts/typescript/native-identity.js +1 -0
  358. package/dist/contracts/typescript/onboarding.js +1 -0
  359. package/dist/contracts/typescript/performance.js +1 -0
  360. package/dist/contracts/typescript/planning.js +1 -0
  361. package/dist/contracts/typescript/preparation.js +1 -0
  362. package/dist/contracts/typescript/real-operation.js +1 -0
  363. package/dist/contracts/typescript/recommendation.js +1 -0
  364. package/dist/contracts/typescript/reports.js +1 -0
  365. package/dist/contracts/typescript/resource-lifecycle.js +1 -0
  366. package/dist/contracts/typescript/saved-record.js +1 -0
  367. package/dist/contracts/typescript/scenarios.js +1 -0
  368. package/dist/contracts/typescript/services.js +1 -0
  369. package/dist/contracts/typescript/session-command.js +1 -0
  370. package/dist/contracts/typescript/session-state.js +1 -0
  371. package/dist/contracts/typescript/session-voice.js +1 -0
  372. package/dist/contracts/typescript/session.js +1 -0
  373. package/dist/contracts/typescript/tool-command.js +1 -0
  374. package/dist/contracts/typescript/tool-connection.js +1 -0
  375. package/dist/contracts/typescript/tool-contribution.js +1 -0
  376. package/dist/contracts/typescript/tool-groups.js +1 -0
  377. package/dist/contracts/typescript/tool-lifecycle.js +1 -0
  378. package/dist/contracts/typescript/tool-package.js +1 -0
  379. package/dist/contracts/typescript/tool-state.js +1 -0
  380. package/dist/contracts/typescript/trial-state.js +1 -0
  381. package/dist/contracts/typescript/vault.js +1 -0
  382. package/dist/contracts/typescript/workspace-team.js +1 -0
  383. package/dist/contracts/typescript/world-stream.js +1 -0
  384. package/dist/contracts/workspace-team-contracts.js +55 -0
  385. package/dist/contracts/world-command.js +65 -0
  386. package/dist/contracts/world-lifetime-contracts.js +74 -0
  387. package/dist/contracts/world-recipe-contracts.js +27 -0
  388. package/dist/contracts/world-stream-contracts.js +27 -0
  389. package/dist/package.json +1 -0
  390. package/package.json +16 -16
  391. package/LICENSE.md +0 -23
  392. package/PACKAGING.json +0 -50
@@ -0,0 +1,227 @@
1
+ import { api, openBrowser } from './auth.js';
2
+ import { APP_ORIGIN, CliError } from './http.js';
3
+ import { selectedWorkspace } from './world-access.js';
4
+ import { selectOne, terminalText } from './prompts.js';
5
+ import { creditAmount as credits } from './credit-format.js';
6
+ const actions = new Set(['credits', 'credit-help', 'usage', 'credit-ledger', 'credit-allocations', 'credit-work', 'credit-purchases', 'credit-purchase', 'recharge', 'credit-settings']);
7
+ const exact = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
8
+ const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
9
+ const invalid = () => new CliError('billing_response_unconfirmed', 'The credit response could not be confirmed. Reopen Billing or retry this read.');
10
+ const uid = (value) => typeof value === 'string' && /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(value);
11
+ const boundedText = (value, limit) => typeof value === 'string' && value.length <= limit && !/[\u0000-\u001f\u007f]/.test(value);
12
+ const retainedCreditUnit = (row) => typeof row.denomination === 'string'
13
+ && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,31}$/.test(row.denomination) && exact(row.credit_scale)
14
+ && row.credit_scale >= 1 && /^10*$/.test(String(row.credit_scale));
15
+ const retainedCreditAmount = (row) => retainedCreditUnit(row)
16
+ ? `${credits(row.units, row.credit_scale)} credits` : 'Credit amount unavailable';
17
+ function creditGuideUrl(scope) {
18
+ if (!uid(scope.workspace))
19
+ throw invalid();
20
+ const url = new URL('/settings/billing/credits', APP_ORIGIN);
21
+ url.searchParams.set('workspace', scope.workspace);
22
+ return url.href;
23
+ }
24
+ async function showCreditGuide(scope, options, output, open = false) {
25
+ options.signal.throwIfAborted();
26
+ const help_url = creditGuideUrl(scope);
27
+ const opened = open && options.openBrowser && !options.json ? await openBrowser(help_url) : false;
28
+ options.signal.throwIfAborted();
29
+ output('billing_credit_help', { workspace_id: scope.workspace, help_url, opened }, `${opened ? 'Opened How credits work' : 'How credits work'}\n${help_url}`);
30
+ }
31
+ function scoped(value, scope) {
32
+ if (!object(value) || value.actor_id !== scope.owner || value.workspace_id !== scope.workspace
33
+ || !uid(value.organization_id) || !uid(value.account_id))
34
+ throw invalid();
35
+ return value;
36
+ }
37
+ async function summary(scope, options) {
38
+ const raw = scoped(await api(`/api/workspaces/${scope.workspace}/billing/credits`, {
39
+ actor: scope.owner, workspace: scope.workspace, signal: options.signal
40
+ }), scope);
41
+ if (raw.schema !== 'firedrill.credit-summary.v1' || !object(raw.policy) || typeof raw.policy.enabled !== 'boolean'
42
+ || raw.policy.help_path !== '/settings/billing/credits' || typeof raw.can_manage !== 'boolean')
43
+ throw invalid();
44
+ if (raw.policy.enabled && (!object(raw.balance) || !exact(raw.balance.available_units) || !exact(raw.balance.reserved_units)
45
+ || !exact(raw.balance.credit_scale) || !object(raw.balance.by_source)
46
+ || Object.values(raw.balance.by_source).some(one => !exact(one))))
47
+ throw invalid();
48
+ return raw;
49
+ }
50
+ async function summaryWithGuide(scope, options, output) {
51
+ try {
52
+ return await summary(scope, options);
53
+ }
54
+ catch (error) {
55
+ if (!options.signal.aborted)
56
+ await showCreditGuide(scope, options, output);
57
+ throw error;
58
+ }
59
+ }
60
+ export function isCreditBillingAction(action) { return !!action && actions.has(action); }
61
+ export async function creditBillingCommand(args, options, output, selected) {
62
+ const action = args[0] ?? 'credits';
63
+ const detail = ['credit-work', 'credit-purchase'].includes(action);
64
+ if (!actions.has(action) || args.length > 2 || detail && !uid(args[1]) || !detail && args[1]
65
+ || options.page !== undefined && (!/^[1-9][0-9]{0,8}$/.test(options.page) || !['usage', 'credit-ledger', 'credit-allocations', 'credit-purchases'].includes(action))
66
+ || options.search !== undefined || options.kind !== undefined && !['credit-ledger', 'credit-allocations'].includes(action)
67
+ || options.status !== undefined && !['usage', 'credit-purchases'].includes(action)
68
+ || (options.from !== undefined || options.to !== undefined) && !['usage', 'credit-ledger'].includes(action))
69
+ throw new CliError('arguments_invalid', 'Use billing credits, credit-help, usage, credit-ledger, credit-allocations, credit-work <id>, credit-purchases, credit-purchase <id> or recharge.', 2);
70
+ const scope = selected ?? await selectedWorkspace({ ...options, interactive: !options.json && !!process.stdin.isTTY && !!process.stderr.isTTY });
71
+ if (action === 'credit-help') {
72
+ await showCreditGuide(scope, options, output, true);
73
+ return;
74
+ }
75
+ if (action === 'recharge' || action === 'credit-settings') {
76
+ const url = new URL('/settings/billing', APP_ORIGIN);
77
+ url.searchParams.set('workspace', scope.workspace);
78
+ const result = { action: 'browser_approval_required', url: url.href, workspace_id: scope.workspace };
79
+ output('billing_browser_handoff', result, 'Open Billing to review credits, event codes, subscriptions and payment settings. Automatic top-ups require the billing owner to approve the pack, threshold, monthly cash cap and saved card.\n' + url.href);
80
+ if (options.openBrowser && !options.json)
81
+ await openBrowser(url.href);
82
+ return;
83
+ }
84
+ // Historical work/payments carry their own frozen identity and scale. A
85
+ // current tariff read is not a prerequisite for those independent reads or
86
+ // for the explicit browser-owned financial recovery ceremony above.
87
+ const row = ['credits', 'credit-ledger', 'credit-allocations'].includes(action) ? await summaryWithGuide(scope, options, output) : null;
88
+ if (row && (!row.policy.enabled || !row.balance)) {
89
+ const help_url = creditGuideUrl(scope);
90
+ output('billing_credit_policy_pending', { workspace_id: scope.workspace, configured: false,
91
+ help_url }, 'Resource-credit rates are not configured for this platform. No free-runtime or fixed-cost assumption was made.\nHow credits work\n' + help_url);
92
+ return;
93
+ }
94
+ if (action === 'credits') {
95
+ if (!row?.balance)
96
+ throw invalid();
97
+ output('billing_credits', row, `${credits(row.balance.available_units, row.balance.credit_scale)} credits available\n`
98
+ + `${credits(row.balance.reserved_units, row.balance.credit_scale)} reserved\n`
99
+ + Object.entries(row.balance.by_source).map(([source, value]) => `${terminalText(source)}: ${credits(value, row.balance.credit_scale)}`).join('\n')
100
+ + '\nCredits cover preparation, actual authorized execution resources and standalone tool runtime.');
101
+ await showCreditGuide(scope, options, output);
102
+ return;
103
+ }
104
+ if (action === 'credit-purchases' || action === 'credit-purchase') {
105
+ const states = ['pending', 'preparing', 'awaiting_payment', 'paid', 'failed', 'uncertain', 'reconciliation_required', 'expired', 'reversed', 'requires_action', 'requires_confirmation', 'canceled'];
106
+ if (options.status !== undefined && !states.includes(options.status))
107
+ throw new CliError('arguments_invalid', 'Choose a supported credit purchase status.', 2);
108
+ const suffix = action === 'credit-purchase' ? `/${args[1]}` : `?${new URLSearchParams({ page: options.page ?? '1', state: options.status ?? '' })}`;
109
+ const value = scoped(await api(`/api/workspaces/${scope.workspace}/billing/credits/purchases${suffix}`, {
110
+ actor: scope.owner, workspace: scope.workspace, signal: options.signal
111
+ }), scope);
112
+ const read = (raw) => {
113
+ if (!object(raw) || !uid(raw.id) || !['topup', 'automatic_topup'].includes(String(raw.kind)) || !boundedText(raw.state, 48) || !raw.state
114
+ || raw.request_state !== null && !boundedText(raw.request_state, 48)
115
+ || !exact(raw.units) || !exact(raw.amount_cents) || raw.currency !== 'usd'
116
+ || !(raw.denomination === null || typeof raw.denomination === 'string' && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,31}$/.test(raw.denomination))
117
+ || !(raw.credit_scale === null ? raw.url === null : retainedCreditUnit(raw))
118
+ || raw.message !== undefined && !boundedText(raw.message, 512))
119
+ throw invalid();
120
+ return raw;
121
+ };
122
+ const state = (row) => !['paid', 'reversed'].includes(String(row.state))
123
+ && ['uncertain', 'failed', 'reconciliation_required'].includes(String(row.request_state)) ? String(row.request_state) : String(row.state);
124
+ const money = (cents) => new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD' }).format(cents / 100);
125
+ if (action === 'credit-purchase') {
126
+ const purchase = read(value.purchase);
127
+ if (purchase.id !== args[1])
128
+ throw invalid();
129
+ const url = new URL('/settings/billing', APP_ORIGIN);
130
+ url.searchParams.set('workspace', scope.workspace);
131
+ url.searchParams.set('credit_history_purchase', purchase.id);
132
+ // Hosted payment capabilities stay in the explicit browser ceremony.
133
+ const { url: _hosted, ...metadata } = purchase;
134
+ output('billing_credit_purchase', { ...value, purchase: metadata, browser_url: url.href }, `${retainedCreditAmount(purchase)} · ${money(purchase.amount_cents)}\n${terminalText(state(purchase)).replaceAll('_', ' ')}`
135
+ + (purchase.message ? `\n${terminalText(purchase.message)}` : '') + `\nReview this original payment in Billing:\n${url.href}`);
136
+ return;
137
+ }
138
+ if (!exact(value.count) || !exact(value.page) || value.page !== Number(options.page ?? '1') || !exact(value.page_size)
139
+ || value.page_size < 1 || value.page_size > 100 || !Array.isArray(value.results) || value.results.length > value.page_size)
140
+ throw invalid();
141
+ const purchases = value.results.map(read);
142
+ if (purchases.some(row => row.url !== null || options.status && state(row) !== options.status))
143
+ throw invalid();
144
+ output('billing_credit_purchases', value, (purchases.map(purchase => `${purchase.id}\t${retainedCreditAmount(purchase)}\t${money(purchase.amount_cents)}\t${terminalText(state(purchase)).replaceAll('_', ' ')}`).join('\n') || 'No credit purchases.')
145
+ + `\nPage ${value.page}; ${value.count} purchases.` + (value.page * value.page_size < value.count ? `\nNext: firedrill billing credit-purchases --page ${value.page + 1}` : ''));
146
+ return;
147
+ }
148
+ const path = action === 'credit-work' ? `/usage/works/${args[1]}` : action === 'usage' ? '/usage/works'
149
+ : action === 'credit-ledger' ? '/credits/ledger' : '/credits/allocations';
150
+ const params = new URLSearchParams();
151
+ if (options.page)
152
+ params.set('page', options.page);
153
+ if (options.kind) {
154
+ const kinds = action === 'credit-ledger' ? ['grant', 'reserved', 'consumed', 'released', 'expired', 'revoked', 'refunded']
155
+ : ['included', 'purchased', 'sponsored', 'adjustment'];
156
+ if (!kinds.includes(options.kind))
157
+ throw new CliError('arguments_invalid', 'Choose a supported credit history kind.', 2);
158
+ params.set('kind', options.kind);
159
+ }
160
+ if (options.status) {
161
+ if (!['reserved', 'measured', 'budget_exhausted', 'usage_pending', 'settled', 'released'].includes(options.status))
162
+ throw new CliError('arguments_invalid', 'Choose a supported work usage status.', 2);
163
+ params.set('state', options.status);
164
+ }
165
+ for (const [key, value] of [['from', options.from], ['to', options.to]])
166
+ if (value !== undefined) {
167
+ if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,6})?)?(?:Z|[+-]\d{2}:\d{2})$/.test(value) || !Number.isFinite(Date.parse(value)))
168
+ throw new CliError('arguments_invalid', 'Use an ISO timestamp with an explicit timezone for --from or --to.', 2);
169
+ params.set(key, new Date(value).toISOString());
170
+ }
171
+ const raw = await api(`/api/workspaces/${scope.workspace}/billing${path}${params.size ? '?' + params : ''}`, {
172
+ actor: scope.owner, workspace: scope.workspace, signal: options.signal
173
+ });
174
+ if (action === 'credit-work') {
175
+ const work = scoped(raw, scope);
176
+ if (work.id !== args[1]
177
+ || !exact(work.estimated_units) || !exact(work.maximum_units) || !exact(work.consumed_units) || !exact(work.credit_scale)
178
+ || work.measured_units !== null && !exact(work.measured_units)
179
+ || typeof work.state !== 'string' || typeof work.measurement_complete !== 'boolean')
180
+ throw invalid();
181
+ output('billing_credit_work', work, `Work: ${terminalText(String(work.id))}\n${terminalText(work.state)}\n`
182
+ + `Estimate: ${credits(work.estimated_units, work.credit_scale)} credits\nBudget: ${credits(work.maximum_units, work.credit_scale)} credits\n`
183
+ + `Recorded use: ${work.measured_units === null ? 'Not yet observed' : credits(work.measured_units, work.credit_scale) + ' credits' + (work.measurement_complete ? '' : ' so far')}\n`
184
+ + (['settled', 'released'].includes(work.state) ? `Charged: ${credits(work.consumed_units, work.credit_scale)} credits` : 'Final charge pending'));
185
+ return;
186
+ }
187
+ const page = scoped(raw, scope);
188
+ if (!exact(page.count) || !exact(page.page_size) || page.page_size < 1 || !Array.isArray(page.results) || page.results.length > 100)
189
+ throw invalid();
190
+ for (const entry of page.results)
191
+ if (!object(entry) || !uid(entry.id))
192
+ throw invalid();
193
+ output('billing_credit_history', page, `${page.count} records, page ${options.page ?? '1'}\n`
194
+ + (page.results.length ? page.results.map((entry) => {
195
+ const label = typeof entry.description === 'string' ? entry.description : `${entry.activity ?? entry.kind ?? 'Allocation'} ${entry.state ?? ''}`;
196
+ // Work history can include a previous tariff/denomination. Never apply
197
+ // today's display scale to another retained quote's actual charge.
198
+ const scale = action === 'usage' ? entry.credit_scale : row.balance.credit_scale;
199
+ if (!exact(scale) || scale < 1 || !/^10*$/.test(String(scale)))
200
+ throw invalid();
201
+ if (action === 'usage' && entry.measured_units !== null && !exact(entry.measured_units))
202
+ throw invalid();
203
+ const amount = action === 'usage' ? entry.measured_units : entry.units ?? entry.available_units;
204
+ if (amount !== undefined && amount !== null && (typeof amount !== 'number' || !Number.isSafeInteger(amount)))
205
+ throw invalid();
206
+ const formatted = amount === null ? 'Not yet observed' : amount === undefined ? '' : amount < 0 ? '-' + credits(-amount, scale) : credits(amount, scale);
207
+ return `${entry.id}\t${terminalText(label)}\t${formatted}`;
208
+ }).join('\n') : 'No matching records.')
209
+ + (Number(options.page ?? 1) * page.page_size < page.count ? `\nNext page: firedrill billing ${action} --page ${Number(options.page ?? 1) + 1}` : ''));
210
+ }
211
+ /** Preserve a single organization scope when upgrading the normal billing flow. */
212
+ export async function useResourceCreditSummary(scope, options, output) {
213
+ const row = await summaryWithGuide(scope, options, output);
214
+ if (!row.policy.enabled) {
215
+ await creditBillingCommand(['credits'], options, output, scope);
216
+ return true;
217
+ }
218
+ await creditBillingCommand(['credits'], options, output, scope);
219
+ if (!options.json && process.stdin.isTTY && process.stderr.isTTY) {
220
+ const choice = await selectOne('Billing', [{ value: 'usage', label: 'Work usage' }, { value: 'credit-ledger', label: 'Credit history' },
221
+ { value: 'credit-allocations', label: 'Allocations and expiry' }, { value: 'credit-help', label: 'How credits work' }, ...(row.can_manage ? [{ value: 'credit-purchases', label: 'Credit purchase history' }, { value: 'recharge', label: 'Payment and top-up settings in browser' }] : []),
222
+ { value: 'done', label: 'Back' }], options.signal);
223
+ if (choice !== 'done')
224
+ await creditBillingCommand([choice], options, output, scope);
225
+ }
226
+ return true;
227
+ }
@@ -0,0 +1 @@
1
+ export * from '../../contracts/billing-wallet-contracts.js';
@@ -0,0 +1,170 @@
1
+ import { api, openBrowser } from './auth.js';
2
+ import { CliError } from './http.js';
3
+ import { selectOne, terminalText } from './prompts.js';
4
+ import { selectedWorkspace } from './world-access.js';
5
+ import { identifier } from './world-contracts.js';
6
+ import { creditBillingCommand, isCreditBillingAction, useResourceCreditSummary } from './billing-usage.js';
7
+ import { billingWalletRead, billingSummaryMatches } from './billing-wallet-contracts.js';
8
+ import { billingCommerceRead, billingOptionsMatch, billingPurchaseMatches, billingPurchasesMatch, billingInvoiceMatches, billingInvoicesMatch, billingInvoiceLinesMatch } from './billing-commerce-contracts.js';
9
+ const interactive = (options) => !options.json && !!process.stdin.isTTY && !!process.stderr.isTTY;
10
+ const auth = (scope, options) => ({ actor: scope.owner, workspace: scope.workspace, signal: options.signal });
11
+ const prefix = (scope) => `/api/workspaces/${scope.workspace}/billing`;
12
+ const money = (cents) => Number.isSafeInteger(cents)
13
+ ? new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD' }).format(cents / 100) : 'Amount unavailable';
14
+ const purchaseStates = { paid: 'Payment recorded', pending: 'Preparing hosted page', preparing: 'Preparing hosted page',
15
+ awaiting_payment: 'Awaiting payment', portal_ready: 'Hosted management ready', uncertain: 'Request status unconfirmed',
16
+ reconciliation_required: 'Payment needs review', failed: 'Request not completed', expired: 'Request expired', reversed: 'Payment adjusted' };
17
+ const effectiveState = (row) => !['paid', 'reversed'].includes(row.state)
18
+ && ['uncertain', 'failed', 'reconciliation_required'].includes(row.request_state ?? '') ? row.request_state : row.state;
19
+ const purchaseName = (row) => row.kind === 'portal' ? row.offer ? `Change to ${row.offer} (${row.interval})` : 'Payment details and cancellation'
20
+ : `${row.offer} subscription (${row.interval})`;
21
+ const purchaseUnitUnavailable = (row) => !(row.kind === 'portal' && row.units === 0 && row.amount_cents === 0
22
+ && row.denomination === null && !row.offer && !row.interval)
23
+ && !(typeof row.denomination === 'string' && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,31}$/.test(row.denomination)
24
+ && typeof row.credit_scale === 'number' && Number.isSafeInteger(row.credit_scale) && row.credit_scale >= 1 && /^10*$/.test(String(row.credit_scale)));
25
+ const invoiceId = (value) => {
26
+ if (!/^in_[A-Za-z0-9_]{1,120}$/.test(value))
27
+ throw new CliError('invoice_required', 'Choose an invoice from billing invoices.', 2);
28
+ return value;
29
+ };
30
+ function showPurchase(row, output) {
31
+ const { url: _url, ...publicRow } = row;
32
+ output('billing_purchase', publicRow, `${purchaseName(row)}\n${purchaseStates[effectiveState(row)] ?? 'Status unavailable'}\n`
33
+ + (row.kind === 'portal' ? '' : `${money(row.amount_cents)} before any Checkout discount. Recorded payment details are in invoices.\n`)
34
+ + (row.kind === 'portal' && row.offer ? 'Confirm the selected change and any proration in Stripe. Opening this page does not confirm a change or payment.\n' : '')
35
+ + `Request: ${row.id}\nRecorded: ${row.created_at}` + (row.message ? `\n${row.message}` : '')
36
+ + (row.url && !purchaseUnitUnavailable(row) ? `\nOpen the hosted page: firedrill billing open ${row.id}` : ''));
37
+ }
38
+ async function purchaseAction(action, id, scope, options, output) {
39
+ const selected = identifier(id), path = `${prefix(scope)}/purchases/${selected}`;
40
+ const row = await api(path, action === 'refresh-purchase'
41
+ ? { ...auth(scope, options), method: 'POST', body: {} } : auth(scope, options));
42
+ billingCommerceRead(row, billingPurchaseMatches(row, scope.workspace, { id: selected }));
43
+ if (action === 'refresh-purchase') {
44
+ showPurchase(row, output);
45
+ output('billing_refresh', { id: selected, scheduled: true }, 'A read of this original payment or subscription was requested. No new payment or plan change was created.');
46
+ return;
47
+ }
48
+ if (action !== 'open') {
49
+ showPurchase(row, output);
50
+ return;
51
+ }
52
+ let link;
53
+ try {
54
+ if (purchaseUnitUnavailable(row) || !row.url || !row.expires_at || !['awaiting_payment', 'portal_ready'].includes(row.state)
55
+ || !Number.isFinite(Date.parse(row.expires_at)) || Date.parse(row.expires_at) <= Date.now())
56
+ throw new Error();
57
+ link = new URL(row.url);
58
+ if (link.protocol !== 'https:' || link.host !== (row.kind === 'portal' ? 'billing.stripe.com' : 'checkout.stripe.com')
59
+ || link.username || link.password)
60
+ throw new Error();
61
+ }
62
+ catch {
63
+ throw new CliError('billing_link_unavailable', 'No current hosted link is available. Inspect this original payment request before starting another.', 2);
64
+ }
65
+ const opened = !options.json && options.openBrowser && await openBrowser(link.href);
66
+ output('billing_hosted_page', { id: row.id, opened, ...(opened ? {} : { url: link.href }), expires_at: row.expires_at }, opened ? 'Opened the hosted payment page. No payment is implied.' : `Open the hosted payment page: ${link.href}`);
67
+ }
68
+ async function invoiceAction(action, id, scope, options, output) {
69
+ const row = await api(`${prefix(scope)}/invoices/${invoiceId(id)}`, action === 'refresh-invoice'
70
+ ? { ...auth(scope, options), method: 'POST', body: {} } : auth(scope, options));
71
+ billingCommerceRead(row, billingInvoiceMatches(row, scope.workspace, id));
72
+ output('billing_invoice', row, `${row.id}\n${row.status} · ${money(row.total_cents)} total · ${money(row.paid_cents)} paid\n`
73
+ + `${money(row.discount_cents)} discount\nRecorded: ${row.created_at}\nReconciliation: ${row.reconciliation}`
74
+ + (row.service_starts_at ? `\nService period: ${row.service_starts_at} to ${row.service_ends_at}` : '')
75
+ + (action === 'refresh-invoice' ? '\nA current invoice read was requested. No payment was changed.' : `\nInspect line items: firedrill billing lines ${row.id}`));
76
+ }
77
+ async function history(action, id, scope, options, output) {
78
+ let page = Number(options.page ?? '1');
79
+ while (true) {
80
+ const query = new URLSearchParams({ page: String(page) });
81
+ if (options.status)
82
+ query.set('status', options.status);
83
+ const path = action === 'lines' ? `/invoices/${invoiceId(id)}/lines` : `/${action}`;
84
+ let rows, labels;
85
+ if (action === 'purchases') {
86
+ const value = await api(`${prefix(scope)}${path}?${query}`, auth(scope, options));
87
+ rows = billingCommerceRead(value, billingPurchasesMatch(value, scope.workspace, options.status ?? '', page));
88
+ labels = value.results.map(item => `${purchaseName(item)} · ${purchaseStates[effectiveState(item)] ?? 'Status unavailable'} · ${item.id}`);
89
+ }
90
+ else if (action === 'invoices') {
91
+ const value = await api(`${prefix(scope)}${path}?${query}`, auth(scope, options));
92
+ rows = billingCommerceRead(value, billingInvoicesMatch(value, scope.workspace, options.status ?? '', page));
93
+ labels = value.results.map(item => `${money(item.total_cents)} · ${item.status} · ${item.id}`);
94
+ }
95
+ else {
96
+ const value = await api(`${prefix(scope)}${path}?${query}`, auth(scope, options));
97
+ rows = billingCommerceRead(value, billingInvoiceLinesMatch(value, scope.workspace, id, page));
98
+ labels = value.results.map(item => `${item.plan}, ${item.interval} · ${money(item.amount_cents)}${item.prorated ? ' · Prorated' : ''} · ${item.service_starts_at} to ${item.service_ends_at}`);
99
+ }
100
+ output(`billing_${action}`, rows, (labels.join('\n') || 'No matching records.') + `\nPage ${rows.page} · ${rows.count} records`
101
+ + (action === 'lines' && 'collection_complete' in rows && !rows.collection_complete ? '\nInvoice line collection is incomplete. These are the items recorded so far.' : '')
102
+ + (rows.next ? `\nNext: firedrill billing ${action}${id ? ` ${id}` : ''} --workspace ${scope.workspace} --page ${page + 1}${options.status ? ` --status ${options.status}` : ''}` : ''));
103
+ if (!interactive(options))
104
+ return;
105
+ const selected = await selectOne('Billing history', [
106
+ ...(['purchases', 'invoices'].includes(action) ? rows.results.map((item, index) => ({ value: item.id, label: terminalText(labels[index]) })) : []),
107
+ ...(page > 1 ? [{ value: 'previous', label: 'Previous page' }] : []),
108
+ ...(rows.next ? [{ value: 'next', label: 'Next page' }] : []), { value: 'done', label: 'Back' }
109
+ ], options.signal);
110
+ if (selected === 'done')
111
+ return;
112
+ if (selected === 'previous') {
113
+ page -= 1;
114
+ continue;
115
+ }
116
+ if (selected === 'next') {
117
+ page += 1;
118
+ continue;
119
+ }
120
+ if (action === 'purchases')
121
+ await purchaseAction('purchase', selected, scope, options, output);
122
+ else
123
+ await invoiceAction('invoice', selected, scope, options, output);
124
+ }
125
+ }
126
+ export async function billingCommand(args, options, output) {
127
+ const action = args[0] ?? 'summary';
128
+ if (isCreditBillingAction(action)) {
129
+ await creditBillingCommand(args, options, output);
130
+ return;
131
+ }
132
+ const financial = ['subscribe', 'change', 'topup', 'portal', 'redeem'];
133
+ const withId = ['purchase', 'open', 'refresh-purchase', 'invoice', 'lines', 'refresh-invoice'];
134
+ const listing = ['purchases', 'invoices', 'lines'];
135
+ if (!['summary', 'options', ...financial, ...withId, ...listing].includes(action) || args.length > 2
136
+ || withId.includes(action) && !args[1] || args[1] && !withId.includes(action)
137
+ || options.from !== undefined || options.to !== undefined || options.kind !== undefined || options.search !== undefined
138
+ || options.page !== undefined && (!listing.includes(action) || !/^[1-9][0-9]{0,8}$/.test(options.page))
139
+ || options.status !== undefined && (!['purchases', 'invoices'].includes(action)
140
+ || !(action === 'purchases' ? Object.keys(purchaseStates) : ['draft', 'open', 'paid', 'void', 'uncollectible']).includes(options.status)))
141
+ throw new CliError('arguments_invalid', 'Use billing credits, usage, credit-ledger, credit-allocations, options, purchases or invoices. Review subscription, payment and event-code changes in Billing.', 2);
142
+ const scope = await selectedWorkspace({ ...options, interactive: interactive(options) });
143
+ if (action === 'summary') {
144
+ await useResourceCreditSummary(scope, options, output);
145
+ return;
146
+ }
147
+ if (financial.includes(action)) {
148
+ await creditBillingCommand(['credit-settings'], options, output, scope);
149
+ return;
150
+ }
151
+ if (withId.includes(action)) {
152
+ if (action === 'lines')
153
+ await history('lines', args[1], scope, options, output);
154
+ else if (action === 'invoice' || action === 'refresh-invoice')
155
+ await invoiceAction(action, args[1], scope, options, output);
156
+ else
157
+ await purchaseAction(action, args[1], scope, options, output);
158
+ return;
159
+ }
160
+ if (action === 'purchases' || action === 'invoices') {
161
+ await history(action, undefined, scope, options, output);
162
+ return;
163
+ }
164
+ const summary = await api(prefix(scope), auth(scope, options));
165
+ billingWalletRead(summary, billingSummaryMatches(summary, scope.workspace, scope.owner));
166
+ const offers = summary.can_manage ? await api(`${prefix(scope)}/options`, auth(scope, options)) : null;
167
+ if (offers)
168
+ billingCommerceRead(offers, billingOptionsMatch(offers, scope.workspace, scope.owner, summary.organization_id));
169
+ output('billing_options', { catalog: summary.catalog, offers, can_manage: summary.can_manage }, JSON.stringify({ catalog: summary.catalog, offers, can_manage: summary.can_manage }, null, 2));
170
+ }
@@ -0,0 +1,136 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { lstat } from 'node:fs/promises';
3
+ import { createServer } from 'node:net';
4
+ import { dirname, isAbsolute } from 'node:path';
5
+ import { setTimeout as delay } from 'node:timers/promises';
6
+ import { CliError } from './http.js';
7
+ const unavailable = () => new CliError('bootstrap_operation_owned', 'This sign-in or helper setup is already owned, a prior lock needs inspection, or local coordination is unavailable. No pending request was repeated.');
8
+ /** Before a trusted Python exists, Node's exclusive loopback binding supplies
9
+ * process-lifetime coordination only. No protocol, credentials or requests are
10
+ * served. A deterministic port collision is a refusal, never another lock slot.
11
+ * This is not proof of account identity or a security boundary against local
12
+ * denial of service. All product/refresh journals retain their original fences.
13
+ */
14
+ class BindCollision extends Error {
15
+ }
16
+ export async function bootstrapProcessLock(path, options = {}) {
17
+ const waitMs = options.waitMs ?? 0;
18
+ if (!Number.isInteger(waitMs) || waitMs < 0 || waitMs > 30000)
19
+ throw unavailable();
20
+ const deadline = performance.now() + waitMs;
21
+ for (;;) {
22
+ options.signal?.throwIfAborted();
23
+ try {
24
+ return await acquireProcessLock(path);
25
+ }
26
+ catch (error) {
27
+ // Only an OS bind collision may wait. No old directory lock, failed
28
+ // ownership check, provider request or persisted refresh is retried.
29
+ if (!(error instanceof BindCollision))
30
+ throw error;
31
+ const remaining = deadline - performance.now();
32
+ if (remaining <= 0)
33
+ throw unavailable();
34
+ await delay(Math.min(remaining, 100 + Math.floor(Math.random() * 150)), undefined, { signal: options.signal });
35
+ }
36
+ }
37
+ }
38
+ async function acquireProcessLock(path) {
39
+ if (!isAbsolute(path))
40
+ throw unavailable();
41
+ const directory = await lstat(dirname(path));
42
+ if (!directory.isDirectory() || directory.isSymbolicLink()
43
+ || process.getuid && directory.uid !== process.getuid()
44
+ || process.platform !== 'win32' && directory.mode & 0o077)
45
+ throw unavailable();
46
+ // Preserve any old directory-based owner, including an ambiguous dead owner.
47
+ // Never infer safety from its PID or unlink it to get a different lock.
48
+ try {
49
+ await lstat(path);
50
+ throw unavailable();
51
+ }
52
+ catch (error) {
53
+ if (error.code !== 'ENOENT')
54
+ throw error;
55
+ }
56
+ const port = 16384 + createHash('sha256').update('firedrill.bootstrap-lock.v1\0').update(path).digest().readUInt32BE(0) % 49152;
57
+ const controller = new AbortController();
58
+ const server = createServer(socket => socket.destroy());
59
+ server.maxConnections = 1;
60
+ let held = false, closing = false, closePromise;
61
+ const lost = () => { held = false; if (!closing)
62
+ controller.abort(); };
63
+ server.on('error', lost);
64
+ server.on('close', lost);
65
+ server.on('listening', () => { if (closing)
66
+ server.close(); });
67
+ try {
68
+ await new Promise((resolve, reject) => {
69
+ let finished = false;
70
+ const fail = (error) => finish(error?.code === 'EADDRINUSE' ? new BindCollision() : unavailable());
71
+ const finish = (error) => {
72
+ if (finished)
73
+ return;
74
+ finished = true;
75
+ clearTimeout(timer);
76
+ server.off('error', fail);
77
+ server.off('listening', ready);
78
+ if (error)
79
+ reject(error);
80
+ else
81
+ resolve();
82
+ };
83
+ const ready = () => { held = true; finish(); };
84
+ const timer = setTimeout(fail, 10000);
85
+ server.once('error', fail);
86
+ server.once('listening', ready);
87
+ try {
88
+ server.listen({ host: '127.0.0.1', port, exclusive: true, backlog: 1 });
89
+ }
90
+ catch {
91
+ finish(unavailable());
92
+ }
93
+ });
94
+ }
95
+ catch (error) {
96
+ closing = true;
97
+ try {
98
+ server.close();
99
+ }
100
+ catch { /* A failed bind may never have opened. */ }
101
+ throw error;
102
+ }
103
+ const close = () => closePromise ??= new Promise((resolve, reject) => {
104
+ closing = true;
105
+ held = false;
106
+ const timer = setTimeout(() => { controller.abort(); reject(unavailable()); }, 5000);
107
+ server.close(error => {
108
+ clearTimeout(timer);
109
+ if (error) {
110
+ controller.abort();
111
+ reject(unavailable());
112
+ }
113
+ else
114
+ resolve();
115
+ });
116
+ });
117
+ return { signal: controller.signal, assertHeld: () => {
118
+ if (!held || !server.listening || controller.signal.aborted || closing)
119
+ throw unavailable();
120
+ }, close };
121
+ }
122
+ export async function withBootstrapProcessLock(path, signal, work) {
123
+ signal.throwIfAborted();
124
+ const lock = await bootstrapProcessLock(path), active = AbortSignal.any([signal, lock.signal]);
125
+ try {
126
+ lock.assertHeld();
127
+ active.throwIfAborted();
128
+ const value = await work(active, lock.signal);
129
+ lock.assertHeld();
130
+ active.throwIfAborted();
131
+ return value;
132
+ }
133
+ finally {
134
+ await lock.close();
135
+ }
136
+ }
@@ -0,0 +1,14 @@
1
+ import { APP_ORIGIN, CliError } from './http.js';
2
+ import { identifier } from './world-contracts.js';
3
+ /** Browser and native workspace selections are independent authenticated state. */
4
+ export function workspaceLink(workspace, path, parameters = {}) {
5
+ if (!path.startsWith('/') || path.startsWith('//'))
6
+ throw new CliError('browser_link_invalid', 'Open this record from its workspace.', 2);
7
+ const url = new URL(path, APP_ORIGIN);
8
+ if (url.origin !== new URL(APP_ORIGIN).origin)
9
+ throw new CliError('browser_link_invalid', 'Open this record from its workspace.', 2);
10
+ for (const [key, value] of Object.entries(parameters))
11
+ url.searchParams.set(key, value);
12
+ url.searchParams.set('workspace', identifier(workspace));
13
+ return url.href;
14
+ }