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,109 @@
1
+ import { api } from './auth.js';
2
+ import { CliError } from './http.js';
3
+ import { selectOne } from './prompts.js';
4
+ import { identifier } from './world-contracts.js';
5
+ import { selectedWorkspace, selectedWorld } from './world-access.js';
6
+ import { approval, auth, choosePaged, interactive, line, query } from './configuration.js';
7
+ import { inputSchema, readMetadata, remember, requestAction, retained, validateBody } from './configuration-intents.js';
8
+ async function current(context, options, id) {
9
+ const data = await api(context.prefix + '/model-connections?' + query(options)
10
+ + (id ? '&connection_id=' + identifier(id) : ''), auth(context, options));
11
+ if (!Number.isSafeInteger(data.configuration_revision) || data.configuration_revision < 0 || !Array.isArray(data.providers)
12
+ || data.providers.some(one => !['anthropic', 'openai'].includes(one.id))) {
13
+ throw new CliError('model_configuration_unavailable', 'Model connection metadata could not be confirmed.', 2);
14
+ }
15
+ return data;
16
+ }
17
+ export async function targetModelsCommand(args, options, output) {
18
+ const interactiveMode = interactive(options);
19
+ let action = args[0] ?? 'list';
20
+ if (!['list', 'show', 'save', 'remove', 'schema', 'status', 'resume', 'withdraw', 'forget'].includes(action) || args.length > 2) {
21
+ throw new CliError('arguments_invalid', 'Use firedrill models list|show|save|remove|schema or status|resume|withdraw|forget --request-key <id>.', 2);
22
+ }
23
+ if (action === 'schema') {
24
+ if (args[1] && args[1] !== 'remove' || options.file || options.request || options.approve)
25
+ throw new CliError('arguments_invalid', 'Use models schema [remove] --json.', 2);
26
+ output('result', inputSchema(args[1] === 'remove' ? 'model_remove' : 'model'), 'Use --json for the exact model connection metadata fields. Credentials belong in Vault.');
27
+ return;
28
+ }
29
+ if (['status', 'resume', 'withdraw', 'forget'].includes(action)) {
30
+ if (args.length > 1 || options.file || ['status', 'resume'].includes(action) && options.approve || !options.request)
31
+ throw new CliError('arguments_invalid', 'Use --request-key <id> to recover the same model connection request.', 2);
32
+ const scope = await selectedWorkspace({ ...options, interactive: interactiveMode });
33
+ const intent = await retained(scope, identifier(options.request));
34
+ if (!['model', 'model_remove'].includes(intent.kind) || options.world && options.world !== intent.world) {
35
+ throw new CliError('model_request_mismatch', 'This recovery record belongs to another action or world.', 2);
36
+ }
37
+ const context = await selectedWorld({ ...options, interactive: false, world: intent.world, expectedOwner: scope.owner });
38
+ if (action === 'withdraw' || action === 'forget')
39
+ await approval(options, action === 'withdraw'
40
+ ? 'Withdraw this exact model access request? An accepted change will not be undone.'
41
+ : 'Remove only this private recovery copy? A delayed or accepted cloud request is unchanged.');
42
+ return requestAction(context, intent, action, options.signal, output);
43
+ }
44
+ if ((options.file || options.approve || options.request) && !['save', 'remove'].includes(action)) {
45
+ throw new CliError('arguments_invalid', 'Use a metadata file or approval only when saving or removing a connection.', 2);
46
+ }
47
+ const context = await selectedWorld({ ...options, interactive: interactiveMode });
48
+ if (!args.length && interactiveMode)
49
+ action = await selectOne('Model access', [
50
+ { value: 'list', label: 'View model connections' }, { value: 'save', label: 'Connect a saved runtime credential' },
51
+ { value: 'remove', label: 'Remove a connection' }
52
+ ], options.signal);
53
+ if (action === 'list') {
54
+ if (args[1])
55
+ throw new CliError('arguments_invalid', 'Use models show <connection> to read one connection.', 2);
56
+ const data = await current(context, options);
57
+ output('result', data, [`Configuration revision ${data.configuration_revision}`, ...data.results.map(one => `${one.id} ${one.key_variable} · ${one.provider}\n ${one.models.join(', ')}${one.mapping_current ? '' : '\n Saved mapping changed. Review this connection.'}`),
58
+ ...(!data.count ? ['No model connections. Save a runtime secret in Configuration, then use firedrill models save.'] : []),
59
+ ...(data.next ? [`Next page: --page ${data.page + 1}`] : [])].join('\n'));
60
+ return;
61
+ }
62
+ let selected = null;
63
+ if (action === 'show' || action === 'remove') {
64
+ if (args[1]) {
65
+ const data = await current(context, { ...options, search: '', page: '1' }, args[1]);
66
+ selected = data.results.find(one => one.id === identifier(args[1])) ?? null;
67
+ }
68
+ else if (interactiveMode)
69
+ selected = await choosePaged('Model connection', context.prefix + '/model-connections', context, options, one => ({ label: one.key_variable, detail: `${one.provider} · ${one.models.join(', ')}` }));
70
+ if (!selected)
71
+ throw new CliError('model_connection_required', 'Choose a connection ID from firedrill models.', 2);
72
+ if (action === 'show') {
73
+ output('result', selected, `${selected.key_variable} · ${selected.provider}\nEndpoint variable: ${selected.base_url_variable}\nAllowed models: ${selected.models.join(', ')}\nMapping: ${selected.mapping_current ? 'Current' : 'Changed; review required'}`);
74
+ return;
75
+ }
76
+ }
77
+ else if (args[1])
78
+ throw new CliError('arguments_invalid', 'Use models save to select a runtime credential, or --file <metadata.json>.', 2);
79
+ const kind = action === 'save' ? 'model' : 'model_remove';
80
+ let body;
81
+ if (options.file)
82
+ body = await readMetadata(options.file);
83
+ else {
84
+ if (!interactiveMode)
85
+ throw new CliError('model_input_required', 'Run models save in a terminal, or provide --file <metadata.json> --approve-configuration. Use models schema --json for fields.', 2);
86
+ const data = await current(context, { ...options, search: '', page: '1' }, selected?.id);
87
+ if (action === 'remove') {
88
+ if (!data.results.some(one => one.id === selected.id && one.revision === selected.revision)) {
89
+ throw new CliError('configuration_conflict', 'The model connection changed. Review it before removing access.', 2);
90
+ }
91
+ body = { configuration_revision: data.configuration_revision };
92
+ output('model_review', { connection_id: selected.id, key_variable: selected.key_variable }, `Remove ${selected.key_variable} scoped model access. This does not delete its Vault credential or change the provider account.`);
93
+ }
94
+ else {
95
+ const mapping = await choosePaged('Runtime credential mapping', context.prefix + '/model-connection-mappings', context, options, one => ({ label: one.name, detail: one.phase }));
96
+ if (!mapping)
97
+ throw new CliError('model_mapping_required', 'Save a runtime secret in Configuration first.', 2);
98
+ const provider = await selectOne('Model provider', data.providers.map(one => ({ value: one, label: one.name })), options.signal);
99
+ const base = await line('Original endpoint variable', 128, options.signal, provider.base_url_variable);
100
+ const names = (await line('Exact model names, separated by commas (not an API key)', 128999, options.signal)).split(',').map(one => one.trim());
101
+ body = { configuration_revision: data.configuration_revision, mapping_id: mapping.id, provider: provider.id,
102
+ base_url_variable: base, models: names, authorize_target_model_usage: true };
103
+ output('model_review', { mapping_id: mapping.id, variable: mapping.name, provider: provider.id, models: names, base_url_variable: base }, `${mapping.name} · ${provider.name}\nEndpoint variable: ${base}\nAllowed models: ${names.join(', ')}\nOnly this agent's approved model calls use the account. No model call is started by saving.`);
104
+ }
105
+ }
106
+ validateBody(kind, body);
107
+ await approval(options, 'Save this model access change and its private metadata recovery copy?');
108
+ return remember(context, kind, selected?.id ?? null, body, options.request, options.signal, output);
109
+ }
@@ -0,0 +1,224 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { api } from './auth.js';
3
+ import { CliError } from './http.js';
4
+ import { selectOne, terminalText } from './prompts.js';
5
+ import { identifier } from './world-contracts.js';
6
+ import { selectedWorkspace } from './world-access.js';
7
+ import { allocationApproval } from './tool-runtime.js';
8
+ import { catalogRead, catalogPageMatches, catalogReleaseDetailMatches, catalogServicesMatch, catalogServiceMatches, catalogOperationsMatch, catalogOperationMatches, catalogSetupMatches, catalogSetupsMatch } from './catalog-contracts.js';
9
+ const tty = (options) => !options.json && Boolean(process.stdin.isTTY && process.stderr.isTTY);
10
+ const text = (row) => `${terminalText(row.name)} ${terminalText(row.version)}: ${terminalText(row.state)}\nRequest: ${row.request_key}`
11
+ + (row.instance_id ? `\nInstance: ${row.instance_id}\nUse tools instance ${row.instance_id} to inspect actual startup.` : '')
12
+ + (row.implementation_id ? `\nSaved version: ${row.implementation_id}` : '') + (row.problem ? `\n${terminalText(row.problem)}` : '');
13
+ async function chooseItem(read, prompt, item, options) {
14
+ let current = 1;
15
+ while (true) {
16
+ const page = await read(current);
17
+ const choices = page.results.map(row => {
18
+ const value = item(row);
19
+ return { value: { kind: 'item', key: value.key }, label: value.label, detail: value.detail };
20
+ });
21
+ if (current > 1)
22
+ choices.push({ value: { kind: 'previous' }, label: 'Previous page', detail: '' });
23
+ if (page.next)
24
+ choices.push({ value: { kind: 'next' }, label: 'Next page', detail: '' });
25
+ if (!choices.length)
26
+ throw new CliError('catalog_selection_empty', 'No matching item is available. Review the catalog version.');
27
+ choices.push({ value: { kind: 'cancel' }, label: 'Cancel', detail: '' });
28
+ const selected = await selectOne(prompt, choices, options.signal);
29
+ if (selected.kind === 'cancel')
30
+ throw new CliError('canceled', 'Catalog selection canceled.', 2);
31
+ if (selected.kind === 'item')
32
+ return selected.key;
33
+ current += selected.kind === 'next' ? 1 : -1;
34
+ }
35
+ }
36
+ export async function toolCatalogCommand(args, options, output) {
37
+ const action = args[0], paged = ['catalog', 'catalog-categories', 'catalog-show', 'catalog-operations', 'catalog-setups'].includes(action);
38
+ const changing = ['add', 'save', 'catalog-withdraw'].includes(action);
39
+ if (!['catalog', 'catalog-categories', 'catalog-show', 'catalog-operations', 'catalog-operation', 'catalog-setups', 'catalog-status', 'catalog-withdraw', 'add', 'save'].includes(action)
40
+ || args.length > (action === 'catalog-operation' ? 4 : 2) || action === 'catalog' && args.length > 1
41
+ || action === 'catalog-categories' && args.length > 1 || options.category !== undefined && action !== 'catalog'
42
+ || options.category !== undefined && !/^[a-z][a-z0-9-]{0,63}$/.test(options.category)
43
+ || !paged && (options.page !== undefined || options.search !== undefined)
44
+ || options.search !== undefined && action === 'catalog-setups'
45
+ || !['add', 'save'].includes(action) && options.request !== undefined
46
+ || action !== 'add' && options.seconds !== undefined || options.upload || options.file !== undefined
47
+ || options.usageQuote !== undefined && action !== 'add'
48
+ || !['catalog-operations'].includes(action) && options.service !== undefined || !changing && options.change
49
+ || options.page !== undefined && !/^[1-9][0-9]{0,8}$/.test(options.page) || (options.search?.length ?? 0) > 200) {
50
+ throw new CliError('arguments_invalid', 'Use tools catalog, catalog-show [release], catalog-operations <release> --service <key>, catalog-operation <release> <service> <operation>, add|save [release], catalog-setups [release], catalog-status <request>, or catalog-withdraw <request>.', 2);
51
+ }
52
+ const scope = await selectedWorkspace({ ...options, interactive: tty(options) });
53
+ const auth = { actor: scope.owner, workspace: scope.workspace, signal: options.signal }, prefix = `/api/workspaces/${scope.workspace}/tools`;
54
+ const owner = { actor: scope.owner, workspace: scope.workspace };
55
+ const readCatalog = async (page, search = '') => {
56
+ const value = await api(`${prefix}/library?page=${page}&q=${encodeURIComponent(search)}&category=${encodeURIComponent(options.category ?? '')}`, auth);
57
+ return catalogRead(value, catalogPageMatches(value, scope.workspace, search, page, options.category ?? ''));
58
+ };
59
+ const readSetup = async (key, selected) => {
60
+ const value = await api(`${prefix}/catalog-requests/${key}`, auth);
61
+ return catalogRead(value, catalogSetupMatches(value, owner, { key, ...selected }));
62
+ };
63
+ const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
64
+ const continuation = (page, command) => `\nPage ${page.page}; ${page.count} items.${page.next ? `\nNext: tools ${command} --workspace ${scope.workspace} --page ${page.page + 1}${options.search ? ` --search ${quote(terminalText(options.search))}` : ''}${options.category ? ` --category ${quote(options.category)}` : ''}` : ''}`;
65
+ if (action === 'catalog-categories') {
66
+ const pageNumber = Number(options.page ?? 1), query = options.search ?? '';
67
+ const page = await api(`${prefix}/library/categories?page=${pageNumber}&q=${encodeURIComponent(query)}`, auth);
68
+ if (page.workspace_id !== scope.workspace || page.query !== query.trim() || page.page !== pageNumber || !Number.isSafeInteger(page.count) || page.count < 0
69
+ || !Number.isSafeInteger(page.page_size) || page.page_size < 1 || page.page_size > 100 || !Array.isArray(page.results) || page.results.length > page.page_size
70
+ || !page.results.every(row => !!row && /^[a-z][a-z0-9-]{0,63}$/.test(row.key) && typeof row.label === 'string' && row.label.length <= 64))
71
+ throw new CliError('catalog_categories_invalid', 'The catalog category response could not be confirmed.', 2);
72
+ output('tool_catalog_categories', page, (page.results.map(row => `${row.key} ${terminalText(row.label)}`).join('\n') || 'No matching categories.')
73
+ + continuation(page, 'catalog-categories'));
74
+ return;
75
+ }
76
+ if (action === 'catalog') {
77
+ const page = await readCatalog(Number(options.page ?? 1), options.search ?? '');
78
+ output('tool_catalog', page, page.results.map(row => `${row.id} ${terminalText(row.name)} ${terminalText(row.version)}\n${row.operation_count} operations · ${row.interfaces.join(', ')}${!row.available ? ' · unavailable' : ''}`)
79
+ .join('\n\n') + continuation(page, 'catalog'));
80
+ return;
81
+ }
82
+ if (action === 'catalog-setups') {
83
+ const release = args[1] ? identifier(args[1]) : null;
84
+ const page = await api(`${prefix}/catalog-setups?page=${options.page ?? '1'}${release ? `&release_id=${release}` : ''}`, auth);
85
+ catalogRead(page, catalogSetupsMatch(page, owner, release, Number(options.page ?? 1)));
86
+ output('catalog_setups', page, (page.results.map(text).join('\n\n') || 'No catalog setups requested by this account in the workspace.')
87
+ + continuation(page, `catalog-setups${release ? ` ${release}` : ''}`));
88
+ return;
89
+ }
90
+ if (action === 'catalog-status' || action === 'catalog-withdraw') {
91
+ const key = identifier(args[1] ?? ''), path = `${prefix}/catalog-requests/${key}`;
92
+ const original = await readSetup(key);
93
+ if (action === 'catalog-status') {
94
+ output('catalog_setup', original, text(original));
95
+ return;
96
+ }
97
+ let consent = options.change === true;
98
+ if (!consent && tty(options))
99
+ consent = await selectOne('Withdraw the original unfinished catalog setup?', [
100
+ { value: false, label: 'Keep the setup' }, { value: true, label: 'Withdraw it' }
101
+ ], options.signal);
102
+ if (!consent)
103
+ throw new CliError('catalog_withdraw_consent_required', 'Use --approve-tool-change to withdraw the original unfinished setup.', 2);
104
+ const row = await api(path, { ...auth, method: 'DELETE', body: { withdraw_consent: true } });
105
+ catalogRead(row, catalogSetupMatches(row, owner, { key, id: original.id }));
106
+ const observed = await readSetup(key, { id: original.id });
107
+ output('catalog_setup', observed, text(observed));
108
+ return;
109
+ }
110
+ let release = args[1];
111
+ if (!release && tty(options)) {
112
+ let page = 1;
113
+ while (!release) {
114
+ const results = await readCatalog(page);
115
+ const choices = results.results.filter(row => row.available && row.saved_version?.state !== 'revoked').map(row => ({ value: row.id,
116
+ label: `${row.name} ${row.version}`, detail: `${row.operation_count} operations · ${row.interfaces.join(', ')}` }));
117
+ if (page > 1)
118
+ choices.push({ value: '__previous__', label: 'Previous page', detail: '' });
119
+ if (results.next)
120
+ choices.push({ value: '__next__', label: 'Next page', detail: '' });
121
+ if (!choices.length)
122
+ throw new CliError('catalog_empty', 'No catalog tool is available. Import a supported package or inspect tools versions.');
123
+ choices.push({ value: '__cancel__', label: 'Cancel', detail: '' });
124
+ const selected = await selectOne('Choose a catalog tool', choices, options.signal);
125
+ if (selected === '__cancel__')
126
+ throw new CliError('canceled', 'Catalog selection canceled.', 2);
127
+ if (selected === '__next__')
128
+ page++;
129
+ else if (selected === '__previous__')
130
+ page--;
131
+ else
132
+ release = selected;
133
+ }
134
+ }
135
+ const releaseId = identifier(release ?? ''), base = `${prefix}/library/${releaseId}`;
136
+ const selected = await api(base, auth);
137
+ catalogRead(selected, catalogReleaseDetailMatches(selected, scope.workspace, releaseId));
138
+ const readServices = async (page, search = '') => {
139
+ const value = await api(`${base}/services?page=${page}&q=${encodeURIComponent(search)}`, auth);
140
+ return catalogRead(value, catalogServicesMatch(value, selected, search, page));
141
+ };
142
+ const readOperations = async (service, page, search = '') => {
143
+ const value = await api(`${base}/services/${service}/operations?page=${page}&q=${encodeURIComponent(search)}`, auth);
144
+ return catalogRead(value, catalogOperationsMatch(value, selected, service, search, page));
145
+ };
146
+ if (action === 'catalog-operations' || action === 'catalog-operation') {
147
+ let service = action === 'catalog-operations' ? options.service : args[2];
148
+ if (!service && tty(options))
149
+ service = await chooseItem(readServices, 'Choose a service', row => ({ key: row.key, label: terminalText(row.name), detail: `${row.operation_count} operations` }), options);
150
+ if (!service || !/^[a-z][a-z0-9_-]{0,79}$/.test(service))
151
+ throw new CliError('service_required', 'Choose a service from tools catalog-show.', 2);
152
+ const serviceValue = await api(`${base}/services/${service}`, auth);
153
+ catalogRead(serviceValue, catalogServiceMatches(serviceValue, selected, service));
154
+ if (action === 'catalog-operation') {
155
+ let name = args[3];
156
+ if (!name && tty(options))
157
+ name = await chooseItem(page => readOperations(service, page), 'Choose an operation', row => ({ key: row.name, label: terminalText(row.name), detail: `${row.transport.kind} · ${terminalText(row.effect)}` }), options);
158
+ if (!name || name.length > 200 || /[\u0000-\u001f\u007f]/.test(name))
159
+ throw new CliError('operation_required', 'Choose an operation from catalog-operations.', 2);
160
+ const row = await api(`${base}/services/${service}/operations/${encodeURIComponent(name)}`, auth);
161
+ catalogRead(row, catalogOperationMatches(row, selected, service, name));
162
+ output('catalog_operation', row, JSON.stringify(row, null, 2));
163
+ return;
164
+ }
165
+ const page = await readOperations(service, Number(options.page ?? 1), options.search ?? '');
166
+ output('catalog_operations', page, (page.results.map(row => `${terminalText(row.name)} · ${row.transport.kind} · ${terminalText(row.effect)}`
167
+ + (row.mcp_aliases?.length ? `\nMCP names: ${row.mcp_aliases.map(terminalText).join(', ')}` : '')).join('\n') || 'No matching operations.')
168
+ + continuation(page, `catalog-operations ${releaseId} --service ${service}`));
169
+ return;
170
+ }
171
+ if (action === 'catalog-show') {
172
+ const services = await readServices(Number(options.page ?? 1), options.search ?? '');
173
+ output('catalog_release', { ...selected, services }, `${terminalText(selected.name)} ${terminalText(selected.version)}\n${terminalText(selected.description)}\n`
174
+ + services.results.map(row => `${terminalText(row.key)} · ${terminalText(row.name)} · ${row.operation_count} operations`).join('\n')
175
+ + continuation(services, `catalog-show ${releaseId}`));
176
+ return;
177
+ }
178
+ let seconds = null;
179
+ if (action === 'add') {
180
+ if (options.seconds !== undefined) {
181
+ if (!/^[1-9][0-9]{1,8}$/.test(options.seconds))
182
+ throw new CliError('tool_duration_invalid', 'Use a supported --seconds value.', 2);
183
+ seconds = Number(options.seconds);
184
+ }
185
+ else if (tty(options) && selected.setup)
186
+ seconds = await selectOne('Compute duration', [
187
+ { value: selected.setup.default_duration_seconds, label: `${selected.setup.default_duration_seconds / 60} minutes` },
188
+ { value: null, label: 'Save the version without starting compute' }
189
+ ], options.signal);
190
+ else
191
+ throw new CliError('tool_duration_required', 'Use --seconds for an instance, or tools save for package-only setup.', 2);
192
+ if (seconds !== null && (!selected.setup || seconds < 60 || seconds > selected.setup.maximum_duration_seconds)) {
193
+ throw new CliError('tool_duration_invalid', 'Choose a compute duration within the supported catalog settings.', 2);
194
+ }
195
+ }
196
+ let consent = options.change === true;
197
+ if (!consent && tty(options))
198
+ consent = await selectOne(seconds === null ? `Save ${selected.name} ${selected.version} in this workspace?`
199
+ : `Save ${selected.name} ${selected.version} and start a separate instance for ${seconds} seconds?`, [
200
+ { value: false, label: 'Cancel' }, { value: true, label: seconds === null ? 'Save version' : 'Save and start instance' }
201
+ ], options.signal);
202
+ if (!consent)
203
+ throw new CliError('catalog_consent_required', 'Use --approve-tool-change to approve this package and any requested compute.', 2);
204
+ const key = options.request ? identifier(options.request) : randomUUID();
205
+ const usageQuote = seconds !== null ? await allocationApproval(scope, options, { release_id: releaseId, implementation_id: null,
206
+ dataset_id: null, restore: null, duration_seconds: seconds, launch_request_key: key }, output) : null;
207
+ const body = { release_id: releaseId, duration_seconds: seconds, install_consent: true, start_consent: seconds !== null, usage_quote_id: usageQuote };
208
+ output('catalog_setup_intent', { request_key: key, ...body }, `Catalog request: ${key}\nRecovery: tools catalog-status ${key}`);
209
+ let row;
210
+ try {
211
+ const accepted = await api(`${prefix}/catalog-setups`, { ...auth, method: 'POST', idempotencyKey: key, body });
212
+ catalogRead(accepted, catalogSetupMatches(accepted, owner, { key, input: body }));
213
+ row = await readSetup(key, { id: accepted.id, input: body });
214
+ }
215
+ catch {
216
+ try {
217
+ row = await readSetup(key, { input: body });
218
+ }
219
+ catch {
220
+ throw new CliError('catalog_acceptance_unconfirmed', `Setup acceptance is unconfirmed. No replacement was submitted. Read tools catalog-status ${key} --workspace ${scope.workspace}.`);
221
+ }
222
+ }
223
+ output('catalog_setup', row, text(row));
224
+ }
@@ -0,0 +1 @@
1
+ export * from '../../contracts/tool-command-contracts.js';
@@ -0,0 +1,245 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { link, lstat, open, realpath, unlink } from 'node:fs/promises';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+ import { createInterface } from 'node:readline/promises';
5
+ import { access, api } from './auth.js';
6
+ import { CliError, endpoint, responseError } from './http.js';
7
+ import { selectOne, terminalText } from './prompts.js';
8
+ import { identifier } from './world-contracts.js';
9
+ import { selectedWorkspace } from './world-access.js';
10
+ import { toolLifecycleReads } from './tool-lifecycle-reads.js';
11
+ import { toolCommandMatches, toolCommandRead, toolCommandsMatch } from './tool-command-contracts.js';
12
+ const interactive = (options) => !options.json && Boolean(process.stdin.isTTY && process.stderr.isTTY);
13
+ const invalid = () => new CliError('tool_download_unconfirmed', 'The original operation file could not be confirmed. Inspect the same operation; do not repeat it to recover content.', 2);
14
+ const fileMetadata = (row, direction) => direction === 'request' ? row.request_file : row.response_file;
15
+ export function toolCommandDownloadHints(row) {
16
+ return ['request', 'response'].filter(direction => fileMetadata(row, direction) !== null).map(direction => `Download ${direction}: firedrill tools download ${row.instance_id} ${row.id} ${direction} --workspace ${row.workspace_id} --into ./tool-${row.id}-${direction}.bin --approve-download`).join('\n');
17
+ }
18
+ async function question(label, signal) {
19
+ const prompt = createInterface({ input: process.stdin, output: process.stderr, terminal: false, historySize: 0 });
20
+ try {
21
+ return (await prompt.question(label, { signal })).trim();
22
+ }
23
+ finally {
24
+ prompt.close();
25
+ }
26
+ }
27
+ async function chooseInstance(scope, signal) {
28
+ const reads = toolLifecycleReads(scope, signal);
29
+ let page = 1, query = '', state = 'all';
30
+ while (true) {
31
+ const rows = await reads.instances(page, query, state);
32
+ const selected = await selectOne('Tool instance', [
33
+ ...rows.results.map(row => ({ value: row.id, label: row.name, detail: `${row.version} · ${row.state}` })),
34
+ ...(page > 1 ? [{ value: 'previous', label: 'Previous page', detail: '' }] : []),
35
+ ...(rows.next ? [{ value: 'next', label: 'Next page', detail: '' }] : []),
36
+ { value: 'switch_scope', label: state === 'all' ? 'Show removed instances' : 'Show current instances', detail: '' },
37
+ { value: 'search', label: 'Search instances', detail: query }, { value: 'cancel', label: 'Cancel', detail: '' },
38
+ ], signal);
39
+ if (selected === 'cancel')
40
+ throw new CliError('canceled', 'No operation file was downloaded.', 130);
41
+ if (selected === 'next') {
42
+ page++;
43
+ continue;
44
+ }
45
+ if (selected === 'previous') {
46
+ page--;
47
+ continue;
48
+ }
49
+ if (selected === 'switch_scope') {
50
+ state = state === 'all' ? 'removed' : 'all';
51
+ page = 1;
52
+ continue;
53
+ }
54
+ if (selected === 'search') {
55
+ query = await question('Find an instance (blank for all): ', signal);
56
+ if (query.length > 200 || /[\u0000-\u001f\u007f-\u009f]/u.test(query))
57
+ throw new CliError('arguments_invalid', 'Use search text of up to 200 characters.', 2);
58
+ page = 1;
59
+ continue;
60
+ }
61
+ return identifier(selected);
62
+ }
63
+ }
64
+ async function chooseCommand(path, scope, instance, signal) {
65
+ let query = '', anchor, before, after;
66
+ while (true) {
67
+ const params = new URLSearchParams({ q: query, limit: '25' });
68
+ if (anchor)
69
+ params.set('anchor', anchor);
70
+ if (before)
71
+ params.set('before', before);
72
+ if (after)
73
+ params.set('after', after);
74
+ const page = await api(`${path}/commands?${params}`, { actor: scope.owner, workspace: scope.workspace, signal });
75
+ toolCommandRead(page, toolCommandsMatch(page, { workspace: scope.workspace, instance }, { query, anchor }));
76
+ anchor = page.anchor;
77
+ const selected = await selectOne('Retained operation file', [
78
+ ...page.results.filter(row => row.request_file !== null || row.response_file !== null).map(row => ({ value: row.id,
79
+ label: `${row.service_key}.${row.operation_name}`, detail: `${row.state} · ${row.id}` })),
80
+ ...(page.next_before ? [{ value: 'older', label: 'Older operations', detail: '' }] : []),
81
+ ...(page.previous_after ? [{ value: 'newer', label: 'Newer operations', detail: '' }] : []),
82
+ { value: 'search', label: 'Search operations', detail: query }, { value: 'cancel', label: 'Cancel', detail: '' },
83
+ ], signal);
84
+ if (selected === 'cancel')
85
+ throw new CliError('canceled', 'No operation file was downloaded.', 130);
86
+ if (selected === 'older') {
87
+ before = page.next_before;
88
+ after = undefined;
89
+ continue;
90
+ }
91
+ if (selected === 'newer') {
92
+ after = page.previous_after;
93
+ before = undefined;
94
+ continue;
95
+ }
96
+ if (selected === 'search') {
97
+ query = await question('Find an operation (blank for all): ', signal);
98
+ if (query.length > 200 || /[\u0000-\u001f\u007f-\u009f]/u.test(query))
99
+ throw new CliError('arguments_invalid', 'Use search text of up to 200 characters.', 2);
100
+ anchor = before = after = undefined;
101
+ continue;
102
+ }
103
+ return identifier(selected);
104
+ }
105
+ }
106
+ function sameOriginal(left, right) {
107
+ return left.id === right.id && left.workspace_id === right.workspace_id && left.instance_id === right.instance_id
108
+ && left.actor_id === right.actor_id && left.request_key === right.request_key && left.request_digest === right.request_digest
109
+ && left.sequence === right.sequence && left.state_epoch === right.state_epoch && left.kind === right.kind
110
+ && left.surface_id === right.surface_id && left.service_key === right.service_key && left.operation_name === right.operation_name
111
+ && left.effect === right.effect;
112
+ }
113
+ /** Export retained bytes only. No tool operation, lease or runtime is dispatched. */
114
+ export async function exportToolCommand(args, options, output) {
115
+ if (args[0] !== 'download' || args.length > 4 || args[3] !== undefined && !['request', 'response'].includes(args[3])
116
+ || [options.page, options.search, options.file, options.request, options.service, options.seconds, options.state, options.usageQuote,
117
+ options.before, options.after, options.anchor, options.outcome].some(value => value !== undefined) || options.upload || options.change)
118
+ throw new CliError('arguments_invalid', 'Use tools download [instance] [command] [request|response] --into <new-file> [--approve-download]. IDs are command IDs, not request keys.', 2);
119
+ const tty = interactive(options);
120
+ const scope = await selectedWorkspace({ ...options, interactive: tty });
121
+ if (!args[1] && !tty)
122
+ throw new CliError('tool_instance_required', 'Provide the original tool instance ID.', 2);
123
+ const instance = args[1] ? identifier(args[1]) : await chooseInstance(scope, options.signal);
124
+ const path = `/api/workspaces/${identifier(scope.workspace)}/tools/instances/${instance}`;
125
+ if (!args[2] && !tty)
126
+ throw new CliError('tool_command_required', 'Provide the original command ID from tools activity or result. Do not repeat the operation.', 2);
127
+ const command = args[2] ? identifier(args[2]) : await chooseCommand(path, scope, instance, options.signal);
128
+ const read = async () => {
129
+ const row = await api(`${path}/commands/${command}`, { actor: scope.owner, workspace: scope.workspace, signal: options.signal });
130
+ return toolCommandRead(row, toolCommandMatches(row, { workspace: scope.workspace, instance }, { id: command }));
131
+ };
132
+ const original = await read();
133
+ const available = ['request', 'response'].filter(direction => fileMetadata(original, direction) !== null);
134
+ if (!available.length)
135
+ throw new CliError('tool_file_unavailable', 'This operation has no currently retained downloadable files. Withheld, expired or missing content cannot be recreated by repeating the operation.', 2);
136
+ let direction = args[3];
137
+ if (!direction && available.length === 1)
138
+ direction = available[0];
139
+ if (!direction && tty)
140
+ direction = await selectOne('File to download', available.map(value => ({ value, label: value === 'request' ? 'Original request' : 'Original response' })), options.signal);
141
+ if (!direction)
142
+ throw new CliError('tool_file_direction_required', 'Choose request or response after the command ID.', 2);
143
+ const expected = fileMetadata(original, direction);
144
+ if (!expected)
145
+ throw new CliError('tool_file_unavailable', 'The selected original file is withheld, expired or not retained. No operation was repeated.', 2);
146
+ const into = options.into ?? (tty ? await question('Save this private file to a new path: ', options.signal) : '');
147
+ if (!into.trim() || /[\u0000-\u001f\u007f-\u009f]/u.test(into))
148
+ throw new CliError('tool_download_path_required', 'Use --into <new-file>. Existing files are never replaced.', 2);
149
+ output('tool_download_review', { instance_id: instance, command_id: command, direction, file: expected }, `Original ${direction} · ${command}\n${expected.size.toLocaleString()} bytes. This private device copy will not be erased by cloud retention or withdrawal.`);
150
+ if (!options.approveDownload) {
151
+ if (!tty)
152
+ throw new CliError('tool_download_consent_required', 'Use --approve-download to save this retained private operation file on the device.', 2);
153
+ if (!await selectOne('Save this private operation file?', [{ value: false, label: 'Cancel' }, { value: true, label: 'Download file' }], options.signal))
154
+ return;
155
+ }
156
+ const chosen = resolve(into), parent = await realpath(dirname(chosen)), folder = await lstat(parent), destination = join(parent, basename(chosen));
157
+ const ownedFolder = (value) => value.isDirectory()
158
+ && (process.platform === 'win32' || (!process.getuid || value.uid === process.getuid()) && !(value.mode & 0o022));
159
+ if (!ownedFolder(folder))
160
+ throw new CliError('tool_download_directory_invalid', 'Choose a directory owned by this OS user without shared write permission.', 2);
161
+ try {
162
+ await lstat(destination);
163
+ throw new CliError('tool_download_file_exists', 'The chosen file already exists. Choose a new path.', 2);
164
+ }
165
+ catch (error) {
166
+ if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT'))
167
+ throw error;
168
+ }
169
+ const partial = join(parent, `.firedrill-tool-${randomUUID()}.partial`), file = await open(partial, 'wx', 0o600);
170
+ let held = null;
171
+ try {
172
+ held = await file.stat();
173
+ await access(async (token) => {
174
+ const response = await fetch(endpoint(`${path}/commands/${command}/files/${direction}`), { redirect: 'error', credentials: 'omit',
175
+ headers: { Authorization: `Bearer ${token}`, 'X-Firedrill-Actor': scope.owner, 'X-Firedrill-Workspace': scope.workspace, 'Accept-Encoding': 'identity' },
176
+ signal: AbortSignal.any([options.signal, AbortSignal.timeout(120000)]) });
177
+ if (response.status !== 200) {
178
+ await response.body?.cancel();
179
+ throw responseError(null, response.status);
180
+ }
181
+ if (response.headers.get('content-length') !== String(expected.size) || response.headers.get('x-content-sha256') !== expected.sha256
182
+ || ![null, 'identity'].includes(response.headers.get('content-encoding')) || !response.body && expected.size !== 0) {
183
+ await response.body?.cancel();
184
+ throw invalid();
185
+ }
186
+ let size = 0;
187
+ const digest = createHash('sha256');
188
+ if (response.body) {
189
+ const reader = response.body.getReader();
190
+ try {
191
+ while (true) {
192
+ options.signal.throwIfAborted();
193
+ const part = await reader.read();
194
+ if (part.done)
195
+ break;
196
+ size += part.value.byteLength;
197
+ if (size > expected.size)
198
+ throw invalid();
199
+ digest.update(part.value);
200
+ let offset = 0;
201
+ while (offset < part.value.byteLength) {
202
+ const written = await file.write(part.value, offset, part.value.byteLength - offset);
203
+ if (!written.bytesWritten)
204
+ throw invalid();
205
+ offset += written.bytesWritten;
206
+ }
207
+ }
208
+ }
209
+ finally {
210
+ await reader.cancel().catch(() => undefined);
211
+ reader.releaseLock();
212
+ }
213
+ }
214
+ if (size !== expected.size || digest.digest('hex') !== expected.sha256)
215
+ throw invalid();
216
+ });
217
+ const fresh = await read(), currentFile = fileMetadata(fresh, direction);
218
+ if (!sameOriginal(original, fresh) || !currentFile || currentFile.size !== expected.size || currentFile.sha256 !== expected.sha256)
219
+ throw invalid();
220
+ await file.sync();
221
+ options.signal.throwIfAborted();
222
+ const current = await lstat(partial), currentFolder = await lstat(parent);
223
+ if (!current.isFile() || current.dev !== held.dev || current.ino !== held.ino || current.size !== expected.size
224
+ || process.platform !== 'win32' && !!(current.mode & 0o077) || !ownedFolder(currentFolder)
225
+ || currentFolder.dev !== folder.dev || currentFolder.ino !== folder.ino)
226
+ throw invalid();
227
+ options.signal.throwIfAborted();
228
+ await link(partial, destination);
229
+ output('tool_file_downloaded', { instance_id: instance, command_id: command, direction, path: destination, file: expected }, `File saved to ${terminalText(destination)}\n${expected.size.toLocaleString()} bytes · SHA-256 ${expected.sha256}\nNo tool operation was repeated.`);
230
+ }
231
+ catch (error) {
232
+ if (error instanceof CliError)
233
+ throw error;
234
+ if (options.signal.aborted)
235
+ throw new CliError('canceled', 'The download was canceled. Inspect the chosen path before retrying; no existing file was replaced.', 130);
236
+ throw new CliError('tool_download_unconfirmed', 'The download did not finish. Inspect the chosen path before retrying; no existing file was replaced. Do not repeat the tool operation.');
237
+ }
238
+ finally {
239
+ const identity = held ?? await file.stat().catch(() => null);
240
+ await file.close().catch(() => undefined);
241
+ const current = await lstat(partial).catch(() => null);
242
+ if (identity && current?.isFile() && current.dev === identity.dev && current.ino === identity.ino)
243
+ await unlink(partial).catch(() => undefined);
244
+ }
245
+ }
@@ -0,0 +1 @@
1
+ export * from '../../contracts/tool-connection-contracts.js';
@@ -0,0 +1,23 @@
1
+ import { api } from './auth.js';
2
+ import { identifier } from './world-contracts.js';
3
+ import { toolConnectionRead, toolConnectionsMatch, toolConnectionMatches, toolConnectionAccepted, toolConnectionOptionsMatch } from './tool-connection-contracts.js';
4
+ export function toolConnectionReads(owner, instance, signal) {
5
+ const scope = { workspace: owner.workspace, actor: owner.owner, instance: identifier(instance) };
6
+ const prefix = `/api/workspaces/${scope.workspace}/tools/instances/${scope.instance}`;
7
+ const auth = { actor: owner.owner, workspace: owner.workspace, signal };
8
+ return {
9
+ async page(page = 1, query = '') {
10
+ const value = await api(`${prefix}/connections?page=${page}&q=${encodeURIComponent(query)}`, auth);
11
+ return toolConnectionRead(value, toolConnectionsMatch(value, scope, query, page));
12
+ },
13
+ async options(page = 1, query = '', epoch) {
14
+ const value = await api(`${prefix}/connection-options?page=${page}&q=${encodeURIComponent(query)}`, auth);
15
+ return toolConnectionRead(value, toolConnectionOptionsMatch(value, { ...scope, epoch }, query, page));
16
+ },
17
+ async original(key, selected = {}) {
18
+ const value = await api(`${prefix}/connection-requests/${identifier(key)}`, auth);
19
+ return toolConnectionRead(value, selected.input ? await toolConnectionAccepted(value, scope, key, selected.input, selected)
20
+ : toolConnectionMatches(value, scope, { ...selected, key }));
21
+ },
22
+ };
23
+ }