deepline 0.3.88 → 0.3.90

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 (280) hide show
  1. package/contracts/install-commands.json +29 -0
  2. package/dist/bundling-sources/sdk/src/client.ts +652 -39
  3. package/dist/bundling-sources/sdk/src/config.ts +77 -0
  4. package/dist/bundling-sources/sdk/src/http.ts +76 -30
  5. package/dist/bundling-sources/sdk/src/index.ts +10 -0
  6. package/dist/bundling-sources/sdk/src/monitor-fleet-contract.ts +1 -1
  7. package/dist/bundling-sources/sdk/src/pagination.ts +25 -0
  8. package/dist/bundling-sources/sdk/src/play.ts +15 -1
  9. package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +3 -3
  10. package/dist/bundling-sources/sdk/src/release.ts +16 -9
  11. package/dist/bundling-sources/sdk/src/types.ts +106 -4
  12. package/dist/bundling-sources/shared_libs/integration-contracts/AGENTS.md +9 -0
  13. package/dist/bundling-sources/shared_libs/integration-contracts/README.md +3 -0
  14. package/dist/bundling-sources/shared_libs/integration-contracts/index.ts +2 -0
  15. package/dist/bundling-sources/shared_libs/monitors/analytics-install-guides.ts +77 -0
  16. package/dist/bundling-sources/shared_libs/monitors/analytics-public.ts +10 -0
  17. package/dist/bundling-sources/shared_libs/monitors/billing/charges.ts +125 -0
  18. package/dist/bundling-sources/shared_libs/monitors/billing/policy-builders.ts +686 -0
  19. package/dist/bundling-sources/shared_libs/monitors/billing/pricing.ts +54 -0
  20. package/dist/bundling-sources/shared_libs/monitors/billing/public-pricing.ts +72 -0
  21. package/dist/bundling-sources/shared_libs/monitors/billing/session.ts +45 -0
  22. package/dist/bundling-sources/shared_libs/monitors/billing/state.ts +454 -0
  23. package/dist/bundling-sources/shared_libs/monitors/billing/tamradar.ts +260 -0
  24. package/dist/bundling-sources/shared_libs/monitors/billing/types.ts +215 -0
  25. package/dist/bundling-sources/shared_libs/monitors/callback-security.ts +333 -0
  26. package/dist/bundling-sources/shared_libs/monitors/cursor-page.ts +42 -0
  27. package/dist/bundling-sources/shared_libs/monitors/deactivation-reasons.ts +16 -0
  28. package/dist/bundling-sources/shared_libs/monitors/framework-managed-payload-fields.ts +71 -0
  29. package/dist/bundling-sources/shared_libs/monitors/generated/provider-monitor-callback-admission-targets.ts +16 -0
  30. package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +270 -0
  31. package/dist/bundling-sources/shared_libs/monitors/inbound-publish-key.ts +39 -0
  32. package/dist/bundling-sources/shared_libs/monitors/inbound-queue.ts +82 -0
  33. package/dist/bundling-sources/shared_libs/monitors/inventory-reconciliation.ts +59 -0
  34. package/dist/bundling-sources/shared_libs/monitors/lifecycle-notifications.ts +471 -0
  35. package/dist/bundling-sources/shared_libs/monitors/org-monitor-limits.ts +1 -1
  36. package/dist/bundling-sources/shared_libs/monitors/package.json +36 -0
  37. package/dist/bundling-sources/shared_libs/monitors/provider-callback-verification.ts +1125 -0
  38. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +305 -0
  39. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-capability.ts +964 -0
  40. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-consumer-health.ts +56 -0
  41. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-errors.ts +131 -0
  42. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-identity-claims.ts +535 -0
  43. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-legacy-aliases.ts +16 -0
  44. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-priority-execution.ts +56 -0
  45. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-public-aliases.ts +28 -0
  46. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-runtime-read-eligibility.ts +38 -0
  47. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-state.ts +122 -0
  48. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-status.ts +10 -0
  49. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-upstream-identity.ts +112 -0
  50. package/dist/bundling-sources/shared_libs/monitors/provider-webhook-monitor-runtime.ts +300 -0
  51. package/dist/bundling-sources/shared_libs/monitors/quickstart-prompts.ts +22 -0
  52. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-dead-letter.ts +245 -0
  53. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-message.ts +38 -0
  54. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue-ack-probe.ts +94 -0
  55. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue-delivery.ts +60 -0
  56. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue.ts +60 -0
  57. package/dist/bundling-sources/shared_libs/monitors/snitcher-install-validation.ts +93 -0
  58. package/dist/bundling-sources/shared_libs/monitors/snitcher-ip2company.ts +332 -0
  59. package/dist/bundling-sources/shared_libs/monitors/snitcher-radar.ts +221 -0
  60. package/dist/bundling-sources/shared_libs/monitors/starter-templates.ts +105 -0
  61. package/dist/bundling-sources/shared_libs/monitors/tracking-provider-guidance.ts +73 -0
  62. package/dist/bundling-sources/shared_libs/monitors/tracking-provider-integrations.ts +81 -0
  63. package/dist/bundling-sources/shared_libs/monitors/tsconfig.json +10 -0
  64. package/dist/bundling-sources/shared_libs/observability/build-context.ts +13 -0
  65. package/dist/bundling-sources/shared_libs/observability/log-metadata.ts +160 -0
  66. package/dist/bundling-sources/shared_libs/observability/request-context.ts +68 -0
  67. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +654 -0
  68. package/dist/bundling-sources/shared_libs/observability/scheduled-work-manual.ts +35 -0
  69. package/dist/bundling-sources/shared_libs/observability/scheduled-work.ts +5 -0
  70. package/dist/bundling-sources/shared_libs/play-data-plane/neon-constants.ts +9 -0
  71. package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +272 -0
  72. package/dist/bundling-sources/shared_libs/play-runtime/activity-observation.ts +6 -11
  73. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +135 -12
  74. package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +66 -1
  75. package/dist/bundling-sources/shared_libs/play-runtime/async-play-invocation.ts +77 -0
  76. package/dist/bundling-sources/shared_libs/play-runtime/bounceban-batching.ts +137 -0
  77. package/dist/bundling-sources/shared_libs/play-runtime/checkpoint-boundaries.ts +70 -0
  78. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1159 -166
  79. package/dist/bundling-sources/shared_libs/play-runtime/coordinator-headers.ts +1 -32
  80. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +103 -0
  81. package/dist/bundling-sources/shared_libs/play-runtime/daytona-payload-contract.ts +68 -0
  82. package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-janitor.ts +103 -0
  83. package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-reaper.ts +725 -0
  84. package/dist/bundling-sources/shared_libs/play-runtime/default-batch-strategies.ts +20 -1
  85. package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +29 -0
  86. package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +29 -0
  87. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-invocation-fence.ts +442 -0
  88. package/dist/bundling-sources/shared_libs/play-runtime/execution-protocol.ts +7 -1
  89. package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +18 -0
  90. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +9 -0
  91. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +1 -1
  92. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +1 -1
  93. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +66 -0
  94. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +145 -0
  95. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/ports.ts +21 -0
  96. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/postgres-store.ts +170 -0
  97. package/dist/bundling-sources/shared_libs/play-runtime/internal-bootstrap-token.ts +1322 -0
  98. package/dist/bundling-sources/shared_libs/play-runtime/internal-step-ids.ts +2 -2
  99. package/dist/bundling-sources/shared_libs/play-runtime/lane-retirement-gate.ts +189 -0
  100. package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +112 -0
  101. package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +8 -0
  102. package/dist/bundling-sources/shared_libs/play-runtime/perf-trace.ts +127 -0
  103. package/dist/bundling-sources/shared_libs/play-runtime/play-artifact-runtime-contract.ts +5 -0
  104. package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +24 -1
  105. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/contracts.ts +56 -0
  106. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/index.ts +13 -0
  107. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/job.ts +41 -0
  108. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/ports.ts +17 -0
  109. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/postgres-adapter.ts +54 -0
  110. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/contracts.ts +114 -0
  111. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/engine-suspension.ts +108 -0
  112. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/index.ts +36 -0
  113. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/job.ts +97 -0
  114. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/ports.ts +104 -0
  115. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/postgres-fence-adapter.ts +263 -0
  116. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/worker-terminal-projection.ts +64 -0
  117. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/contracts.ts +29 -0
  118. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +56 -0
  119. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/index.ts +15 -0
  120. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/job.ts +65 -0
  121. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/ports.ts +17 -0
  122. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/contracts.ts +42 -0
  123. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/index.ts +17 -0
  124. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/job.ts +142 -0
  125. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/ports.ts +42 -0
  126. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/postgres-adapter.ts +102 -0
  127. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +60 -0
  128. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/index.ts +16 -0
  129. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +78 -0
  130. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +54 -0
  131. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +71 -0
  132. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-batching-registry.ts +4 -0
  133. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +28 -0
  134. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +4 -0
  135. package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +5 -8
  136. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +23 -2
  137. package/dist/bundling-sources/shared_libs/play-runtime/run-identity.ts +168 -0
  138. package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +1 -1
  139. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +90 -13
  140. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +6 -12
  141. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-start-admission.ts +68 -0
  142. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +21 -0
  143. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/bundle.ts +0 -1
  144. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +14 -43
  145. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +67 -213
  146. package/dist/bundling-sources/shared_libs/play-runtime/runtime-authority.ts +201 -0
  147. package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +20 -0
  148. package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +6 -0
  149. package/dist/bundling-sources/shared_libs/play-runtime/runtime-env.ts +12 -0
  150. package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +67 -0
  151. package/dist/bundling-sources/shared_libs/play-runtime/runtime-release-environment.ts +2 -0
  152. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-contract.ts +68 -0
  153. package/dist/bundling-sources/shared_libs/play-runtime/runtime-scheduler-topology.ts +38 -2
  154. package/dist/bundling-sources/shared_libs/play-runtime/runtime-telemetry.ts +108 -0
  155. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +4 -0
  156. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +3 -0
  157. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +37 -1
  158. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-deadline-reconciliation.ts +88 -0
  159. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +124 -0
  160. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-run-task.ts +68 -0
  161. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-pool.ts +193 -0
  162. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-queue-lifecycle.ts +269 -0
  163. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-queue-suffix.ts +73 -0
  164. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +6642 -0
  165. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +897 -0
  166. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-metrics.ts +120 -0
  167. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +76 -0
  168. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +476 -0
  169. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +47 -0
  170. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +147 -0
  171. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/index.ts +27 -0
  172. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/lazy.ts +50 -0
  173. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/play-run-recovery.ts +98 -0
  174. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-budget-state.ts +260 -0
  175. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +1416 -0
  176. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +1255 -0
  177. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +942 -0
  178. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +14636 -0
  179. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +888 -0
  180. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +355 -0
  181. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +46 -0
  182. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runner-push.ts +60 -0
  183. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-api-origin.ts +33 -0
  184. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-gateway-health.ts +64 -0
  185. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-capacity.ts +835 -0
  186. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-ledger-reconciliation.ts +1218 -0
  187. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-operation.ts +207 -0
  188. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-scheduler-schema.ts +10 -0
  189. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/scheduler-capacity.ts +123 -0
  190. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/scheduler-launch-read-observability.ts +78 -0
  191. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/submit-admission.ts +169 -0
  192. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/terminal-contract.ts +20 -0
  193. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-queue-metrics.ts +34 -0
  194. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +249 -0
  195. package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +7 -0
  196. package/dist/bundling-sources/shared_libs/play-runtime/step-progress.ts +296 -0
  197. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/native-batch.ts +3 -3
  198. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +20 -1
  199. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +60 -15
  200. package/dist/bundling-sources/shared_libs/play-runtime/tool-execution-outcome.ts +20 -0
  201. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +1 -1
  202. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +1 -1
  203. package/dist/bundling-sources/shared_libs/play-runtime/transient-service-error.ts +12 -0
  204. package/dist/bundling-sources/shared_libs/play-runtime/vercel-protection.ts +14 -1
  205. package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-storage-contract.ts +165 -0
  206. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +15 -23
  207. package/dist/bundling-sources/shared_libs/play-runtime/workflow-executor-auth.ts +169 -0
  208. package/dist/bundling-sources/shared_libs/play-runtime/zerobounce-batching.ts +122 -0
  209. package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +8 -3
  210. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +454 -17
  211. package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +19 -4
  212. package/dist/bundling-sources/shared_libs/plays/contracts.ts +8 -14
  213. package/dist/bundling-sources/shared_libs/plays/core.ts +11 -6
  214. package/dist/bundling-sources/shared_libs/plays/deployment-status.ts +1 -1
  215. package/dist/bundling-sources/shared_libs/plays/docflow-binding-owner.ts +16 -11
  216. package/dist/bundling-sources/shared_libs/plays/docflow.ts +1 -1
  217. package/dist/bundling-sources/shared_libs/plays/email-status.ts +1 -1
  218. package/dist/bundling-sources/shared_libs/plays/play-grep-search.ts +39 -0
  219. package/dist/bundling-sources/shared_libs/plays/resolve-static-pipeline.ts +1 -1
  220. package/dist/bundling-sources/shared_libs/plays/share/copy.ts +271 -0
  221. package/dist/bundling-sources/shared_libs/plays/share/metrics.ts +140 -0
  222. package/dist/bundling-sources/shared_libs/plays/share/public-read-model.ts +86 -0
  223. package/dist/bundling-sources/shared_libs/plays/share/seo-quality.ts +181 -0
  224. package/dist/bundling-sources/shared_libs/plays/share/static-pipeline-summary.ts +658 -0
  225. package/dist/bundling-sources/shared_libs/plays/share/types.ts +197 -0
  226. package/dist/bundling-sources/shared_libs/plays/slack-hitl-timeout-error.ts +37 -0
  227. package/dist/bundling-sources/shared_libs/plays/slug.ts +115 -0
  228. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +50 -15
  229. package/dist/bundling-sources/shared_libs/plays/tool-category-descriptions.ts +2 -2
  230. package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +1 -1
  231. package/dist/bundling-sources/shared_libs/product-notifications/catalog.ts +877 -0
  232. package/dist/bundling-sources/shared_libs/product-notifications/configuration-draft.ts +456 -0
  233. package/dist/bundling-sources/shared_libs/product-notifications/configuration.ts +566 -0
  234. package/dist/bundling-sources/shared_libs/product-notifications/content.ts +28 -0
  235. package/dist/bundling-sources/shared_libs/product-notifications/default-policy.ts +60 -0
  236. package/dist/bundling-sources/shared_libs/product-notifications/email.ts +246 -0
  237. package/dist/bundling-sources/shared_libs/product-notifications/evaluation.ts +280 -0
  238. package/dist/bundling-sources/shared_libs/product-notifications/events.ts +433 -0
  239. package/dist/bundling-sources/shared_libs/product-notifications/legacy-slack.ts +80 -0
  240. package/dist/bundling-sources/shared_libs/product-notifications/overflow.ts +18 -0
  241. package/dist/bundling-sources/shared_libs/product-notifications/rolling-limit.ts +24 -0
  242. package/dist/bundling-sources/shared_libs/product-notifications/slack-delivery.ts +624 -0
  243. package/dist/bundling-sources/shared_libs/product-notifications/slack-payload.ts +140 -0
  244. package/dist/bundling-sources/shared_libs/product-notifications/slack-transport.ts +75 -0
  245. package/dist/bundling-sources/shared_libs/product-notifications/workspace.ts +38 -0
  246. package/dist/bundling-sources/shared_libs/runtime-env.ts +12 -0
  247. package/dist/bundling-sources/shared_libs/security/email-blacklist.ts +42 -0
  248. package/dist/bundling-sources/shared_libs/security/header-safe-env.ts +35 -0
  249. package/dist/bundling-sources/shared_libs/security/internal-actor.ts +50 -0
  250. package/dist/bundling-sources/shared_libs/security/secret-redaction.ts +267 -0
  251. package/dist/bundling-sources/shared_libs/security/slack-sanitize.ts +133 -0
  252. package/dist/cli/getting-started.d.mts +52 -0
  253. package/dist/cli/getting-started.d.ts +52 -0
  254. package/dist/cli/getting-started.js +65 -0
  255. package/dist/cli/getting-started.mjs +36 -0
  256. package/dist/cli/index.d.mts +9 -0
  257. package/dist/cli/index.d.ts +9 -0
  258. package/dist/cli/index.js +1611 -453
  259. package/dist/cli/index.mjs +1596 -447
  260. package/dist/{compiler-manifest-Dbov16F7.d.mts → compiler-manifest-BaJfE-5H.d.mts} +136 -10
  261. package/dist/{compiler-manifest-Dbov16F7.d.ts → compiler-manifest-BaJfE-5H.d.ts} +136 -10
  262. package/dist/index.d.mts +910 -155
  263. package/dist/index.d.ts +910 -155
  264. package/dist/index.js +550 -64
  265. package/dist/index.mjs +550 -64
  266. package/dist/install-integrity.json +193 -4
  267. package/dist/monitor-fleet-contract.d.mts +138 -0
  268. package/dist/monitor-fleet-contract.d.ts +138 -0
  269. package/dist/monitor-fleet-contract.js +404 -0
  270. package/dist/monitor-fleet-contract.mjs +367 -0
  271. package/dist/plays/bundle-play-file.d.mts +8 -3
  272. package/dist/plays/bundle-play-file.d.ts +8 -3
  273. package/dist/plays/bundle-play-file.mjs +100 -20
  274. package/dist/release.d.mts +255 -0
  275. package/dist/release.d.ts +255 -0
  276. package/dist/release.js +215 -0
  277. package/dist/release.mjs +188 -0
  278. package/package.json +23 -4
  279. /package/dist/bundling-sources/shared_libs/{integrations/bettercontact-execution-policy.ts → integration-contracts/bettercontact/execution-policy.ts} +0 -0
  280. /package/dist/bundling-sources/shared_libs/{integrations/theirstack-execution-policy.ts → integration-contracts/theirstack/execution-policy.ts} +0 -0
package/dist/cli/index.js CHANGED
@@ -6,6 +6,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
9
13
  var __copyProps = (to, from, except, desc) => {
10
14
  if (from && typeof from === "object" || typeof from === "function") {
11
15
  for (let key of __getOwnPropNames(from))
@@ -22,6 +26,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
26
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
27
  mod
24
28
  ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/cli/index.ts
32
+ var cli_exports = {};
33
+ __export(cli_exports, {
34
+ main: () => main
35
+ });
36
+ module.exports = __toCommonJS(cli_exports);
25
37
 
26
38
  // src/cli/proxy-env.ts
27
39
  var import_undici = require("undici");
@@ -186,6 +198,26 @@ var import_node_path29 = require("path");
186
198
  var import_node_os18 = require("os");
187
199
  var import_commander5 = require("commander");
188
200
 
201
+ // src/pagination.ts
202
+ async function* iterateCursorPages(fetchPage) {
203
+ let cursor = null;
204
+ const seen = /* @__PURE__ */ new Set();
205
+ for (; ; ) {
206
+ const page = await fetchPage(cursor);
207
+ if (page.hasMore) {
208
+ if (!page.cursor || seen.has(page.cursor)) {
209
+ throw new Error(
210
+ "Pagination reported more results without an advancing cursor"
211
+ );
212
+ }
213
+ seen.add(page.cursor);
214
+ }
215
+ yield page.items;
216
+ if (!page.hasMore) return;
217
+ cursor = page.cursor;
218
+ }
219
+ }
220
+
189
221
  // src/config.ts
190
222
  var import_node_fs = require("fs");
191
223
  var import_node_os = require("os");
@@ -587,6 +619,7 @@ var HOST_URL_ENV = "DEEPLINE_HOST_URL";
587
619
  var API_KEY_ENV = "DEEPLINE_API_KEY";
588
620
  var ACTIVE_ORG_ID_ENV = "DEEPLINE_ACTIVE_ORG_ID";
589
621
  var ACTIVE_ORG_NAME_ENV = "DEEPLINE_ACTIVE_ORG_NAME";
622
+ var FEATURES_ENV = "DEEPLINE_FEATURES";
590
623
  var PROD_URL = "https://code.deepline.com";
591
624
  var DEFAULT_TIMEOUT = 6e4;
592
625
  var DEFAULT_MAX_RETRIES = 3;
@@ -611,8 +644,40 @@ var CLI_ENV_ALLOWED_KEYS = /* @__PURE__ */ new Set([
611
644
  HOST_URL_ENV,
612
645
  API_KEY_ENV,
613
646
  ACTIVE_ORG_ID_ENV,
614
- ACTIVE_ORG_NAME_ENV
647
+ ACTIVE_ORG_NAME_ENV,
648
+ FEATURES_ENV
615
649
  ]);
650
+ function featureEnvValues(data) {
651
+ if (!Array.isArray(data.features)) return {};
652
+ return {
653
+ [FEATURES_ENV]: data.features.filter(
654
+ (key) => typeof key === "string" && key.trim() !== ""
655
+ ).map((key) => key.trim()).join(",")
656
+ };
657
+ }
658
+ function storedHostApiKey(baseUrl) {
659
+ return loadCliEnv(normalizeBaseUrl(baseUrl) || baseUrl)[API_KEY_ENV] ?? "";
660
+ }
661
+ function storedHostFeatures(baseUrl) {
662
+ return loadCliEnv(normalizeBaseUrl(baseUrl) || baseUrl)[FEATURES_ENV];
663
+ }
664
+ function envHasFeature(env, feature) {
665
+ return (env[FEATURES_ENV] ?? "").split(",").map((key) => key.trim()).includes(feature);
666
+ }
667
+ function cliFeatureEnabled(feature) {
668
+ const processEnv = process.env;
669
+ if (envHasFeature(processEnv, feature)) return true;
670
+ if (processEnv[API_KEY_ENV]?.trim()) return false;
671
+ try {
672
+ const baseUrl = resolveBaseUrlOnly();
673
+ const projectEnv = loadProjectEnvCandidates().find(
674
+ ({ env }) => normalizeBaseUrl(env[HOST_URL_ENV] ?? "") === baseUrl && Boolean(env[API_KEY_ENV])
675
+ );
676
+ return envHasFeature(projectEnv?.env ?? loadCliEnv(baseUrl), feature);
677
+ } catch {
678
+ return false;
679
+ }
680
+ }
616
681
  function baseUrlSlug(baseUrl) {
617
682
  let url;
618
683
  try {
@@ -812,6 +877,10 @@ function mergePersistedEnvValues(existing, values) {
812
877
  if (nextApiKey !== void 0 && nextApiKey !== existing[API_KEY_ENV] && values[ACTIVE_ORG_ID_ENV] === void 0) {
813
878
  delete merged[ACTIVE_ORG_ID_ENV];
814
879
  delete merged[ACTIVE_ORG_NAME_ENV];
880
+ delete merged[FEATURES_ENV];
881
+ }
882
+ if (values[FEATURES_ENV] === "") {
883
+ delete merged[FEATURES_ENV];
815
884
  }
816
885
  return merged;
817
886
  }
@@ -935,7 +1004,10 @@ function resolveConfig(options) {
935
1004
  apiKey,
936
1005
  baseUrl,
937
1006
  timeout: options?.timeout ?? DEFAULT_TIMEOUT,
938
- maxRetries: options?.maxRetries ?? DEFAULT_MAX_RETRIES
1007
+ maxRetries: options?.maxRetries ?? DEFAULT_MAX_RETRIES,
1008
+ // Pass through untouched: the transport owns case-insensitive merging so
1009
+ // there is exactly one place that decides which header wins.
1010
+ ...options?.headers ? { headers: { ...options.headers } } : {}
939
1011
  };
940
1012
  }
941
1013
  function mergeProjectEnvFile(filePath, values) {
@@ -1225,20 +1297,27 @@ var SDK_RELEASE = {
1225
1297
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
1226
1298
  // getters keep their established compatibility behavior.
1227
1299
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
1228
- version: "0.3.88",
1229
- updateSummary: "Automatic CLI updates are now enabled by default. To opt out, run `deepline settings autoupdate off`; use `deepline settings autoupdate on` to re-enable updates or `deepline settings autoupdate pin <version>` to hold an exact release. This release also adds raw-v2 tool responses at toolResponse.rawV2 while preserving existing toolResponse.raw and declared getters.",
1300
+ // 0.3.90 is the first deliberately versioned SDK release for API v3. Play
1301
+ // grep hard-cuts to the single Convex full-text index; it does not retain a
1302
+ // registry/scanner reader for any SDK/API major.
1303
+ version: "0.3.90",
1304
+ updateSummary: "Play grep now uses one source-only Convex full-text index. It no longer scans the registry, pages through the complete indexed match set, and rejects retired grep modes. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
1230
1305
  packageCapabilities: {
1231
1306
  updatePreferences: 1
1232
1307
  },
1233
1308
  contracts: {
1234
1309
  api: {
1235
1310
  name: "sdk-http-api",
1236
- // API v2 is append-only. A breaking public request/response change needs
1237
- // v3 and an explicit v2 support-window decision, never a patch release.
1238
- currentMajor: 2,
1239
- supportedMajors: [2],
1240
- // Accepted only to bridge SDKs released before X-Deepline-API-Major.
1311
+ // API v3 hard-cuts Play grep to the canonical Convex index. API v2 stays
1312
+ // admitted for the explicit SDK support window, but it selects neither a
1313
+ // registry scanner nor another grep reader: every accepted grep request
1314
+ // uses the same indexed query and retired flags fail at the API boundary.
1315
+ currentMajor: 3,
1316
+ supportedMajors: [3, 2],
1317
+ // The first wire id is emitted by the current SDK. Remaining ids are
1318
+ // accepted only while the API v2 support window is open.
1241
1319
  legacyWireIds: [
1320
+ "2026-09-play-grep-convex-index-hard-cutover",
1242
1321
  "2026-07-admin-cli-local-cutover",
1243
1322
  "2026-07-native-monitor-launch-hard-cutover"
1244
1323
  ]
@@ -1779,11 +1858,33 @@ function normalizeRequestAbortError(error, input2) {
1779
1858
  }
1780
1859
  return normalized;
1781
1860
  }
1861
+ function applyExplicitHeaders(headers, explicit) {
1862
+ if (!explicit) return headers;
1863
+ for (const [rawName, value] of Object.entries(explicit)) {
1864
+ const name = rawName.trim().toLowerCase();
1865
+ if (!name) continue;
1866
+ for (const existing of Object.keys(headers)) {
1867
+ if (existing.toLowerCase() === name) delete headers[existing];
1868
+ }
1869
+ headers[name] = value;
1870
+ }
1871
+ return headers;
1872
+ }
1782
1873
  function isAbortLikeError(error) {
1783
1874
  const name = error.name.toLowerCase();
1784
1875
  const message = error.message.toLowerCase();
1785
1876
  return name === "aborterror" || message.includes("operation was aborted") || message === "aborted";
1786
1877
  }
1878
+ function withErrorCause(error, cause) {
1879
+ if (cause !== null && cause !== void 0) {
1880
+ Object.defineProperty(error, "cause", {
1881
+ value: cause,
1882
+ configurable: true,
1883
+ writable: true
1884
+ });
1885
+ }
1886
+ return error;
1887
+ }
1787
1888
  function mapNetworkError(baseUrl, error, targetLabel2) {
1788
1889
  const tagged = error;
1789
1890
  const timeoutMs = tagged?.[REQUEST_TIMEOUT_MARKER];
@@ -1900,7 +2001,7 @@ var HttpClient = class {
1900
2001
  if (coordinatorInternalToken?.trim() && (coordinatorUrl?.trim() || workerCallbackUrl?.trim() || runtimeTestFault?.trim() || absurdReleaseOverride?.trim() || preferredReceiptGatewayMachineId?.trim())) {
1901
2002
  headers[COORDINATOR_INTERNAL_TOKEN_HEADER] = coordinatorInternalToken.trim();
1902
2003
  }
1903
- return headers;
2004
+ return applyExplicitHeaders(headers, this.config.headers);
1904
2005
  }
1905
2006
  /**
1906
2007
  * Send an HTTP request with automatic retries and error handling.
@@ -2054,32 +2155,38 @@ var HttpClient = class {
2054
2155
  );
2055
2156
  if (options?.toolId) {
2056
2157
  const code = mappedNetworkError.code;
2057
- throw new ToolExecutionError(
2058
- withCoworkNetworkHint(mappedNetworkError.message),
2059
- {
2060
- toolId: options.toolId,
2061
- provider: providerLabelFromToolId(options.toolId),
2062
- operation: options.toolId,
2063
- code,
2064
- origin: "deepline",
2065
- category: "network",
2066
- // A direct client timeout has ambiguous delivery. Fail closed unless
2067
- // the server returns the authoritative action delivery contract.
2068
- retryable: false,
2069
- statusCode: null,
2070
- requestId: null,
2071
- retryAfterMs: null,
2072
- networkKind: code === "NETWORK_TIMEOUT" ? "timeout" : code === "NETWORK_ABORTED" ? "unknown" : "unavailable",
2073
- networkScope: "client_to_deepline",
2074
- details: mappedNetworkError.details
2075
- }
2158
+ throw withErrorCause(
2159
+ new ToolExecutionError(
2160
+ withCoworkNetworkHint(mappedNetworkError.message),
2161
+ {
2162
+ toolId: options.toolId,
2163
+ provider: providerLabelFromToolId(options.toolId),
2164
+ operation: options.toolId,
2165
+ code,
2166
+ origin: "deepline",
2167
+ category: "network",
2168
+ // A direct client timeout has ambiguous delivery. Fail closed unless
2169
+ // the server returns the authoritative action delivery contract.
2170
+ retryable: false,
2171
+ statusCode: null,
2172
+ requestId: null,
2173
+ retryAfterMs: null,
2174
+ networkKind: code === "NETWORK_TIMEOUT" ? "timeout" : code === "NETWORK_ABORTED" ? "unknown" : "unavailable",
2175
+ networkScope: "client_to_deepline",
2176
+ details: mappedNetworkError.details
2177
+ }
2178
+ ),
2179
+ lastError
2076
2180
  );
2077
2181
  }
2078
- throw new DeeplineError(
2079
- withCoworkNetworkHint(mappedNetworkError.message),
2080
- void 0,
2081
- mappedNetworkError.code,
2082
- mappedNetworkError.details
2182
+ throw withErrorCause(
2183
+ new DeeplineError(
2184
+ withCoworkNetworkHint(mappedNetworkError.message),
2185
+ void 0,
2186
+ mappedNetworkError.code,
2187
+ mappedNetworkError.details
2188
+ ),
2189
+ lastError
2083
2190
  );
2084
2191
  }
2085
2192
  /**
@@ -2453,6 +2560,17 @@ var PRODUCT_NOTIFICATION_DELIVERY_LEASE_MS = 2 * 6e4;
2453
2560
  var PRODUCT_NOTIFICATION_SUCCESS_TTL_MS = 15 * 6e4;
2454
2561
  var PRODUCT_NOTIFICATION_FAILURE_TTL_MS = 24 * 60 * 6e4;
2455
2562
 
2563
+ // ../product-notifications/configuration.ts
2564
+ var PRODUCT_NOTIFICATION_EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2565
+ function normalizeProductNotificationEmail(value) {
2566
+ const email = typeof value === "string" ? value.trim().toLowerCase() : "";
2567
+ return email && email.length <= 320 && PRODUCT_NOTIFICATION_EMAIL_PATTERN.test(email) ? email : null;
2568
+ }
2569
+ var DEFAULT_PRODUCT_NOTIFICATION_DELIVERY_TIMING = {
2570
+ mode: "rolling_limit",
2571
+ windowMs: 36e5
2572
+ };
2573
+
2456
2574
  // src/stream-reconnect.ts
2457
2575
  var STREAM_RECONNECT_BASE_DELAY_MS = 500;
2458
2576
  var STREAM_RECONNECT_MAX_DELAY_MS = 15e3;
@@ -2981,7 +3099,7 @@ function isRecord4(value) {
2981
3099
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
2982
3100
  }
2983
3101
  function isPlayActivityObservation(value) {
2984
- if (!isRecord4(value) || value.schemaVersion !== 1 || typeof value.activityId !== "string" || !value.activityId.trim() || typeof value.observedAt !== "number" || !Number.isFinite(value.observedAt) || !isRecord4(value.target) || !isRecord4(value.state)) {
3102
+ if (!isRecord4(value) || value.schemaVersion !== void 0 && value.schemaVersion !== 1 || typeof value.activityId !== "string" || !value.activityId.trim() || typeof value.observedAt !== "number" || !Number.isFinite(value.observedAt) || !isRecord4(value.target) || !isRecord4(value.state)) {
2985
3103
  return false;
2986
3104
  }
2987
3105
  const targetKinds = /* @__PURE__ */ new Set([
@@ -3088,7 +3206,6 @@ function projectPlayRunActivity(input2) {
3088
3206
  if (input2.waitKind === "detached_runner") {
3089
3207
  return projection(
3090
3208
  {
3091
- schemaVersion: 1,
3092
3209
  activityId: "runner:managed",
3093
3210
  target: {
3094
3211
  kind: "runner",
@@ -3104,7 +3221,6 @@ function projectPlayRunActivity(input2) {
3104
3221
  if (input2.waitKind === "sleep") {
3105
3222
  return projection(
3106
3223
  {
3107
- schemaVersion: 1,
3108
3224
  activityId: "run:sleep",
3109
3225
  target: {
3110
3226
  kind: "step",
@@ -3124,7 +3240,6 @@ function projectPlayRunActivity(input2) {
3124
3240
  if (input2.waitKind === "integration_event" || input2.waitKind === "integration_event_batch") {
3125
3241
  return projection(
3126
3242
  {
3127
- schemaVersion: 1,
3128
3243
  activityId: `event:${input2.activeNodeId ?? "run"}`,
3129
3244
  stepId: input2.activeNodeId ?? void 0,
3130
3245
  target: {
@@ -3148,7 +3263,6 @@ function projectPlayRunActivity(input2) {
3148
3263
  if (normalizedStatus === "waiting") {
3149
3264
  return projection(
3150
3265
  {
3151
- schemaVersion: 1,
3152
3266
  activityId: "run:legacy-wait",
3153
3267
  target: {
3154
3268
  kind: "step",
@@ -3175,7 +3289,6 @@ function projectPlayRunActivity(input2) {
3175
3289
  const datasetProgress = datasetStep?.progress ?? null;
3176
3290
  return projection(
3177
3291
  {
3178
- schemaVersion: 1,
3179
3292
  activityId: `dataset:${pendingDataset.datasetId ?? pendingDataset.tableNamespace}`,
3180
3293
  target: {
3181
3294
  kind: "dataset",
@@ -3203,7 +3316,6 @@ function projectPlayRunActivity(input2) {
3203
3316
  const provider = toolId?.split(/[._]/)[0]?.trim() || null;
3204
3317
  return projection(
3205
3318
  {
3206
- schemaVersion: 1,
3207
3319
  activityId: `step:${activeStep.nodeId}`,
3208
3320
  stepId: activeStep.nodeId,
3209
3321
  target: toolId && provider ? {
@@ -3228,7 +3340,6 @@ function projectPlayRunActivity(input2) {
3228
3340
  if (normalizedStatus === "queued") {
3229
3341
  return projection(
3230
3342
  {
3231
- schemaVersion: 1,
3232
3343
  activityId: "capacity:worker",
3233
3344
  target: { kind: "capacity", pool: "worker" },
3234
3345
  state: { kind: "queued", reason: "capacity" },
@@ -3238,7 +3349,6 @@ function projectPlayRunActivity(input2) {
3238
3349
  );
3239
3350
  }
3240
3351
  const fallback = {
3241
- schemaVersion: 1,
3242
3352
  activityId: "run:legacy",
3243
3353
  target: {
3244
3354
  kind: "step",
@@ -4353,7 +4463,7 @@ async function* observeRunEvents(options) {
4353
4463
  }
4354
4464
  }
4355
4465
 
4356
- // ../../shared_libs/integrations/theirstack-execution-policy.ts
4466
+ // ../integration-contracts/theirstack/execution-policy.ts
4357
4467
  var THEIRSTACK_SLOW_JOB_SEARCH_PROVIDER_TIMEOUT_MS = 135e3;
4358
4468
  var THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS = 6e4 + THEIRSTACK_SLOW_JOB_SEARCH_PROVIDER_TIMEOUT_MS + 15e3;
4359
4469
  var THEIRSTACK_COMPANY_SEARCH_CLIENT_TIMEOUT_MS = 21e4;
@@ -4407,7 +4517,7 @@ function resolveTheirstackClientTimeoutMs(endpointId, payload) {
4407
4517
  return usesExtendedTheirstackJobSearchBudget(endpointId, payload) ? THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS : null;
4408
4518
  }
4409
4519
 
4410
- // ../../shared_libs/integrations/bettercontact-execution-policy.ts
4520
+ // ../integration-contracts/bettercontact/execution-policy.ts
4411
4521
  var BETTERCONTACT_LAUNCHER_CLIENT_TIMEOUT_MS = 12e4;
4412
4522
  var BETTERCONTACT_LAUNCHER_TOOL_IDS = /* @__PURE__ */ new Set([
4413
4523
  "bettercontact_enrich",
@@ -4498,6 +4608,42 @@ function providerMetaFromToolResponseRawV2(rawV2, view) {
4498
4608
  }
4499
4609
 
4500
4610
  // src/client.ts
4611
+ var MAX_NOTIFICATION_GROUP_CREATE_TARGETS = 50;
4612
+ function parseNotificationGroupTarget(value) {
4613
+ const normalized = value.trim();
4614
+ if (normalized.toLowerCase().startsWith("email:")) {
4615
+ const email = normalizeProductNotificationEmail(normalized.slice(6));
4616
+ if (!email) {
4617
+ throw new Error(
4618
+ `Invalid email notification target "${value}". Use --to email:name@example.com.`
4619
+ );
4620
+ }
4621
+ return { kind: "email", email };
4622
+ }
4623
+ const slack = /^slack:(.+)$/i.exec(normalized)?.[1]?.trim();
4624
+ if (!slack) {
4625
+ const provider = normalized.split(":", 1)[0] || normalized;
4626
+ throw new Error(
4627
+ `Unsupported notification destination "${provider}". Use email: or slack:.`
4628
+ );
4629
+ }
4630
+ const member = /^member:([UW][A-Z0-9]{8,})$/i.exec(slack);
4631
+ if (member?.[1]) {
4632
+ return { kind: "slack", memberId: member[1].toUpperCase() };
4633
+ }
4634
+ if (slack.toLowerCase().startsWith("member:")) {
4635
+ throw new Error(
4636
+ "Slack member IDs must look like slack:member:U0123456789."
4637
+ );
4638
+ }
4639
+ if (!slack.replace(/^#/, "").trim()) {
4640
+ throw new Error("Use --to slack:#channel.");
4641
+ }
4642
+ return { kind: "slack", channel: slack };
4643
+ }
4644
+ function notificationObjectKey(kind) {
4645
+ return `${kind}.${globalThis.crypto.randomUUID()}`;
4646
+ }
4501
4647
  var TERMINAL_PLAY_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
4502
4648
  var INCLUDE_TOOL_METADATA_HEADER = "x-deepline-include-tool-metadata";
4503
4649
  var EXECUTE_RESPONSE_CONTRACT_HEADER = "x-deepline-execute-response-contract";
@@ -5017,6 +5163,7 @@ var DeeplineClient = class {
5017
5163
  this.runs = {
5018
5164
  get: (runId, options2) => this.getRunStatus(runId, options2),
5019
5165
  list: (options2) => this.listRuns(options2),
5166
+ listPage: (options2) => this.listRunsPage(options2),
5020
5167
  tail: (runId, options2) => this.tailRun(runId, options2),
5021
5168
  logs: (runId, options2) => this.getRunLogs(runId, options2),
5022
5169
  input: (runId) => this.getRunInput(runId),
@@ -5067,6 +5214,10 @@ var DeeplineClient = class {
5067
5214
  audit: (options2) => this.auditMonitors(options2),
5068
5215
  repair: (options2) => this.repairMonitors(options2),
5069
5216
  health: (options2) => this.readMonitorsHealth(options2),
5217
+ batch: {
5218
+ submit: (options2) => this.submitMonitorBatch(options2),
5219
+ get: (operationId, options2) => this.getMonitorBatch(operationId, options2)
5220
+ },
5070
5221
  fleets: {
5071
5222
  sync: (definitionOrId, options2) => this.syncMonitorFleet(definitionOrId, options2),
5072
5223
  get: (fleetId, options2) => fleetId === void 0 ? this.listMonitorFleets() : this.getMonitorFleet(fleetId, options2),
@@ -5199,6 +5350,16 @@ var DeeplineClient = class {
5199
5350
  (secret) => secret.name === normalized && secret.status === "active" && secret.hasValue
5200
5351
  ) ?? null;
5201
5352
  }
5353
+ /** Check effective scope and active-version health without reading the value.
5354
+ * Without playName, checks the organization-scoped secret only. With a Play
5355
+ * name, an active Play secret takes precedence; a deleted/disabled Play
5356
+ * secret falls back to the organization-scoped secret.
5357
+ */
5358
+ async inspectSecret(name, options = {}) {
5359
+ const params = new URLSearchParams({ name: name.trim().toUpperCase() });
5360
+ if (options.playName !== void 0) params.set("play", options.playName);
5361
+ return this.http.get(`/api/v2/secrets?${params.toString()}`);
5362
+ }
5202
5363
  // ——————————————————————————————————————————————————————————
5203
5364
  // Tools
5204
5365
  // ——————————————————————————————————————————————————————————
@@ -5290,6 +5451,10 @@ var DeeplineClient = class {
5290
5451
  `/api/v2/integrations/${encodeURIComponent(toolId)}/get`,
5291
5452
  {
5292
5453
  method: "GET",
5454
+ // Tool metadata is a read-only catalog lookup. Keep the metadata
5455
+ // header while opting into the same transient edge-response retries as
5456
+ // other GET catalog methods such as searchTools().
5457
+ retryApiErrors: true,
5293
5458
  headers: {
5294
5459
  "x-deepline-tool-meta-only": "1"
5295
5460
  }
@@ -6212,6 +6377,10 @@ var DeeplineClient = class {
6212
6377
  * ```
6213
6378
  */
6214
6379
  async listRuns(options) {
6380
+ return (await this.listRunsPage(options)).runs;
6381
+ }
6382
+ /** Read one run page without discarding server pagination metadata. */
6383
+ async listRunsPage(options) {
6215
6384
  const playName = options.play?.trim();
6216
6385
  const params = new URLSearchParams();
6217
6386
  if (playName) {
@@ -6242,7 +6411,7 @@ var DeeplineClient = class {
6242
6411
  const response = await this.http.get(
6243
6412
  `/api/v2/runs?${params.toString()}`
6244
6413
  );
6245
- return response.runs ?? [];
6414
+ return { ...response, runs: response.runs ?? [] };
6246
6415
  }
6247
6416
  /**
6248
6417
  * Observe one run's live events. Uses the Convex Run Snapshot subscription
@@ -6637,13 +6806,33 @@ var DeeplineClient = class {
6637
6806
  );
6638
6807
  }
6639
6808
  /**
6640
- * List callable plays visible to the workspace.
6809
+ * List one page of callable plays; grep retains exhaustive matching results.
6810
+ * Use listPlaysPage to retain pagination metadata.
6641
6811
  *
6642
6812
  * Pass `origin: "prebuilt"` for Deepline-managed prebuilts or
6643
6813
  * `origin: "owned"` for org-owned plays.
6644
6814
  */
6645
6815
  async listPlays(options) {
6816
+ if (!options?.grep?.trim()) {
6817
+ return (await this.requestPlaysPage(options, false)).plays;
6818
+ }
6819
+ const plays = [];
6820
+ for await (const items of iterateCursorPages(async (cursor) => {
6821
+ const page = await this.listPlaysPage({ ...options, cursor });
6822
+ return { items: page.plays, cursor: page.cursor, hasMore: page.hasMore };
6823
+ })) {
6824
+ plays.push(...items);
6825
+ }
6826
+ return plays;
6827
+ }
6828
+ /** Read one bounded inventory page; cursor continues the same filters. */
6829
+ async listPlaysPage(options) {
6830
+ return this.requestPlaysPage(options);
6831
+ }
6832
+ async requestPlaysPage(options, paginateInventory = true) {
6646
6833
  const params = new URLSearchParams();
6834
+ if (paginateInventory && !options?.grep?.trim())
6835
+ params.set("inventory_paginated", "1");
6647
6836
  if (options?.origin) params.set("origin", options.origin);
6648
6837
  if (options?.categories) {
6649
6838
  params.set(
@@ -6655,26 +6844,27 @@ var DeeplineClient = class {
6655
6844
  params.set("include_tool_categories", "1");
6656
6845
  }
6657
6846
  if (options?.includeArchived) params.set("include_archived", "1");
6847
+ if (options?.includeCostEstimates) {
6848
+ params.set("include_cost_estimates", "1");
6849
+ }
6658
6850
  if (options?.grep?.trim()) {
6659
6851
  params.set("grep", options.grep.trim());
6660
- params.set("grep_mode", options.grepMode ?? "all");
6661
- params.set("grep_paginated", "1");
6662
6852
  params.set("limit", "60");
6663
6853
  }
6664
- const plays = [];
6665
- let cursor = null;
6666
- let hasMore = true;
6667
- do {
6668
- if (cursor) params.set("cursor", cursor);
6669
- const suffix = params.toString() ? `?${params.toString()}` : "";
6670
- const response = await this.http.get(
6671
- `/api/v2/plays${suffix}`
6672
- );
6673
- plays.push(...response.plays ?? []);
6674
- cursor = response.pagination?.cursor ?? response.cursor ?? null;
6675
- hasMore = Boolean(response.pagination?.hasMore ?? response.hasMore);
6676
- } while (Boolean(options?.grep?.trim()) && hasMore && cursor);
6677
- return plays;
6854
+ if (options?.limit !== void 0) {
6855
+ if (!Number.isSafeInteger(options.limit) || options.limit < 1) {
6856
+ throw new Error("Play page limit must be a positive integer");
6857
+ }
6858
+ params.set("limit", String(options.limit));
6859
+ }
6860
+ if (options?.cursor) params.set("cursor", options.cursor);
6861
+ const suffix = params.size ? `?${params.toString()}` : "";
6862
+ const response = await this.http.get(`/api/v2/plays${suffix}`);
6863
+ return {
6864
+ plays: response.plays ?? [],
6865
+ cursor: response.pagination?.cursor ?? response.cursor ?? null,
6866
+ hasMore: Boolean(response.pagination?.hasMore ?? response.hasMore)
6867
+ };
6678
6868
  }
6679
6869
  /** Set whether an org-owned Play sorts before unpinned Plays. */
6680
6870
  async setPlayPinned(playName, pinned) {
@@ -6745,6 +6935,197 @@ var DeeplineClient = class {
6745
6935
  async getNotifications() {
6746
6936
  return this.http.get("/api/v2/notifications");
6747
6937
  }
6938
+ /** Read the group-based product-notification configuration and rule catalog. */
6939
+ async getNotificationConfiguration() {
6940
+ return this.http.get("/api/v2/notifications/configuration");
6941
+ }
6942
+ /** Atomically apply one retry-safe desired configuration change. */
6943
+ async changeNotificationConfiguration(expectedWorkspaceVersion, change) {
6944
+ return this.http.put("/api/v2/notifications/configuration", {
6945
+ expectedWorkspaceVersion,
6946
+ change
6947
+ });
6948
+ }
6949
+ /** Create one notification group and its destinations as a retry-safe unit. */
6950
+ async createNotificationGroup(input2) {
6951
+ const name = input2.name.trim().replace(/\s+/g, " ");
6952
+ if (!name) throw new Error("Notification group name is required.");
6953
+ if (input2.targets.length < 1 || input2.targets.length > MAX_NOTIFICATION_GROUP_CREATE_TARGETS) {
6954
+ throw new Error(
6955
+ `Choose between 1 and ${MAX_NOTIFICATION_GROUP_CREATE_TARGETS} notification targets.`
6956
+ );
6957
+ }
6958
+ const targets = input2.targets.map(parseNotificationGroupTarget);
6959
+ const legacyEvents = input2.legacyEvents ?? [];
6960
+ const legacyTarget = targets.length === 1 && targets[0]?.kind === "slack" && legacyEvents.length > 0 ? targets[0] : null;
6961
+ if (legacyEvents.length > 0 && !legacyTarget) {
6962
+ throw new Error(
6963
+ "--for is supported only by the legacy one-Slack-target form. Create the group first, then add rules in notification configuration."
6964
+ );
6965
+ }
6966
+ if (!legacyTarget && name.toLowerCase() === "default") {
6967
+ throw new Error(
6968
+ "Default is reserved for the workspace default notification group."
6969
+ );
6970
+ }
6971
+ const normalizedTargets = targets.map(
6972
+ (target) => target.kind === "email" ? `email:${target.email}` : "memberId" in target ? `slack:member:${target.memberId}` : `slack:${target.channel}`
6973
+ );
6974
+ if (input2.dryRun) {
6975
+ return {
6976
+ mode: "dry_run",
6977
+ compatibilityMode: legacyTarget !== null,
6978
+ name: legacyTarget ? input2.name : name,
6979
+ targets: normalizedTargets,
6980
+ legacyEvents
6981
+ };
6982
+ }
6983
+ if (legacyTarget) {
6984
+ const notification = await this.createNotification({
6985
+ name: input2.name,
6986
+ provider: "slack",
6987
+ ..."channel" in legacyTarget ? { channel: legacyTarget.channel } : { memberId: legacyTarget.memberId },
6988
+ eventTypes: legacyEvents
6989
+ });
6990
+ return { mode: "legacy", notification };
6991
+ }
6992
+ const current = await this.getNotificationConfiguration();
6993
+ const existingGroup = current.configuration.groups.find(
6994
+ (candidate) => candidate.name.toLowerCase() === name.toLowerCase()
6995
+ );
6996
+ const groupKey = existingGroup?.groupKey ?? notificationObjectKey("group");
6997
+ const group = existingGroup ?? {
6998
+ groupKey,
6999
+ name,
7000
+ enabled: true,
7001
+ deliveryTiming: DEFAULT_PRODUCT_NOTIFICATION_DELIVERY_TIMING,
7002
+ source: "workspace",
7003
+ configMode: "custom",
7004
+ version: 0
7005
+ };
7006
+ const destinations = current.configuration.destinations.filter(
7007
+ (destination) => destination.groupKey === groupKey
7008
+ );
7009
+ const emails = [
7010
+ ...new Set(
7011
+ targets.flatMap(
7012
+ (target) => target.kind === "email" ? [target.email] : []
7013
+ )
7014
+ )
7015
+ ].sort();
7016
+ const existingEmails = new Set(
7017
+ destinations.flatMap(
7018
+ (destination) => destination.settings.kind === "email_addresses" ? destination.settings.to : []
7019
+ )
7020
+ );
7021
+ const newEmails = emails.filter((email) => !existingEmails.has(email));
7022
+ if (newEmails.length > 0) {
7023
+ destinations.push({
7024
+ destinationKey: notificationObjectKey("destination"),
7025
+ groupKey,
7026
+ name: "Email recipients",
7027
+ enabled: true,
7028
+ source: "workspace",
7029
+ configMode: "custom",
7030
+ version: 0,
7031
+ status: "active",
7032
+ settings: { kind: "email_addresses", to: newEmails }
7033
+ });
7034
+ }
7035
+ const slackTargets = /* @__PURE__ */ new Map();
7036
+ for (const target of targets) {
7037
+ if (target.kind !== "slack") continue;
7038
+ const targetKey = "memberId" in target ? `member:${target.memberId}` : `channel:${target.channel.replace(/^#/, "").toLowerCase()}`;
7039
+ slackTargets.set(targetKey, target);
7040
+ }
7041
+ const connectionId = current.slackConnection.connectionId;
7042
+ if (slackTargets.size > 0 && !connectionId) {
7043
+ throw new Error(
7044
+ "Slack is not connected to this workspace. Connect it in Dashboard \u2192 Integrations, then retry."
7045
+ );
7046
+ }
7047
+ const resolvedSlackTargets = await Promise.all(
7048
+ [...slackTargets.values()].map(async (target) => {
7049
+ if ("memberId" in target) {
7050
+ return {
7051
+ targetKind: "member",
7052
+ targetId: target.memberId,
7053
+ targetName: target.memberId
7054
+ };
7055
+ }
7056
+ const channel = target.channel;
7057
+ const requested = channel.replace(/^#/, "");
7058
+ const response = await this.listNotificationChannels(channel);
7059
+ const matches = response.channels.filter(
7060
+ (candidate) => candidate.id === requested || candidate.name.toLowerCase() === requested.toLowerCase()
7061
+ );
7062
+ if (matches.length !== 1) {
7063
+ throw new Error(
7064
+ matches.length === 0 ? `Slack channel ${channel} was not found or is not visible to the Deepline app.` : `Slack channel ${channel} is ambiguous. Use its channel ID.`
7065
+ );
7066
+ }
7067
+ return {
7068
+ targetKind: "channel",
7069
+ targetId: matches[0].id,
7070
+ targetName: matches[0].name
7071
+ };
7072
+ })
7073
+ );
7074
+ const uniqueSlackTargets = new Map(
7075
+ resolvedSlackTargets.map((target) => [
7076
+ `${target.targetKind}:${target.targetId}`,
7077
+ target
7078
+ ])
7079
+ );
7080
+ for (const target of uniqueSlackTargets.values()) {
7081
+ if (destinations.some(
7082
+ (destination) => destination.settings.kind === "slack" && destination.settings.targetKind === target.targetKind && destination.settings.targetId === target.targetId
7083
+ )) {
7084
+ continue;
7085
+ }
7086
+ destinations.push({
7087
+ destinationKey: notificationObjectKey("destination"),
7088
+ groupKey,
7089
+ name: `${target.targetKind === "channel" ? "#" : "@"}${target.targetName}`,
7090
+ enabled: true,
7091
+ source: "workspace",
7092
+ configMode: "custom",
7093
+ version: 0,
7094
+ status: "pending_verification",
7095
+ settings: {
7096
+ kind: "slack",
7097
+ ...target,
7098
+ oauthConnectionId: connectionId
7099
+ }
7100
+ });
7101
+ }
7102
+ const draft = {
7103
+ group,
7104
+ destinations,
7105
+ rules: current.configuration.rules.filter(
7106
+ (rule) => rule.groupKey === groupKey
7107
+ )
7108
+ };
7109
+ const result = await this.changeNotificationConfiguration(
7110
+ current.configuration.workspaceVersion,
7111
+ { kind: "save_group", draft }
7112
+ );
7113
+ const storedGroup = result.configuration.groups.find(
7114
+ (candidate) => candidate.groupKey === groupKey
7115
+ );
7116
+ if (!storedGroup) {
7117
+ throw new Error("Notification group creation returned no saved group.");
7118
+ }
7119
+ return {
7120
+ mode: "native",
7121
+ groupKey,
7122
+ targets: normalizedTargets,
7123
+ group: storedGroup,
7124
+ destinations: result.configuration.destinations.filter(
7125
+ (destination) => destination.groupKey === groupKey
7126
+ )
7127
+ };
7128
+ }
6748
7129
  /** List Slack channels available to an already-connected Slack integration. */
6749
7130
  async listNotificationChannels(query) {
6750
7131
  const suffix = query ? `?search=${encodeURIComponent(query)}` : "";
@@ -7289,6 +7670,78 @@ var DeeplineClient = class {
7289
7670
  { method: "GET" }
7290
7671
  );
7291
7672
  }
7673
+ /** Submit durable monitor work; transport retries use the same idempotency key. */
7674
+ async submitMonitorBatch(options) {
7675
+ const idempotencyKey = options.idempotencyKey.trim();
7676
+ if (!idempotencyKey) {
7677
+ throw new DeeplineError(
7678
+ "Monitor batch idempotencyKey is required.",
7679
+ void 0,
7680
+ "INVALID_MONITOR_BATCH_IDEMPOTENCY_KEY"
7681
+ );
7682
+ }
7683
+ if (options.kind === "deploy") {
7684
+ if (!options.manifestNdjson.trim()) {
7685
+ throw new DeeplineError(
7686
+ "Monitor batch deploy requires a non-empty NDJSON manifest.",
7687
+ void 0,
7688
+ "INVALID_MONITOR_BATCH_MANIFEST"
7689
+ );
7690
+ }
7691
+ return this.http.request(
7692
+ "/api/v2/monitors/batch",
7693
+ {
7694
+ method: "POST",
7695
+ body: {
7696
+ kind: "deploy",
7697
+ manifest_ndjson: options.manifestNdjson,
7698
+ dry_run: options.dryRun === true,
7699
+ idempotency_key: idempotencyKey
7700
+ },
7701
+ headers: { "Idempotency-Key": idempotencyKey },
7702
+ maxRetries: 0,
7703
+ exactUrlOnly: true
7704
+ }
7705
+ );
7706
+ }
7707
+ const scope = options.scope.kind === "keys" ? { kind: "keys", monitor_keys: options.scope.monitorKeys } : options.scope.kind === "fleet" ? { kind: "fleet", fleet_id: options.scope.fleetId } : { kind: "target", target: options.scope.target };
7708
+ return this.http.request(
7709
+ "/api/v2/monitors/batch",
7710
+ {
7711
+ method: "POST",
7712
+ body: {
7713
+ kind: options.kind,
7714
+ scope,
7715
+ dry_run: options.dryRun === true,
7716
+ idempotency_key: idempotencyKey,
7717
+ ...options.deactivationReason === void 0 ? {} : { deactivation_reason: options.deactivationReason }
7718
+ },
7719
+ headers: { "Idempotency-Key": idempotencyKey },
7720
+ // Durable submission is itself idempotent, but an ambiguous local retry
7721
+ // must retain the exact key and never turn a CLI attempt into two runs.
7722
+ maxRetries: 0,
7723
+ exactUrlOnly: true
7724
+ }
7725
+ );
7726
+ }
7727
+ /** Get durable batch progress and a bounded retained evidence page. */
7728
+ async getMonitorBatch(operationId, options) {
7729
+ const runId = operationId.trim();
7730
+ if (!runId) {
7731
+ throw new DeeplineError(
7732
+ "Monitor batch operation id is required.",
7733
+ void 0,
7734
+ "INVALID_MONITOR_BATCH_OPERATION_ID"
7735
+ );
7736
+ }
7737
+ const params = new URLSearchParams();
7738
+ if (options?.cursor) params.set("cursor", options.cursor);
7739
+ const suffix = params.size > 0 ? `?${params}` : "";
7740
+ return this.http.request(
7741
+ `/api/v2/monitors/batch/${encodeURIComponent(runId)}${suffix}`,
7742
+ { method: "GET" }
7743
+ );
7744
+ }
7292
7745
  /**
7293
7746
  * The deployable monitor tools catalog. Pass a tool id (positional or
7294
7747
  * `{ tool }`) to describe one tool's full payload/stream contract, or no tool
@@ -7761,13 +8214,6 @@ function getAuthedHttpClient() {
7761
8214
  const config = resolveConfig();
7762
8215
  return { config, http: new HttpClient(config) };
7763
8216
  }
7764
- async function writeOutputFile(filename, content) {
7765
- const outputDir = (0, import_node_path4.resolve)(process.cwd(), "deepline", "data");
7766
- await (0, import_promises.mkdir)(outputDir, { recursive: true });
7767
- const fullPath = (0, import_node_path4.join)(outputDir, filename);
7768
- await (0, import_promises.writeFile)(fullPath, content, "utf-8");
7769
- return fullPath;
7770
- }
7771
8217
  function stableOsUserId() {
7772
8218
  try {
7773
8219
  const info = (0, import_node_os4.userInfo)();
@@ -8410,13 +8856,6 @@ function createTimestampedName(prefix, extension) {
8410
8856
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8411
8857
  return `${prefix}-${timestamp}.${extension}`;
8412
8858
  }
8413
- async function writeCsvRowsFile(prefix, rows) {
8414
- const path = await writeOutputFile(
8415
- createTimestampedName(prefix, "csv"),
8416
- csvStringFromRows(rows)
8417
- );
8418
- return path;
8419
- }
8420
8859
  function clip(value, maxLength) {
8421
8860
  return value.length > maxLength ? `${value.slice(0, maxLength - 1)}\u2026` : value;
8422
8861
  }
@@ -8594,7 +9033,9 @@ function saveEnvValues(values, baseUrl, scope) {
8594
9033
  ...values[HOST_URL_ENV] ? { [HOST_URL_ENV]: values[HOST_URL_ENV] } : {},
8595
9034
  ...values[API_KEY_ENV] ? { [API_KEY_ENV]: values[API_KEY_ENV] } : {},
8596
9035
  ...values[ACTIVE_ORG_ID_ENV] ? { [ACTIVE_ORG_ID_ENV]: values[ACTIVE_ORG_ID_ENV] } : {},
8597
- ...Object.prototype.hasOwnProperty.call(values, ACTIVE_ORG_NAME_ENV) ? { [ACTIVE_ORG_NAME_ENV]: values[ACTIVE_ORG_NAME_ENV] } : {}
9036
+ ...Object.prototype.hasOwnProperty.call(values, ACTIVE_ORG_NAME_ENV) ? { [ACTIVE_ORG_NAME_ENV]: values[ACTIVE_ORG_NAME_ENV] } : {},
9037
+ // An explicit empty list must reach the store: it clears a stale line.
9038
+ ...Object.prototype.hasOwnProperty.call(values, FEATURES_ENV) ? { [FEATURES_ENV]: values[FEATURES_ENV] } : {}
8598
9039
  };
8599
9040
  if (scope === "folder") {
8600
9041
  saveProjectDeeplineEnvValues(filtered);
@@ -8608,7 +9049,8 @@ function activeOrgEnvValues(data) {
8608
9049
  const orgName = typeof data.org_name === "string" ? data.org_name.trim() : "";
8609
9050
  return {
8610
9051
  [ACTIVE_ORG_ID_ENV]: orgId,
8611
- [ACTIVE_ORG_NAME_ENV]: orgName
9052
+ [ACTIVE_ORG_NAME_ENV]: orgName,
9053
+ ...featureEnvValues(data)
8612
9054
  };
8613
9055
  }
8614
9056
  async function httpJson(method, url, apiKey, body, timeoutMs = AUTH_HTTP_TIMEOUT_MS) {
@@ -9133,6 +9575,8 @@ async function handleStatus(args) {
9133
9575
  savedApiKeyPath = envFilePath(baseUrl);
9134
9576
  }
9135
9577
  }
9578
+ } else if (resolvedAuthScope === "folder" || storedHostApiKey(baseUrl) === apiKey) {
9579
+ saveEnvValues(featureEnvValues(data), baseUrl, resolvedAuthScope);
9136
9580
  }
9137
9581
  printCommandEnvelope(
9138
9582
  {
@@ -9196,7 +9640,7 @@ Notes:
9196
9640
  workspace. To switch, use deepline org list and deepline org set <org-id>.
9197
9641
  `
9198
9642
  );
9199
- auth.command("workspaces").description("Deprecated. Use deepline org list to see workspaces.").action(() => {
9643
+ auth.command("workspaces", { hidden: true }).description("Deprecated. Use deepline org list to see workspaces.").action(() => {
9200
9644
  console.error(
9201
9645
  "Workspaces live under `deepline org`.\n List: deepline org list\n Switch: deepline org set <org-id>\n`deepline auth register` re-approves this device; it does not change the active workspace."
9202
9646
  );
@@ -9293,7 +9737,6 @@ var SUBSCRIPTION_STATUS_NEXT_COMMAND = "deepline billing subscription status --j
9293
9737
  var SUBSCRIPTION_CANCEL_PATH = "/api/v2/billing/subscription/cancel";
9294
9738
  var BILLING_TOP_UP_PATH = "/api/v2/billing/top-up";
9295
9739
  var DEFAULT_BALANCE_RECOVERY_CREDITS = 100;
9296
- var BILLING_HISTORY_EXPORT_LIMIT = 5e3;
9297
9740
  function billingFailureFromError(error, options) {
9298
9741
  if (error instanceof AuthError) {
9299
9742
  return {
@@ -9584,6 +10027,9 @@ async function handleBalance(options) {
9584
10027
  );
9585
10028
  return;
9586
10029
  }
10030
+ function quoteShellArgument(value) {
10031
+ return `'${value.replace(/'/g, `'\\''`)}'`;
10032
+ }
9587
10033
  async function handleUsage(options) {
9588
10034
  const { http } = getAuthedHttpClient();
9589
10035
  const params = new URLSearchParams();
@@ -9600,6 +10046,7 @@ async function handleUsage(options) {
9600
10046
  const recent = payload.recent ?? {};
9601
10047
  const last30Usage = usage.last_30_days_usage_credits ?? usage.month_spent_credits;
9602
10048
  const temporaryHolds = Number(usage.temporary_hold_credits ?? 0);
10049
+ const nextPage = typeof recent.next_cursor === "string" && recent.next_cursor.length > 0 ? `deepline billing usage --cursor ${quoteShellArgument(recent.next_cursor)}${options.limit ? ` --limit ${options.limit}` : ""}${options.runId ? ` --run-id ${quoteShellArgument(options.runId)}` : ""} --json` : null;
9603
10050
  const lines = [
9604
10051
  `Balance: ${payload.balance ?? "(unknown)"}`,
9605
10052
  `Last 30 days usage: ${last30Usage ?? "(unknown)"}`,
@@ -9611,13 +10058,20 @@ async function handleUsage(options) {
9611
10058
  ...recentUsageLines(
9612
10059
  Array.isArray(recent.entries) ? recent.entries : []
9613
10060
  ),
9614
- ...typeof recent.next_cursor === "string" && recent.next_cursor.length > 0 ? [
9615
- `Next page: deepline billing usage --cursor ${recent.next_cursor}${options.limit ? ` --limit ${options.limit}` : ""}${options.runId ? ` --run-id ${options.runId}` : ""}`
10061
+ ...recent.has_more === true || recent.incomplete === true ? ["List truncated \u2014 more recent calls match this query."] : [],
10062
+ ...nextPage ? [`Next page: ${nextPage}`] : [],
10063
+ ...!nextPage && (recent.has_more === true || recent.incomplete === true) ? [
10064
+ "No continuation is available for this view. Use deepline billing ledger export all --json for the full ledger."
9616
10065
  ] : []
9617
10066
  ];
9618
10067
  printCommandEnvelope(
9619
10068
  {
9620
10069
  ...payload,
10070
+ recent: {
10071
+ ...recent,
10072
+ is_truncated: recent.has_more === true || recent.incomplete === true
10073
+ },
10074
+ next: { page: nextPage },
9621
10075
  render: { sections: [{ title: "billing usage", lines }] }
9622
10076
  },
9623
10077
  { json: options.json }
@@ -9681,6 +10135,39 @@ async function handleLimitOff(options) {
9681
10135
  { json: options.json }
9682
10136
  );
9683
10137
  }
10138
+ async function exportLedger(http, outputPath, options) {
10139
+ let summary = { row_count: 0, net_delta_credits: 0 };
10140
+ let initialized = false;
10141
+ for await (const entries of iterateCursorPages(async (cursor) => {
10142
+ const params = new URLSearchParams({ limit: "5000" });
10143
+ if (cursor) params.set("cursor", cursor);
10144
+ if (options.sinceAt !== null)
10145
+ params.set("since_at", String(options.sinceAt));
10146
+ if (options.runId) params.set("run_id", options.runId);
10147
+ if (options.operation) params.set("operation", options.operation);
10148
+ const payload = await http.get(
10149
+ `/api/v2/billing/ledger?${params.toString()}`
10150
+ );
10151
+ if (!Array.isArray(payload.entries) || typeof payload.has_more !== "boolean") {
10152
+ throw new Error("Billing ledger response is missing entries");
10153
+ }
10154
+ return {
10155
+ items: payload.entries,
10156
+ hasMore: payload.has_more === true,
10157
+ cursor: typeof payload.next_cursor === "string" ? payload.next_cursor : null
10158
+ };
10159
+ })) {
10160
+ if (!initialized) {
10161
+ await (0, import_promises2.mkdir)((0, import_node_path6.dirname)(outputPath), { recursive: true });
10162
+ await (0, import_promises2.writeFile)(outputPath, ledgerRowsToCsv([], true), "utf-8");
10163
+ initialized = true;
10164
+ }
10165
+ const rows = entries.map(ledgerApiEntryToRow);
10166
+ await (0, import_promises2.appendFile)(outputPath, ledgerRowsToCsv(rows, false), "utf-8");
10167
+ summary = summarizeLedgerRows(summary, rows);
10168
+ }
10169
+ return summary;
10170
+ }
9684
10171
  async function handleHistory(options) {
9685
10172
  const { http } = getAuthedHttpClient();
9686
10173
  const windows = {
@@ -9690,41 +10177,20 @@ async function handleHistory(options) {
9690
10177
  "1y": 31536e3
9691
10178
  };
9692
10179
  const sinceAt = Math.max(0, Math.floor(Date.now() / 1e3) - windows[options.time]) * 1e3;
9693
- const entries = [];
9694
- let cursor = null;
9695
- while (entries.length < BILLING_HISTORY_EXPORT_LIMIT) {
9696
- const params = new URLSearchParams({
9697
- since_at: String(sinceAt),
9698
- limit: String(BILLING_HISTORY_EXPORT_LIMIT - entries.length)
9699
- });
9700
- if (cursor !== null) params.set("cursor", cursor);
9701
- const payload = await http.get(
9702
- `/api/v2/billing/ledger?${params.toString()}`
9703
- );
9704
- if (Array.isArray(payload.entries)) {
9705
- entries.push(
9706
- ...payload.entries.slice(
9707
- 0,
9708
- BILLING_HISTORY_EXPORT_LIMIT - entries.length
9709
- )
9710
- );
9711
- }
9712
- const nextCursor = typeof payload.next_cursor === "string" && payload.next_cursor.length > 0 ? payload.next_cursor : null;
9713
- if (payload.has_more !== true || nextCursor === null) break;
9714
- if (nextCursor === cursor) {
9715
- throw new Error("Billing history pagination cursor did not advance");
9716
- }
9717
- cursor = nextCursor;
9718
- }
9719
- const rows = entries.map(ledgerApiEntryToRow);
9720
- const outputPath = await writeCsvRowsFile(
9721
- `billing-history-${options.time}`,
9722
- rows
10180
+ const outputPath = (0, import_node_path6.resolve)(
10181
+ process.cwd(),
10182
+ "deepline",
10183
+ "data",
10184
+ createTimestampedName(`billing-history-${options.time}`, "csv")
9723
10185
  );
10186
+ const summary = await exportLedger(http, outputPath, { sinceAt });
9724
10187
  printCommandEnvelope(
9725
10188
  {
9726
10189
  output_path: outputPath,
9727
- row_count: rows.length,
10190
+ row_count: summary.row_count,
10191
+ has_more: false,
10192
+ is_truncated: false,
10193
+ next_cursor: null,
9728
10194
  time_window: options.time,
9729
10195
  render: {
9730
10196
  sections: [
@@ -9732,7 +10198,7 @@ async function handleHistory(options) {
9732
10198
  title: "billing history",
9733
10199
  lines: [
9734
10200
  `Billing history written to ${outputPath}`,
9735
- `${rows.length} row(s) exported.`
10201
+ `${summary.row_count} row(s) exported.`
9736
10202
  ]
9737
10203
  }
9738
10204
  ]
@@ -9757,40 +10223,18 @@ async function handleLedgerExportAll(options) {
9757
10223
  }
9758
10224
  const { http } = getAuthedHttpClient();
9759
10225
  const outputPath = options.output ? (0, import_node_path6.resolve)(String(options.output)) : defaultLedgerExportPath();
9760
- let summary = { row_count: 0, net_delta_credits: 0 };
9761
- let cursor = null;
9762
- let initializedOutput = false;
9763
- for (; ; ) {
9764
- const params = new URLSearchParams({ limit: "5000" });
9765
- if (cursor !== null) params.set("cursor", cursor);
9766
- if (options.runId) params.set("run_id", options.runId);
9767
- if (options.operation) params.set("operation", options.operation);
9768
- if (sinceAt !== null) params.set("since_at", String(sinceAt));
9769
- const payload = await http.get(
9770
- `/api/v2/billing/ledger?${params.toString()}`
9771
- );
9772
- const entries = Array.isArray(payload.entries) ? payload.entries : [];
9773
- const rows = entries.map(ledgerApiEntryToRow);
9774
- if (!initializedOutput) {
9775
- await (0, import_promises2.mkdir)((0, import_node_path6.dirname)(outputPath), { recursive: true });
9776
- await (0, import_promises2.writeFile)(outputPath, ledgerRowsToCsv([], true), "utf-8");
9777
- initializedOutput = true;
9778
- }
9779
- if (rows.length > 0) {
9780
- await (0, import_promises2.appendFile)(outputPath, ledgerRowsToCsv(rows, false), "utf-8");
9781
- summary = summarizeLedgerRows(summary, rows);
9782
- }
9783
- const nextCursor = typeof payload.next_cursor === "string" && payload.next_cursor.length > 0 ? payload.next_cursor : null;
9784
- if (payload.has_more !== true || nextCursor === null) {
9785
- break;
9786
- }
9787
- if (nextCursor === cursor) break;
9788
- cursor = nextCursor;
9789
- }
10226
+ const summary = await exportLedger(http, outputPath, {
10227
+ sinceAt,
10228
+ runId: options.runId,
10229
+ operation: options.operation
10230
+ });
9790
10231
  printCommandEnvelope(
9791
10232
  {
9792
10233
  output_path: outputPath,
9793
10234
  row_count: summary.row_count,
10235
+ has_more: false,
10236
+ is_truncated: false,
10237
+ next_cursor: null,
9794
10238
  net_delta_credits: summary.net_delta_credits,
9795
10239
  scope: "workspace",
9796
10240
  ...options.runId ? { run_id: options.runId } : {},
@@ -10337,12 +10781,12 @@ async function handleBuyCredits(creditsRaw, options) {
10337
10781
  );
10338
10782
  }
10339
10783
  async function handleTargetPlan(planSku, options) {
10340
- if (planSku !== "payg-v1" && planSku !== "builder-v1" && planSku !== "team-v1") {
10784
+ if (planSku !== "payg-v1" && planSku !== "builder-v1" && planSku !== "growth-v1" && planSku !== "growth-canary-v1" && planSku !== "team-v1") {
10341
10785
  reportBillingFailure(
10342
10786
  {
10343
10787
  exitCode: 2,
10344
10788
  code: "INVALID_PLAN",
10345
- message: "Plan must be payg-v1, builder-v1, or team-v1."
10789
+ message: "Plan must be payg-v1, builder-v1, growth-v1, growth-canary-v1, or team-v1."
10346
10790
  },
10347
10791
  options
10348
10792
  );
@@ -10535,7 +10979,9 @@ Examples:
10535
10979
  `
10536
10980
  Notes:
10537
10981
  Read-only server query that writes a local CSV file. With --json, stdout is a
10538
- summary containing the output path and row count.
10982
+ summary containing the output path, row count, and completeness metadata.
10983
+ Exports the complete window using bounded pages. For an exact start timestamp:
10984
+ deepline billing ledger export all --since <ISO-8601> --json
10539
10985
 
10540
10986
  Examples:
10541
10987
  deepline billing history --time 1m
@@ -10603,7 +11049,10 @@ Examples:
10603
11049
  "Disable automatic recharge without clearing saved amounts."
10604
11050
  ).option("--idempotency-key <key>", "Stable retry key").option("--dry-run", "Print the planned update without applying it").option("--json", "Emit JSON output").action(handleAutoRechargeOff)
10605
11051
  );
10606
- billing.command("change-plan").description("Start or change the target billing plan.").argument("<plan_sku>", "payg-v1, builder-v1, or team-v1").option("--idempotency-key <key>", "Stable retry key").option("--json", "Emit JSON output").action(handleTargetPlan);
11052
+ billing.command("change-plan").description("Start or change the target billing plan.").argument(
11053
+ "<plan_sku>",
11054
+ "payg-v1, builder-v1, growth-v1, growth-canary-v1, or team-v1"
11055
+ ).option("--idempotency-key <key>", "Stable retry key").option("--json", "Emit JSON output").action(handleTargetPlan);
10607
11056
  billing.command("cancel-plan").description("Cancel a target subscription at period end, or undo it.").option("--undo", "Undo a pending period-end cancellation").option("--idempotency-key <key>", "Stable retry key").option("--json", "Emit JSON output").action(handleTargetPlanCancellation);
10608
11057
  billing.command("portal").description("Open the Stripe-hosted billing recovery portal.").option("--no-open", "Print the URL without opening a browser").option("--json", "Emit JSON output").action(handleTargetPortal);
10609
11058
  billing.command("plans").description("Show published plans and the plan you are on.").addHelpText(
@@ -10889,6 +11338,12 @@ function mergeCanonicalRowsInfo(left, right) {
10889
11338
  const exportUnavailableReason = left.exportUnavailableReason === "shared_table_namespace" || right.exportUnavailableReason === "shared_table_namespace" ? "shared_table_namespace" : left.exportUnavailableReason ?? right.exportUnavailableReason;
10890
11339
  const unavailableSource = left.exportUnavailableReason === exportUnavailableReason ? left : right.exportUnavailableReason === exportUnavailableReason ? right : null;
10891
11340
  const columns = preferred.columns.length > 0 ? preferred.columns : fallback.columns;
11341
+ const recoveredReturnPaths2 = [
11342
+ .../* @__PURE__ */ new Set([
11343
+ ...left.recoveredReturnPaths ?? [],
11344
+ ...right.recoveredReturnPaths ?? []
11345
+ ])
11346
+ ];
10892
11347
  return {
10893
11348
  ...fallback,
10894
11349
  ...preferred,
@@ -10901,6 +11356,7 @@ function mergeCanonicalRowsInfo(left, right) {
10901
11356
  datasetId: preferred.datasetId ?? fallback.datasetId,
10902
11357
  tableNamespace: preferred.tableNamespace ?? fallback.tableNamespace,
10903
11358
  ...left.recovered || right.recovered ? { recovered: true } : {},
11359
+ ...recoveredReturnPaths2.length ? { recoveredReturnPaths: recoveredReturnPaths2 } : {},
10904
11360
  ...exportUnavailableReason ? {
10905
11361
  exportUnavailableReason,
10906
11362
  ...unavailableSource?.exportUnavailableMessage ? {
@@ -10961,6 +11417,16 @@ function isSerializedDataset(value) {
10961
11417
  function isPackagedDatasetOutput(value) {
10962
11418
  return isRecord8(value) && value.kind === "dataset" && isRecord8(value.preview) && Array.isArray(value.preview.rows);
10963
11419
  }
11420
+ function recoveredReturnPaths(value) {
11421
+ if (!Array.isArray(value)) return [];
11422
+ return [
11423
+ ...new Set(
11424
+ value.filter(
11425
+ (path) => typeof path === "string" && /^result\.[A-Za-z_$][A-Za-z0-9_$]*$/.test(path)
11426
+ ).slice(0, 200)
11427
+ )
11428
+ ];
11429
+ }
10964
11430
  function pathParts(path) {
10965
11431
  return path.split(".").map((part) => part.trim()).filter(Boolean);
10966
11432
  }
@@ -11057,6 +11523,11 @@ function canonicalRowsInfoFromCandidate(input2) {
11057
11523
  datasetId: typeof candidate.value.datasetId === "string" ? candidate.value.datasetId : null,
11058
11524
  tableNamespace: typeof candidate.value.tableNamespace === "string" ? candidate.value.tableNamespace : null,
11059
11525
  ...candidate.value.recovered === true ? { recovered: true } : {},
11526
+ ...recoveredReturnPaths(candidate.value.recoveredReturnPaths).length ? {
11527
+ recoveredReturnPaths: recoveredReturnPaths(
11528
+ candidate.value.recoveredReturnPaths
11529
+ )
11530
+ } : {},
11060
11531
  ...isRecord8(candidate.value.exportUnavailable) && (candidate.value.exportUnavailable.reason === "empty_dataset" || candidate.value.exportUnavailable.reason === "shared_table_namespace") ? {
11061
11532
  exportUnavailableReason: candidate.value.exportUnavailable.reason,
11062
11533
  ...typeof candidate.value.exportUnavailable.message === "string" ? {
@@ -15162,6 +15633,82 @@ var PLAY_AUTHORING_FIELD_REGISTRY = {
15162
15633
  description: "Unsupported legacy child-workflow timeout.",
15163
15634
  errorMessage: "ctx.runPlay timeoutMs is unsupported because child Plays execute inline."
15164
15635
  },
15636
+ "ctx.runPlayAsync.key": {
15637
+ schema: import_typebox.Type.String({ minLength: 1 }),
15638
+ fixtures: {
15639
+ valid: "request-approval",
15640
+ invalid: "",
15641
+ absent: void 0,
15642
+ unresolved: { expression: "callKey" },
15643
+ edition1: "request-approval"
15644
+ },
15645
+ referenceType: "string",
15646
+ required: true,
15647
+ resolution: "runtime-dynamic",
15648
+ issueCode: "play_authoring_run_play_option_invalid",
15649
+ description: "Stable identity for one independent child Play Run.",
15650
+ errorMessage: "ctx.runPlayAsync key must be a non-empty string."
15651
+ },
15652
+ "ctx.runPlayAsync.playRef": {
15653
+ schema: import_typebox.Type.Union([
15654
+ import_typebox.Type.String({ minLength: 1 }),
15655
+ import_typebox.Type.Union([
15656
+ import_typebox.Type.Object(
15657
+ { playName: import_typebox.Type.String({ minLength: 1 }) },
15658
+ { additionalProperties: true }
15659
+ ),
15660
+ import_typebox.Type.Object(
15661
+ { name: import_typebox.Type.String({ minLength: 1 }) },
15662
+ { additionalProperties: true }
15663
+ )
15664
+ ])
15665
+ ]),
15666
+ fixtures: {
15667
+ valid: "request-approval",
15668
+ invalid: {},
15669
+ absent: void 0,
15670
+ unresolved: { expression: "playRef" },
15671
+ edition1: "request-approval"
15672
+ },
15673
+ referenceType: "string | PlayReferenceLike",
15674
+ required: true,
15675
+ resolution: "runtime-dynamic",
15676
+ issueCode: "play_authoring_run_play_option_invalid",
15677
+ description: "Independently published scalar child Play name or strongly typed definition handle.",
15678
+ errorMessage: "ctx.runPlayAsync playRef must be a non-empty Play name or definition handle."
15679
+ },
15680
+ "ctx.runPlayAsync.input": {
15681
+ schema: import_typebox.Type.Record(import_typebox.Type.String(), import_typebox.Type.Unknown()),
15682
+ fixtures: {
15683
+ valid: { draftId: "draft-1" },
15684
+ invalid: "draft-1",
15685
+ absent: void 0,
15686
+ unresolved: "childInput",
15687
+ edition1: {}
15688
+ },
15689
+ referenceType: "Record<string, unknown>",
15690
+ required: true,
15691
+ resolution: "runtime-dynamic",
15692
+ issueCode: "play_authoring_run_play_option_invalid",
15693
+ description: "Scalar input object submitted to the independent child Run.",
15694
+ errorMessage: "ctx.runPlayAsync input must be an object."
15695
+ },
15696
+ "ctx.runPlayAsync.options.description": {
15697
+ schema: import_typebox.Type.String({ minLength: 1 }),
15698
+ fixtures: {
15699
+ valid: "Request approval.",
15700
+ invalid: "",
15701
+ absent: void 0,
15702
+ unresolved: { expression: "description" },
15703
+ edition1: "Request approval."
15704
+ },
15705
+ referenceType: "string",
15706
+ required: true,
15707
+ resolution: "runtime-dynamic",
15708
+ issueCode: "play_authoring_run_play_option_invalid",
15709
+ description: "Non-empty purpose for one independent child Play Run.",
15710
+ errorMessage: "ctx.runPlayAsync options.description must be non-empty."
15711
+ },
15165
15712
  "runtime.timeout": {
15166
15713
  schema: import_typebox.Type.String({ pattern: "^\\d+\\s*[mh]$" }),
15167
15714
  fixtures: {
@@ -15449,6 +15996,10 @@ var PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
15449
15996
  `export type CsvOptions = { description?: ${cloudReferenceType("ctx.csv.options.description")}; columns?: ${cloudReferenceType("ctx.csv.options.columns")}; rename?: ${cloudReferenceType("ctx.csv.options.rename")}; required?: ${cloudReferenceType("ctx.csv.options.required")} };`,
15450
15997
  "export type PlayCallExecution = 'inline';",
15451
15998
  `export type PlayCallOptions = { description: ${cloudReferenceType("ctx.runPlay.options.description")}; execution?: ${cloudReferenceType("ctx.runPlay.options.execution")}; timeoutMs?: ${cloudReferenceType("ctx.runPlay.options.timeoutMs")} };`,
15999
+ `export type PlayAsyncCallOptions = { description: ${cloudReferenceType("ctx.runPlayAsync.options.description")} };`,
16000
+ "declare const PLAY_RUN_ID_BRAND: unique symbol;",
16001
+ "export type PlayRunId = string & { readonly [PLAY_RUN_ID_BRAND]: 'PlayRunId' };",
16002
+ "export type AsyncPlayRunRef = Readonly<{ runId: PlayRunId }>;",
15452
16003
  `export type RuntimeStepOptions = { semanticKey?: ${cloudReferenceType("ctx.step.semanticKey")}; staleAfterSeconds?: ${cloudReferenceType("ctx.step.staleAfterSeconds")} };`,
15453
16004
  `export type FetchOptions = { staleAfterSeconds?: ${cloudReferenceType("ctx.fetch.staleAfterSeconds")}; transient?: boolean };`,
15454
16005
  "export type PlayFetchResponse = { ok: boolean; status: number; statusText: string; url: string; headers: Record<string, string>; bodyText: string; json: unknown | null };",
@@ -15474,11 +16025,11 @@ var PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
15474
16025
  " withColumn<Name extends string, Value>(name: Name, definition: DatasetColumnDefinition<OutputRow, Value> & { readonly runIf: (row: OutputRow, index: number) => boolean | Promise<boolean> }): DatasetBuilder<InputRow, OutputRow & Record<Name, Value | null>>;",
15475
16026
  " withColumn<Name extends string, Value>(name: Name, definition: DatasetColumnDefinition<OutputRow, Value>): DatasetBuilder<InputRow, OutputRow & Record<Name, Value>>;",
15476
16027
  " withColumn<Name extends string, Value>(name: Name, resolver: StepResolver<OutputRow, Value> | RunnableColumnStepProgram<Value>, options: StepOptions<OutputRow, Value>): DatasetBuilder<InputRow, OutputRow & Record<Name, Value | null>>;",
15477
- " withColumns<Program extends StepProgram<OutputRow, object, unknown>>(program: Program): DatasetBuilder<InputRow, StepProgramOutput<Program>>;",
16028
+ " withColumns<ProgramOutput extends OutputRow, Return>(program: StepProgram<OutputRow, ProgramOutput, Return>): DatasetBuilder<InputRow, ProgramOutput>;",
15478
16029
  " step<Name extends string, Value>(name: Name, resolver: ColumnResolver<OutputRow, Value>): never;",
15479
16030
  " run(options?: DatasetRunOptions<InputRow>): Promise<PlayDataset<OutputRow>>;",
15480
16031
  "};",
15481
- "export type PlayReferenceLike = { readonly playName: string; readonly name?: string } | { readonly name: string; readonly playName?: string };",
16032
+ "export type PlayReferenceLike<TInput extends Record<string, unknown> = Record<string, unknown>> = ({ readonly playName: string; readonly name?: string } | { readonly name: string; readonly playName?: string }) & { readonly __inputType?: TInput };",
15482
16033
  "export interface DeeplinePlayRuntimeContext {",
15483
16034
  " csv<T = Record<string, unknown>>(path: string | CsvInput<T & object>, options?: CsvOptions): Promise<PlayDataset<T>>;",
15484
16035
  " dataset<TSource extends PlayDatasetInput<object>>(key: string, items: TSource): DatasetBuilder<PlayDatasetRow<TSource> & object, PlayDatasetRow<TSource> & object>;",
@@ -15492,6 +16043,8 @@ var PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
15492
16043
  " fetch(key: string, url: string | URL, init?: RequestInit & { headers?: HeadersInit & { 'Idempotency-Key'?: string; 'X-Idempotency-Key'?: string }; auth?: SecretAuthInput }, options?: FetchOptions): Promise<PlayFetchResponse>;",
15493
16044
  " secrets: { get(name: string): SecretPromise; bearer(secret: string | SecretPromise | SecretHandle): SecretAuth; header(header: string, secret: string | SecretPromise | SecretHandle): SecretAuth };",
15494
16045
  ` runPlay<TOutput = unknown>(key: string, playRef: ${cloudReferenceType("ctx.runPlay.playRef")}, input: ${cloudReferenceType("ctx.runPlay.input")}, options: PlayCallOptions): Promise<TOutput>;`,
16046
+ " runPlayAsync<TInput extends Record<string, unknown>>(key: string, playRef: PlayReferenceLike<TInput>, input: NoInfer<TInput>, options: PlayAsyncCallOptions): Promise<AsyncPlayRunRef>;",
16047
+ " runPlayAsync(key: string, playRef: string, input: Record<string, unknown>, options: PlayAsyncCallOptions): Promise<AsyncPlayRunRef>;",
15495
16048
  " log(message: string, options?: { level?: 'debug' | 'info' | 'warn' | 'error' }): void;",
15496
16049
  ` sleep(ms: ${cloudReferenceType("ctx.sleep.ms")}): Promise<void>;`,
15497
16050
  "}",
@@ -15766,7 +16319,7 @@ function collectInlineSecretFindings(sourceCode) {
15766
16319
  var MAX_PLAY_BUNDLE_BYTES = 30 * 1024 * 1024;
15767
16320
 
15768
16321
  // ../plays/bundling/index.ts
15769
- var PLAY_BUNDLE_CACHE_VERSION = 36;
16322
+ var PLAY_BUNDLE_CACHE_VERSION = 37;
15770
16323
  var PLAY_ARTIFACT_CACHE_DIR = (0, import_node_path11.join)(
15771
16324
  (0, import_node_os7.tmpdir)(),
15772
16325
  `deepline-play-artifacts-v${PLAY_BUNDLE_CACHE_VERSION}`
@@ -16498,7 +17051,7 @@ var RunDataset = class _RunDataset {
16498
17051
  return this.#info.exportUnavailableMessage;
16499
17052
  }
16500
17053
  matches(path) {
16501
- return this.source === path || this.datasetId === path || this.tableNamespace === path;
17054
+ return this.source === path || this.datasetId === path || this.tableNamespace === path || this.#info.recoveredReturnPaths?.includes(path) === true;
16502
17055
  }
16503
17056
  /** Return a new Handle when its preview has been replaced by fetched rows. */
16504
17057
  withRows(info) {
@@ -17008,7 +17561,7 @@ function resolveDaytonaSandboxComputeItem(input2) {
17008
17561
  };
17009
17562
  }
17010
17563
 
17011
- // ../../shared_libs/billing/compute-pricing.ts
17564
+ // ../billing/compute-pricing-contract.ts
17012
17565
  var COMPUTE_BILLING_MARKUP_MULTIPLIER = 5;
17013
17566
  var DEFAULT_USD_PER_CUSTOMER_CREDIT = 0.1;
17014
17567
  var DEFAULT_PROVIDER_MARKUP_MULTIPLIER = 1.4;
@@ -17343,6 +17896,39 @@ var PLAY_RUN_RESERVED_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
17343
17896
  "--open",
17344
17897
  "--debug-map-latency"
17345
17898
  ]);
17899
+ var PLAY_RUN_HEADER_FLAG_USAGE = `--header requires "<name>: <value>", for example: --header 'x-deepline-absurd-release: 2026-09-07.1'`;
17900
+ var HTTP_HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9a-z]+$/;
17901
+ function parsePlayRunHeaderFlag(raw) {
17902
+ const separator = raw.indexOf(":");
17903
+ if (separator < 0) {
17904
+ throw new Error(
17905
+ `${PLAY_RUN_HEADER_FLAG_USAGE}. Got ${JSON.stringify(raw)} with no ":" separator.`
17906
+ );
17907
+ }
17908
+ const name = raw.slice(0, separator).trim().toLowerCase();
17909
+ const value = raw.slice(separator + 1).trim();
17910
+ if (!name) {
17911
+ throw new Error(
17912
+ `${PLAY_RUN_HEADER_FLAG_USAGE}. Got ${JSON.stringify(raw)} with an empty header name.`
17913
+ );
17914
+ }
17915
+ if (!HTTP_HEADER_NAME_PATTERN.test(name)) {
17916
+ throw new Error(
17917
+ `${PLAY_RUN_HEADER_FLAG_USAGE}. ${JSON.stringify(name)} is not a valid HTTP header name.`
17918
+ );
17919
+ }
17920
+ if (!value) {
17921
+ throw new Error(
17922
+ `${PLAY_RUN_HEADER_FLAG_USAGE}. Header ${JSON.stringify(name)} was given an empty value.`
17923
+ );
17924
+ }
17925
+ if (/[\u0000-\u001f\u007f]/.test(value)) {
17926
+ throw new Error(
17927
+ `${PLAY_RUN_HEADER_FLAG_USAGE}. Header ${JSON.stringify(name)} contains a control character.`
17928
+ );
17929
+ }
17930
+ return { name, value };
17931
+ }
17346
17932
  function isBarePlayRunDebugFlag(args, index) {
17347
17933
  return args[index] === "--debug" && (args[index + 1] === void 0 || args[index + 1].startsWith("--"));
17348
17934
  }
@@ -18956,6 +19542,9 @@ function assertPlayWaitNotTimedOut(input2) {
18956
19542
  }
18957
19543
  }
18958
19544
  var PLAY_DURABLE_STATUS_PROBE_INTERVAL_MS = 15e3;
19545
+ function isAdmittedRunVisibilityDelay(error) {
19546
+ return error instanceof DeeplineError && error.statusCode === 404 && error.message.trim() === "Workflow run not found.";
19547
+ }
18959
19548
  async function waitForPlayCompletionByStream(input2) {
18960
19549
  let lastPhase = null;
18961
19550
  let reconnectAttempt = 0;
@@ -18969,7 +19558,7 @@ async function waitForPlayCompletionByStream(input2) {
18969
19558
  billing: false
18970
19559
  });
18971
19560
  } catch (error) {
18972
- if (isTransientPlayStreamError(error)) {
19561
+ if (isTransientPlayStreamError(error) || isAdmittedRunVisibilityDelay(error)) {
18973
19562
  return null;
18974
19563
  }
18975
19564
  throw error;
@@ -19080,7 +19669,7 @@ async function waitForPlayCompletionByStream(input2) {
19080
19669
  if (timedOut) {
19081
19670
  return { kind: "timed_out", sawEvent };
19082
19671
  }
19083
- if (!isTransientPlayStreamError(error)) {
19672
+ if (!isTransientPlayStreamError(error) && !isAdmittedRunVisibilityDelay(error)) {
19084
19673
  throw error;
19085
19674
  }
19086
19675
  return {
@@ -19248,6 +19837,7 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
19248
19837
  runId: workflowId,
19249
19838
  playName: input2.playName,
19250
19839
  dashboardUrl,
19840
+ inputFieldCount: Object.keys(input2.request.input ?? {}).length,
19251
19841
  jsonOutput: false,
19252
19842
  progress: input2.progress
19253
19843
  });
@@ -21316,7 +21906,7 @@ function writeStartedPlayRun(input2) {
21316
21906
  );
21317
21907
  }
21318
21908
  function parsePlayRunOptions(args) {
21319
- const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--run-id-file <path>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--open] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--run-id-file <path>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--open] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--profile <id>] [--run-id-file <path>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--open] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--profile <id>] [--live|--latest|--revision-id <id>] [--run-id-file <path>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--open] [--json] [--full] [--<input> value]\n Runs use the Absurd CJS runtime.\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotent call caching and ctx.dataset guidance.";
21909
+ const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--run-id-file <path>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--open] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--run-id-file <path>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--open] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--profile <id>] [--run-id-file <path>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--open] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--profile <id>] [--live|--latest|--revision-id <id>] [--run-id-file <path>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--open] [--json] [--full] [--<input> value]\n Runs use the Absurd CJS runtime.\n --header '<name>: <value>' may repeat; each is sent on every request for this run.\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotent call caching and ctx.dataset guidance.";
21320
21910
  let filePath = null;
21321
21911
  let playName = null;
21322
21912
  let input2 = null;
@@ -21337,8 +21927,20 @@ function parsePlayRunOptions(args) {
21337
21927
  let profile = null;
21338
21928
  let fixtureBehavior = null;
21339
21929
  let runIdFile = null;
21930
+ let headers = null;
21340
21931
  for (let index = 0; index < args.length; index += 1) {
21341
21932
  const arg = args[index];
21933
+ if (arg === "--header" || arg.startsWith("--header=")) {
21934
+ const raw = arg === "--header" ? args[index + 1] : arg.slice("--header=".length);
21935
+ if (raw === void 0 || arg === "--header" && raw.startsWith("--")) {
21936
+ throw new Error(`${PLAY_RUN_HEADER_FLAG_USAGE}. No value was given.`);
21937
+ }
21938
+ const header = parsePlayRunHeaderFlag(raw);
21939
+ headers ??= {};
21940
+ headers[header.name] = header.value;
21941
+ if (arg === "--header") index += 1;
21942
+ continue;
21943
+ }
21342
21944
  if (arg === "--file" && args[index + 1]) {
21343
21945
  filePath = args[++index];
21344
21946
  continue;
@@ -21577,7 +22179,8 @@ function parsePlayRunOptions(args) {
21577
22179
  profile,
21578
22180
  debugMapLatency,
21579
22181
  fixtureBehavior,
21580
- runIdFile
22182
+ runIdFile,
22183
+ headers
21581
22184
  };
21582
22185
  }
21583
22186
  function parsePlayCheckOptions(args) {
@@ -22333,11 +22936,14 @@ function printPlayCheckOutcomes(outcomes, target) {
22333
22936
  printPlayCheckOutcome(outcome, target, `[${outcome.exportName}] `);
22334
22937
  }
22335
22938
  }
22939
+ function playRunClientOptions(options) {
22940
+ return options.headers ? { headers: options.headers } : void 0;
22941
+ }
22336
22942
  async function handleFileBackedRun(options, hooks) {
22337
22943
  if (options.target.kind !== "file") {
22338
22944
  throw new Error("Expected a file-backed play run target.");
22339
22945
  }
22340
- const client2 = new DeeplineClient();
22946
+ const client2 = new DeeplineClient(playRunClientOptions(options));
22341
22947
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
22342
22948
  const absolutePlayPath = (0, import_node_path14.resolve)(options.target.path);
22343
22949
  progress.phase("compiling play");
@@ -22545,7 +23151,7 @@ async function handleNamedRun(options, hooks) {
22545
23151
  if (options.target.kind !== "name") {
22546
23152
  throw new Error("Expected a named play run target.");
22547
23153
  }
22548
- const client2 = new DeeplineClient();
23154
+ const client2 = new DeeplineClient(playRunClientOptions(options));
22549
23155
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
22550
23156
  const playName = options.target.name;
22551
23157
  const runtimeInput = options.input ? { ...options.input } : {};
@@ -22875,12 +23481,13 @@ async function handleRunsList(args) {
22875
23481
  return 1;
22876
23482
  }
22877
23483
  const client2 = new DeeplineClient();
22878
- const runs = (await client2.runs.list({
23484
+ const page = await client2.runs.listPage({
22879
23485
  ...playName ? { play: playName } : {},
22880
23486
  ...statusFilter ? { status: statusFilter } : {},
22881
23487
  ...limit !== void 0 ? { limit } : {},
22882
23488
  ...offset !== void 0 ? { offset } : {}
22883
- })).map((run) => ({
23489
+ });
23490
+ const runs = page.runs.map((run) => ({
22884
23491
  runId: run.workflowId,
22885
23492
  // `workflowId` is the legacy SDK name for the same public Deepline run
22886
23493
  // identity. Keep it during the compatibility window, but never label the
@@ -22918,11 +23525,24 @@ async function handleRunsList(args) {
22918
23525
  if (run.rootRunId) lines2.push(` root: ${run.rootRunId}`);
22919
23526
  return lines2.join("\n");
22920
23527
  });
23528
+ const nextOffset = (page.offset ?? offset ?? 0) + runs.length;
23529
+ const nextPage = page.hasMore && playName ? `deepline runs list --play ${shellQuote2(playName)}${statusFilter ? ` --status ${shellQuote2(statusFilter)}` : ""} --limit ${page.limit ?? limit ?? 20} --offset ${nextOffset} --json` : null;
23530
+ if (page.hasMore) {
23531
+ lines.push("List truncated \u2014 more runs match this filter.");
23532
+ lines.push(
23533
+ nextPage ? `Next page: ${nextPage}` : "Narrow with --play <play-name> to page through runs."
23534
+ );
23535
+ }
22921
23536
  printCommandEnvelope(
22922
23537
  {
22923
23538
  runs,
22924
23539
  count: runs.length,
23540
+ ...page.total !== void 0 ? { total: page.total } : {},
23541
+ offset: page.offset ?? offset ?? 0,
23542
+ limit: page.limit ?? limit ?? (playName ? 20 : 100),
23543
+ ...page.hasMore !== void 0 ? { has_more: page.hasMore, is_truncated: page.hasMore } : {},
22925
23544
  next: {
23545
+ page: nextPage,
22926
23546
  get: runs[0]?.runId ? `deepline runs get ${runs[0].runId} --json` : null
22927
23547
  },
22928
23548
  render: { sections: [{ title: "runs", lines }] }
@@ -23570,23 +24190,47 @@ async function handlePlayVersions(args) {
23570
24190
  }
23571
24191
  return 0;
23572
24192
  }
24193
+ function formatPlayCostEstimate(estimate) {
24194
+ if (!estimate || estimate.status === "unavailable") {
24195
+ return `unavailable${estimate ? `: ${estimate.message}` : ""}`;
24196
+ }
24197
+ const formatCredits = (value) => value.toLocaleString("en-US", {
24198
+ maximumFractionDigits: value < 1 ? 4 : 2
24199
+ });
24200
+ const credits = estimate.lowCredits === estimate.highCredits ? `${estimate.source === "observed" ? "~" : ""}${formatCredits(estimate.lowCredits)}` : `${formatCredits(estimate.lowCredits)}\u2013${formatCredits(estimate.highCredits)}`;
24201
+ const unit = estimate.unit === "row" ? "output row" : "run";
24202
+ const source = estimate.source === "observed" ? `observed from ${estimate.sampleRunCount} ${estimate.sampleRunCount === 1 ? "run" : "runs"}` : "static estimate";
24203
+ return `${credits} Deepline credits per ${unit} (${source})`;
24204
+ }
23573
24205
  async function handlePlayList(args) {
23574
24206
  const jsonOutput = argsWantJson(args);
24207
+ const showCost = args.includes("--show-cost");
23575
24208
  const client2 = new DeeplineClient();
23576
24209
  const categoriesIndex = args.indexOf("--categories");
23577
24210
  const categories = categoriesIndex >= 0 ? args[categoriesIndex + 1]?.trim() : void 0;
23578
- const plays = await client2.listPlays({
24211
+ const limitIndex = args.indexOf("--limit");
24212
+ const cursorIndex = args.indexOf("--cursor");
24213
+ const limit = limitIndex >= 0 ? Number(args[limitIndex + 1]) : void 0;
24214
+ const cursor = cursorIndex >= 0 ? args[cursorIndex + 1] : void 0;
24215
+ if (limitIndex >= 0 && (!Number.isSafeInteger(limit) || limit < 1) || cursorIndex >= 0 && !cursor?.trim()) {
24216
+ console.error("Pass a positive --limit and a non-empty --cursor.");
24217
+ return 2;
24218
+ }
24219
+ const page = await client2.listPlaysPage({
23579
24220
  categories,
23580
- includeToolCategories: true
23581
- });
23582
- if (jsonOutput) {
23583
- process.stdout.write(`${JSON.stringify(plays)}
23584
- `);
23585
- return 0;
24221
+ includeToolCategories: true,
24222
+ ...showCost ? { includeCostEstimates: true } : {},
24223
+ limit,
24224
+ cursor
24225
+ });
24226
+ const plays = page.plays;
24227
+ const nextPage = page.hasMore && page.cursor ? `deepline plays list${categories ? ` --categories ${shellQuote2(categories)}` : ""}${limit ? ` --limit ${limit}` : ""}${showCost ? " --show-cost" : ""} --cursor ${shellQuote2(page.cursor)} --json` : null;
24228
+ if (page.hasMore && !nextPage) {
24229
+ throw new Error(
24230
+ "Play list reported more results without a continuation cursor"
24231
+ );
23586
24232
  }
23587
- process.stdout.write(`${plays.length} plays available:
23588
-
23589
- `);
24233
+ const lines = [`${plays.length} plays in this page:`];
23590
24234
  for (const play of plays) {
23591
24235
  const flags = [
23592
24236
  play.pinned ? "pinned" : null,
@@ -23596,32 +24240,48 @@ async function handlePlayList(args) {
23596
24240
  play.isDraftDirty ? "draft-dirty" : null
23597
24241
  ].filter(Boolean).join(", ");
23598
24242
  const reference = formatPlayListReference(play);
23599
- process.stdout.write(` ${reference}${flags ? ` [${flags}]` : ""}
23600
- `);
24243
+ lines.push(` ${reference}${flags ? ` [${flags}]` : ""}`);
23601
24244
  if (play.inputSchema || play.hasInputSchema) {
23602
- process.stdout.write(" inputSchema: yes\n");
24245
+ lines.push(" inputSchema: yes");
23603
24246
  }
23604
24247
  if (play.triggerStatus?.cron) {
23605
- process.stdout.write(
23606
- ` scheduled execution: ${cronExecutionLabel(play.triggerStatus.cron)}
23607
- `
24248
+ lines.push(
24249
+ ` scheduled execution: ${cronExecutionLabel(play.triggerStatus.cron)}`
23608
24250
  );
23609
24251
  if (play.triggerStatus.blockedReason) {
23610
- process.stdout.write(
23611
- ` schedule issue: ${play.triggerStatus.blockedReason}
23612
- `
23613
- );
24252
+ lines.push(` schedule issue: ${play.triggerStatus.blockedReason}`);
23614
24253
  }
23615
24254
  } else {
23616
- process.stdout.write(" scheduled execution: not configured\n");
24255
+ lines.push(" scheduled execution: not configured");
23617
24256
  }
23618
24257
  if (play.triggerStatus?.webhook) {
23619
- process.stdout.write(` webhook: ${play.triggerStatus.webhook}
23620
- `);
24258
+ lines.push(` webhook: ${play.triggerStatus.webhook}`);
23621
24259
  }
23622
- process.stdout.write(` run: deepline plays run ${reference} --watch
23623
- `);
24260
+ if (showCost) {
24261
+ lines.push(
24262
+ ` cost estimate: ${formatPlayCostEstimate(play.costEstimate)}`
24263
+ );
24264
+ }
24265
+ lines.push(` run: deepline plays run ${reference} --watch`);
24266
+ }
24267
+ if (page.hasMore) {
24268
+ lines.push("List truncated \u2014 more plays are available.");
24269
+ lines.push(`Next page: ${nextPage}`);
23624
24270
  }
24271
+ printCommandEnvelope(
24272
+ {
24273
+ plays,
24274
+ count: plays.length,
24275
+ has_more: page.hasMore,
24276
+ is_truncated: page.hasMore,
24277
+ next_cursor: page.cursor,
24278
+ next: { page: nextPage },
24279
+ render: {
24280
+ sections: [{ title: "plays", lines }]
24281
+ }
24282
+ },
24283
+ { json: jsonOutput }
24284
+ );
23625
24285
  return 0;
23626
24286
  }
23627
24287
  async function handlePlayPin(args, pinned) {
@@ -23885,76 +24545,23 @@ async function handlePlaySearch(args) {
23885
24545
  }
23886
24546
  return 0;
23887
24547
  }
23888
- function normalizePlayGrepText(value) {
23889
- if (value === null || value === void 0) return "";
23890
- if (typeof value === "string") return value.toLowerCase();
23891
- if (typeof value === "number" || typeof value === "boolean") {
23892
- return String(value).toLowerCase();
23893
- }
23894
- if (Array.isArray(value)) return value.map(normalizePlayGrepText).join(" ");
23895
- if (typeof value === "object") {
23896
- return Object.values(value).map(normalizePlayGrepText).join(" ");
23897
- }
23898
- return "";
23899
- }
23900
- function parsePlayGrepTerms(query, mode) {
23901
- const trimmed = query.trim().toLowerCase();
23902
- if (!trimmed) return [];
23903
- if (mode === "phrase") return [trimmed];
23904
- const matches = trimmed.match(/"([^"]+)"|'([^']+)'|\S+/g) ?? [];
23905
- return matches.map((term) => term.replace(/^["']|["']$/g, "").trim()).filter(Boolean);
23906
- }
23907
- function matchesPlayGrepQuery(value, query, mode) {
23908
- const terms = parsePlayGrepTerms(query, mode);
23909
- if (terms.length === 0) return true;
23910
- const haystack = normalizePlayGrepText(value);
23911
- if (mode === "any") return terms.some((term) => haystack.includes(term));
23912
- return terms.every((term) => haystack.includes(term));
23913
- }
23914
24548
  async function handlePlayGrep(args) {
23915
24549
  const query = args[0]?.trim();
23916
24550
  if (!query) {
23917
- console.error(
23918
- "Usage: deepline plays grep <query> [--mode all|any|phrase] [--compact] [--json]"
23919
- );
24551
+ console.error("Usage: deepline plays grep <query> [--compact] [--json]");
23920
24552
  return 1;
23921
24553
  }
23922
- let mode = "all";
23923
- for (let index = 1; index < args.length; index += 1) {
23924
- const arg = args[index];
23925
- if (arg === "--mode" && args[index + 1]) {
23926
- const rawMode = args[++index].trim().toLowerCase();
23927
- mode = rawMode === "any" || rawMode === "phrase" ? rawMode : "all";
23928
- }
23929
- }
23930
24554
  const compact = args.includes("--compact");
23931
24555
  const client2 = new DeeplineClient();
23932
- const plays = (await client2.listPlays({
23933
- grep: query,
23934
- grepMode: mode
23935
- })).filter(
23936
- (play) => matchesPlayGrepQuery(
23937
- {
23938
- name: play.name,
23939
- reference: play.reference,
23940
- displayName: play.displayName,
23941
- origin: play.origin,
23942
- ownerType: play.ownerType,
23943
- aliases: play.aliases,
23944
- toolCategories: play.toolCategories,
23945
- inputSchema: play.inputSchema,
23946
- outputSchema: play.outputSchema
23947
- },
23948
- query,
23949
- mode
23950
- )
23951
- ).map((play) => summarizePlayListItemForCli(play, { compact }));
24556
+ const plays = (await client2.listPlays({ grep: query })).map(
24557
+ (play) => summarizePlayListItemForCli(play, { compact })
24558
+ );
23952
24559
  if (argsWantJson(args)) {
23953
24560
  printCompactJson({
23954
24561
  plays,
23955
24562
  count: plays.length,
23956
24563
  query,
23957
- grep: { mode, terms: parsePlayGrepTerms(query, mode) }
24564
+ grep: { engine: "convex_full_text" }
23958
24565
  });
23959
24566
  return 0;
23960
24567
  }
@@ -24662,7 +25269,7 @@ Pass-through input flags:
24662
25269
  if (effectiveTarget && passthroughArgs[0] === effectiveTarget) {
24663
25270
  passthroughArgs.shift();
24664
25271
  }
24665
- process.exitCode = await handlePlayRun([
25272
+ const exitCode = await handlePlayRun([
24666
25273
  ...effectiveTarget ? [effectiveTarget] : [],
24667
25274
  ...options.file ? ["--file", options.file] : [],
24668
25275
  ...options.name ? ["--name", options.name] : [],
@@ -24692,6 +25299,11 @@ Pass-through input flags:
24692
25299
  ...options.full ? ["--full"] : [],
24693
25300
  ...passthroughArgs
24694
25301
  ]);
25302
+ recordCliTrace({
25303
+ phase: "cli.play_command_exit",
25304
+ exitCode
25305
+ });
25306
+ process.exitCode = exitCode;
24695
25307
  });
24696
25308
  registerPlayBootstrapCommand(play);
24697
25309
  play.command("get <target>").description(
@@ -24733,17 +25345,22 @@ Examples:
24733
25345
  `
24734
25346
  Notes:
24735
25347
  Inventory command for saved org plays and prebuilt plays. Use search for
24736
- ranked discovery by task or schema.
25348
+ ranked discovery by task or schema. Returns one bounded page with has_more,
25349
+ is_truncated, next_cursor, and a next-page command. Use --limit to choose
25350
+ the page size and --cursor to continue with the same filters.
24737
25351
 
24738
25352
  Examples:
24739
25353
  deepline plays list
24740
25354
  deepline plays search email --json
24741
25355
  `
24742
- ).option("--json", "Emit JSON output. Also automatic when stdout is piped").option(
25356
+ ).option("--limit <count>", "Page size").option("--cursor <cursor>", "Continue a previous page").option("--show-cost", "Include the per-row or per-run cost estimate").option("--json", "Emit JSON output. Also automatic when stdout is piped").option(
24743
25357
  "--categories <categories>",
24744
25358
  "Filter by comma-separated canonical tool categories"
24745
25359
  ).action(async (options) => {
24746
25360
  process.exitCode = await handlePlayList([
25361
+ ...options.limit ? ["--limit", options.limit] : [],
25362
+ ...options.cursor ? ["--cursor", options.cursor] : [],
25363
+ ...options.showCost ? ["--show-cost"] : [],
24747
25364
  ...options.categories ? ["--categories", options.categories] : [],
24748
25365
  ...options.json ? ["--json"] : []
24749
25366
  ]);
@@ -24782,8 +25399,8 @@ Notes:
24782
25399
  because they are trusted, robust plays with maintained contracts. Use describe
24783
25400
  on a result before running it.
24784
25401
  Add --all only when you are specifically looking for a play you created.
24785
- The grep alias is the same ranked retrieval surface with a more literal name
24786
- for agents that are filtering the play registry.
25402
+ Use plays grep to search active org-owned Plays through Convex's indexed
25403
+ authored corpus. It does not search prebuilt or archived Plays.
24787
25404
 
24788
25405
  Examples:
24789
25406
  deepline plays search email
@@ -24801,25 +25418,24 @@ Examples:
24801
25418
  ]);
24802
25419
  });
24803
25420
  addPlaySearchCommand(play.command("search <query>"));
24804
- play.command("grep <query>").description("Literal grep over play names, schemas, and descriptions.").addHelpText(
25421
+ play.command("grep <query>").description(
25422
+ "Search active org-owned Plays through Convex full-text search."
25423
+ ).addHelpText(
24805
25424
  "after",
24806
25425
  `
24807
25426
  Notes:
24808
- Literal registry filtering. Terms are matched case-insensitively against play
24809
- names, references, display names, ownership, and schemas. Use
24810
- --mode phrase for exact phrase matching, --mode any for OR, and the default
24811
- --mode all for AND.
25427
+ Queries use Convex token semantics over the indexed authored Play corpus.
25428
+ Prebuilt and archived Plays are not part of this index.
24812
25429
 
24813
25430
  Examples:
24814
25431
  deepline plays grep email --json
24815
- deepline plays grep "company contact" --mode all --json
25432
+ deepline plays grep "company contact" --compact --json
24816
25433
  deepline plays describe prebuilt/company-to-contact --json
24817
25434
  `
24818
- ).option("--compact", "Emit compact schemas").option("--mode <mode>", "Grep matching mode: all, any, or phrase").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
25435
+ ).option("--compact", "Emit compact schemas").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
24819
25436
  process.exitCode = await handlePlayGrep([
24820
25437
  query,
24821
25438
  ...options.compact ? ["--compact"] : [],
24822
- ...options.mode ? ["--mode", options.mode] : [],
24823
25439
  ...options.json ? ["--json"] : []
24824
25440
  ]);
24825
25441
  });
@@ -30417,7 +31033,7 @@ async function compileConfig(input2) {
30417
31033
  })).config;
30418
31034
  }
30419
31035
  function registerEnrichCommand(program) {
30420
- program.command("enrich").allowUnknownOption(true).description("Run v1-style CSV enrichment through the V2 play runner.").configureHelp({
31036
+ program.command("enrich", { hidden: true }).allowUnknownOption(true).description("Run v1-style CSV enrichment through the V2 play runner.").configureHelp({
30421
31037
  optionTerm(option) {
30422
31038
  return option.long === "--timeout" ? "--timeout SECONDS" : option.flags;
30423
31039
  }
@@ -31473,7 +32089,7 @@ function loadViewerAssets() {
31473
32089
  "viewer"
31474
32090
  )
31475
32091
  ] : [],
31476
- (0, import_node_path16.join)(process.cwd(), "src", "lib", "cli", "viewer")
32092
+ (0, import_node_path16.join)(process.cwd(), "apps", "deepline-api", "src", "lib", "cli", "viewer")
31477
32093
  ];
31478
32094
  for (const root of candidateRoots2) {
31479
32095
  try {
@@ -31690,7 +32306,7 @@ function addDeprecatedSubcommand(parent, family, subcommand, description) {
31690
32306
  });
31691
32307
  }
31692
32308
  function registerDeprecatedCommands(program) {
31693
- const session = program.command("session").description("Deprecated legacy session command namespace.").allowUnknownOption(true).allowExcessArguments(true).option("--json", "Emit JSON output").argument("[args...]").addHelpText(
32309
+ const session = program.command("session", { hidden: true }).description("Deprecated legacy session command namespace.").allowUnknownOption(true).allowExcessArguments(true).option("--json", "Emit JSON output").argument("[args...]").addHelpText(
31694
32310
  "after",
31695
32311
  `
31696
32312
  Notes:
@@ -31722,7 +32338,7 @@ Examples:
31722
32338
  `Deprecated legacy "deepline session ${subcommand}" command.`
31723
32339
  );
31724
32340
  }
31725
- const backend = program.command("backend").description("Deprecated legacy local backend command namespace.").allowUnknownOption(true).allowExcessArguments(true).option("--json", "Emit JSON output").argument("[args...]").addHelpText(
32341
+ const backend = program.command("backend", { hidden: true }).description("Deprecated legacy local backend command namespace.").allowUnknownOption(true).allowExcessArguments(true).option("--json", "Emit JSON output").argument("[args...]").addHelpText(
31726
32342
  "after",
31727
32343
  `
31728
32344
  Notes:
@@ -32633,7 +33249,7 @@ function renderDeployedListText(payload, requestedStatus) {
32633
33249
  }
32634
33250
  const applied = asString(payload.status_filter_applied) ?? requestedStatus ?? "active";
32635
33251
  lines.push(
32636
- `Status filter: ${applied}${requestedStatus === void 0 && applied === "active" ? " (default)" : ""} \u2014 use --status all to include disabled monitors.`
33252
+ `Status filter: ${applied}${requestedStatus === void 0 && applied === "active" ? " (default)" : ""} \u2014 use --status all to include inactive monitors.`
32637
33253
  );
32638
33254
  const summary = Array.isArray(payload.status_summary) ? payload.status_summary.flatMap((raw) => {
32639
33255
  const item = asRecord2(raw);
@@ -32641,7 +33257,7 @@ function renderDeployedListText(payload, requestedStatus) {
32641
33257
  const count = item ? asFiniteNumber(item.count) : void 0;
32642
33258
  return status && count !== void 0 ? [`${status}: ${count}`] : [];
32643
33259
  }).join(" ") : "";
32644
- if (summary) lines.push(`All lifecycle states: ${summary}`);
33260
+ if (summary) lines.push(`All monitor states: ${summary}`);
32645
33261
  if (payload.has_more === true || payload.is_truncated === true) {
32646
33262
  const nextCursor = asString(payload.next_cursor);
32647
33263
  lines.push(
@@ -33730,11 +34346,16 @@ async function handleMonitorsReactivate(key, options) {
33730
34346
  }
33731
34347
  var MONITOR_VERB_POLL_INTERVAL_MS = 2e3;
33732
34348
  function monitorHealthLine(health) {
34349
+ if (health.refresh?.state === "running") {
34350
+ return `refreshing whole scope \xB7 scanned ${health.scanned} so far`;
34351
+ }
34352
+ const alignmentRequired = health.alignment_required ?? 0;
33733
34353
  return [
33734
34354
  `converged ${health.converged}/${health.scanned}`,
33735
34355
  `needs_repair ${health.needs_repair}`,
33736
34356
  `missing ${health.observed.missing}`,
33737
34357
  `unaudited ${health.observed.unknown + health.observed.unset}`,
34358
+ ...alignmentRequired > 0 ? [`needs_alignment ${alignmentRequired}`] : [],
33738
34359
  `silent ${health.silent}`
33739
34360
  ].join(" \xB7 ");
33740
34361
  }
@@ -33768,7 +34389,11 @@ async function watchMonitorHealth(input2) {
33768
34389
  let last = await input2.client.monitors.health(scope);
33769
34390
  input2.onProgress(last);
33770
34391
  while (Date.now() < deadlineAt) {
33771
- if (last.needs_repair === 0 && last.observed.unknown === 0) return last;
34392
+ const unresolved = last.observed.unknown + last.observed.unset;
34393
+ const alignmentRequired = last.alignment_required ?? 0;
34394
+ if (last.refresh?.state !== "running" && !last.truncated && last.needs_repair === 0 && (unresolved === 0 || alignmentRequired === unresolved)) {
34395
+ return last;
34396
+ }
33772
34397
  await new Promise(
33773
34398
  (resolve21) => setTimeout(resolve21, MONITOR_VERB_POLL_INTERVAL_MS)
33774
34399
  );
@@ -33778,11 +34403,22 @@ async function watchMonitorHealth(input2) {
33778
34403
  return last;
33779
34404
  }
33780
34405
  function monitorHealthText(health) {
33781
- const partial = health.truncated ? " (partial \u2014 scope larger than one read)" : "";
34406
+ const partial = health.refresh?.state === "running" ? " (refreshing \u2014 counts below are not final)" : health.truncated ? " (partial \u2014 scope larger than one read)" : "";
34407
+ const unauditedMonitors = health.unaudited_monitors ?? [];
33782
34408
  const lines = [
33783
34409
  `scope: ${health.scope.fleet_id ?? "workspace"}${partial}`,
33784
34410
  monitorHealthLine(health)
33785
34411
  ];
34412
+ if ((health.batch_operations?.length ?? 0) > 0) {
34413
+ lines.push("");
34414
+ lines.push("batch work in progress:");
34415
+ for (const operation of health.batch_operations ?? []) {
34416
+ const retry = operation.next_attempt_at === void 0 ? "" : ` \xB7 next ${new Date(operation.next_attempt_at).toISOString()}`;
34417
+ lines.push(
34418
+ ` ${operation.kind} ${operation.run_id} \xB7 ${operation.state}${retry}`
34419
+ );
34420
+ }
34421
+ }
33786
34422
  if (health.deactivation_reasons.length > 0) {
33787
34423
  lines.push("");
33788
34424
  lines.push("deactivated by Deepline:");
@@ -33791,6 +34427,29 @@ function monitorHealthText(health) {
33791
34427
  lines.push(` ${entry.reason} (${entry.count})${how}`);
33792
34428
  }
33793
34429
  }
34430
+ if (unauditedMonitors.length > 0) {
34431
+ const reason = {
34432
+ not_yet_audited: "has not been observed yet",
34433
+ missing_provider_resource_id: "has no stored upstream resource id",
34434
+ provider_observation_unknown: "the provider observation is still unknown"
34435
+ };
34436
+ lines.push("");
34437
+ lines.push("unresolved monitor observations:");
34438
+ for (const entry of unauditedMonitors) {
34439
+ lines.push(
34440
+ ` ${entry.monitor_key} ${entry.tool} \u2014 ${reason[entry.reason]}`
34441
+ );
34442
+ }
34443
+ }
34444
+ if ((health.alignment_required ?? 0) > 0) {
34445
+ lines.push("");
34446
+ lines.push(
34447
+ `${health.alignment_required} monitor(s) need provider identity alignment; normal repair intentionally will not guess or create a paid replacement.`
34448
+ );
34449
+ lines.push(
34450
+ `Run the exact inventory audit, then review one safe adoption candidate: deepline-admin monitors reconcile ${health.scope.org_id} --monitor <monitor-key> --environment production --json`
34451
+ );
34452
+ }
33794
34453
  if (health.worst_offenders.length > 0) {
33795
34454
  lines.push("");
33796
34455
  lines.push(
@@ -33855,78 +34514,143 @@ async function handleMonitorsAudit(options) {
33855
34514
  );
33856
34515
  }
33857
34516
  async function handleMonitorsRepair(options) {
33858
- const client2 = new DeeplineClient();
34517
+ return await handleMonitorBatch("repair", {
34518
+ ...options,
34519
+ ...options.fleet?.trim() ? {} : { target: "needs-repair" }
34520
+ });
34521
+ }
34522
+ async function handleMonitorsHealth(options) {
33859
34523
  const fleetId = options.fleet?.trim() || void 0;
33860
- const scope = fleetId ? { fleetId } : {};
33861
- const result = await client2.monitors.repair({
33862
- ...scope,
33863
- ...options.dryRun ? { dryRun: true } : {}
34524
+ const health = await new DeeplineClient().monitors.health(
34525
+ fleetId ? { fleetId } : {}
34526
+ );
34527
+ printCommandEnvelope(health, {
34528
+ json: options.json,
34529
+ text: monitorHealthText(health)
33864
34530
  });
33865
- if (options.dryRun) {
33866
- assertMonitorDryRunAcknowledged(
33867
- result,
33868
- { command: "monitors repair", mutation: "repair" }
34531
+ }
34532
+ function monitorBatchScope(options, kind) {
34533
+ const selectors = [
34534
+ options.target ? "target" : null,
34535
+ options.fleet?.trim() ? "fleet" : null,
34536
+ options.keys?.trim() ? "keys" : null
34537
+ ].filter((value) => value !== null);
34538
+ if (selectors.length === 0 && kind === "audit") {
34539
+ return { kind: "target", target: "all" };
34540
+ }
34541
+ if (selectors.length !== 1) {
34542
+ throw new MonitorsUsageError(
34543
+ `monitors batch ${kind} needs exactly one selector: --target, --fleet, or --keys.`
33869
34544
  );
33870
34545
  }
33871
- let health = result.health;
33872
- if (options.watch && !options.dryRun) {
33873
- const progress = monitorVerbProgressReporter();
33874
- try {
33875
- health = await watchMonitorHealth({
33876
- client: client2,
33877
- ...scope,
33878
- timeoutMs: fleetTimeoutMs(options.timeout),
33879
- onProgress: progress.report
33880
- });
33881
- } finally {
33882
- progress.finish();
33883
- }
34546
+ if (options.target) return { kind: "target", target: options.target };
34547
+ if (options.fleet?.trim())
34548
+ return { kind: "fleet", fleetId: options.fleet.trim() };
34549
+ const monitorKeys = (options.keys ?? "").split(",").map((key) => key.trim()).filter(Boolean);
34550
+ if (monitorKeys.length === 0) {
34551
+ throw new MonitorsUsageError(
34552
+ "--keys needs one or more comma-separated monitor keys."
34553
+ );
33884
34554
  }
33885
- const verb = options.dryRun ? "would queue" : "queued";
33886
- const lines = [
33887
- `${verb} ${result.repair.queued} of ${result.repair.scanned} monitors`
33888
- ];
33889
- for (const item of result.repair.plan.slice(0, REPAIR_PLAN_PREVIEW)) {
33890
- const why = item.deactivation_reason ? ` \xB7 ${item.deactivation_reason}` : "";
33891
- lines.push(
33892
- ` ${item.action.padEnd(6)} ${item.monitor_key} desired ${item.desired} \xB7 observed ${item.observed}${why}`
34555
+ return { kind: "keys", monitorKeys };
34556
+ }
34557
+ function monitorBatchIdempotencyKey(kind, value) {
34558
+ return value?.trim() || `monitor-batch-${kind}-${(0, import_node_crypto8.randomUUID)()}`;
34559
+ }
34560
+ async function handleMonitorBatchDeploy(options) {
34561
+ if (!options.file?.trim()) {
34562
+ throw new MonitorsUsageError(
34563
+ "monitors batch deploy requires --file <monitors.ndjson>."
33893
34564
  );
33894
34565
  }
33895
- if (result.repair.plan.length > REPAIR_PLAN_PREVIEW) {
33896
- lines.push(` \u2026 ${result.repair.plan.length - REPAIR_PLAN_PREVIEW} more`);
34566
+ if (options.dryRun !== true && options.yes !== true) {
34567
+ throw new MonitorsUsageError(
34568
+ "monitors batch deploy changes provider monitors asynchronously. Preview with --dry-run, then confirm with --yes."
34569
+ );
33897
34570
  }
33898
- if (result.repair.unaudited.length > 0) {
33899
- lines.push(
33900
- `${result.repair.unaudited.length} monitors have never been observed \u2014 run 'deepline monitors audit' first.`
34571
+ let manifestNdjson;
34572
+ try {
34573
+ manifestNdjson = (0, import_node_fs15.readFileSync)((0, import_node_path17.resolve)(options.file), "utf8");
34574
+ } catch (error) {
34575
+ throw new MonitorsUsageError(
34576
+ `Could not read monitor batch manifest '${options.file}': ${error instanceof Error ? error.message : String(error)}`
33901
34577
  );
33902
34578
  }
33903
- if (result.repair.unowned.length > 0) {
33904
- lines.push(
33905
- `${result.repair.unowned.length} monitors disagree but no fleet owns them; repair those with 'deepline monitors deploy' or 'deepline monitors delete'.`
34579
+ if (!manifestNdjson.trim()) {
34580
+ throw new MonitorsUsageError("Monitor batch manifest is empty.");
34581
+ }
34582
+ const idempotencyKey = monitorBatchIdempotencyKey(
34583
+ "deploy",
34584
+ options.idempotencyKey
34585
+ );
34586
+ const result = await new DeeplineClient().monitors.batch.submit({
34587
+ kind: "deploy",
34588
+ manifestNdjson,
34589
+ dryRun: options.dryRun === true,
34590
+ idempotencyKey
34591
+ });
34592
+ const operation = result.operation;
34593
+ printCommandEnvelope(
34594
+ { ...result, idempotency_key: idempotencyKey },
34595
+ {
34596
+ json: options.json,
34597
+ text: [
34598
+ `${result.rejoined ? "rejoined" : "submitted"} deploy batch ${operation.runId}`,
34599
+ `state ${operation.state} \xB7 retry key ${idempotencyKey}`,
34600
+ `inspect: deepline monitors batch get ${operation.runId} --json`
34601
+ ].join("\n") + "\n"
34602
+ }
34603
+ );
34604
+ }
34605
+ async function handleMonitorBatch(kind, options) {
34606
+ if ((kind === "deactivate" || kind === "reactivate" || kind === "repair") && options.dryRun !== true && options.yes !== true) {
34607
+ throw new MonitorsUsageError(
34608
+ `monitors batch ${kind} changes provider monitors asynchronously. Preview with --dry-run, then confirm with --yes.`
33906
34609
  );
33907
34610
  }
33908
- if (result.repair.fenced.length > 0) {
33909
- lines.push(
33910
- `${result.repair.fenced.length} monitors have a provider operation in flight; Deepline is finishing those and repair left them alone.`
34611
+ if (kind === "deactivate" && !options.reason?.trim()) {
34612
+ throw new MonitorsUsageError(
34613
+ "monitors batch deactivate requires --reason (for example insufficient_balance or billing_failure)."
33911
34614
  );
33912
34615
  }
33913
- lines.push("");
33914
- lines.push(monitorHealthText(health));
34616
+ const idempotencyKey = monitorBatchIdempotencyKey(
34617
+ kind,
34618
+ options.idempotencyKey
34619
+ );
34620
+ const result = await new DeeplineClient().monitors.batch.submit({
34621
+ kind,
34622
+ scope: monitorBatchScope(options, kind),
34623
+ dryRun: options.dryRun === true,
34624
+ ...options.reason?.trim() ? { deactivationReason: options.reason.trim() } : {},
34625
+ idempotencyKey
34626
+ });
34627
+ const operation = result.operation;
34628
+ const text = [
34629
+ `${result.rejoined ? "rejoined" : "submitted"} ${operation.kind} batch ${operation.runId}`,
34630
+ `state ${operation.state} \xB7 retry key ${idempotencyKey}`,
34631
+ `inspect: deepline monitors batch get ${operation.runId} --json`
34632
+ ].join("\n");
33915
34633
  printCommandEnvelope(
33916
- { ...result, health },
33917
- { json: options.json, text: lines.join("\n") }
34634
+ { ...result, idempotency_key: idempotencyKey },
34635
+ { json: options.json, text: `${text}
34636
+ ` }
33918
34637
  );
33919
34638
  }
33920
- var REPAIR_PLAN_PREVIEW = 20;
33921
- async function handleMonitorsHealth(options) {
33922
- const fleetId = options.fleet?.trim() || void 0;
33923
- const health = await new DeeplineClient().monitors.health(
33924
- fleetId ? { fleetId } : {}
33925
- );
33926
- printCommandEnvelope(health, {
33927
- json: options.json,
33928
- text: monitorHealthText(health)
34639
+ async function handleMonitorBatchGet(operationId, options) {
34640
+ const result = await new DeeplineClient().monitors.batch.get(operationId, {
34641
+ ...options.cursor ? { cursor: options.cursor } : {}
33929
34642
  });
34643
+ const summary = result.operation.summary ?? {};
34644
+ const text = [
34645
+ `${result.operation.kind} batch ${result.operation.runId}`,
34646
+ `state ${result.operation.state}`,
34647
+ `progress ${JSON.stringify(summary)}`,
34648
+ ...result.results ? [
34649
+ `results ${result.results.page.length}${result.results.isDone ? "" : " (more; pass --cursor)"}`
34650
+ ] : []
34651
+ ].join("\n");
34652
+ printCommandEnvelope(result, { json: options.json, text: `${text}
34653
+ ` });
33930
34654
  }
33931
34655
  function registerMonitorsCommands(program) {
33932
34656
  const monitors = program.command("monitors").description("Discover, deploy, and manage Deepline monitors.").showSuggestionAfterError(true).addHelpText(
@@ -33950,9 +34674,8 @@ Exit codes:
33950
34674
 
33951
34675
  Examples:
33952
34676
  deepline monitors status --json
33953
- deepline monitors available --json # compact list by default
33954
- deepline monitors available --full --json # complete catalog
33955
- deepline monitors available deepline_native.company_radar --json
34677
+ deepline tools list --categories monitors --json
34678
+ deepline tools get deepline_native.company_radar --json
33956
34679
  deepline monitors check '{"key":"job-openings","tool":"deepline_native.company_radar","payload":{"domain":"stripe.com","radar_type":"company_job_openings"}}'
33957
34680
  deepline monitors deploy --dry-run --file monitor.json
33958
34681
  deepline monitors deploy --file monitor.json
@@ -33991,25 +34714,29 @@ Examples:
33991
34714
  withJsonOption(
33992
34715
  monitors.command("repair").description(
33993
34716
  "Converge the monitors whose desired and observed states disagree."
33994
- ).option("--fleet <fleet-id>", "Repair one fleet instead of every monitor").option("--dry-run", "Show what would be queued without queueing it").option("--watch", "Keep polling health until the scope settles").option(
33995
- "--timeout <duration>",
33996
- "How long --watch waits, e.g. 90s, 5m, 1h (default 10m)"
34717
+ ).option("--fleet <fleet-id>", "Repair one fleet instead of every monitor").option(
34718
+ "--dry-run",
34719
+ "Record the bounded repair plan without provider changes"
34720
+ ).option("--yes", "Confirm the asynchronous provider repair").option(
34721
+ "--idempotency-key <key>",
34722
+ "Stable retry key (generated when omitted)"
33997
34723
  ).addHelpText(
33998
34724
  "after",
33999
34725
  `
34000
34726
  Notes:
34001
- Queues the monitors that disagree with the provider onto the same convergence
34002
- loop a fleet sync uses; the provider work happens on Deepline's own schedule.
34003
- Idempotent \u2014 the queue IS the disagreement, so a repeat run queues only what
34004
- is still wrong.
34727
+ This is the short spelling of 'monitors batch repair --target needs-repair'.
34728
+ It handles both direct monitors and Fleet members through one durable
34729
+ service-account worker. An active historical monitor without an upstream id
34730
+ is selected for repair; current Convex writes cannot create that state.
34005
34731
 
34006
- Monitors nobody has observed are never repaired: run 'deepline monitors audit'
34007
- first. Monitors no fleet owns are reported rather than queued; deploy or delete
34008
- those directly.
34732
+ It returns a run id immediately. Inspect retained results with
34733
+ 'deepline monitors batch get <run-id> --json'. Reuse --idempotency-key after
34734
+ a lost response to rejoin the exact run without another provider request.
34009
34735
 
34010
34736
  Examples:
34011
34737
  deepline monitors repair --dry-run
34012
- deepline monitors repair --fleet accounts-eu --watch
34738
+ deepline monitors repair --yes
34739
+ deepline monitors repair --fleet accounts-eu --yes --idempotency-key repair-eu-2026-09-09
34013
34740
  `
34014
34741
  )
34015
34742
  ).action(monitorsAction(handleMonitorsRepair));
@@ -34032,6 +34759,75 @@ Examples:
34032
34759
  `
34033
34760
  )
34034
34761
  ).action(monitorsAction(handleMonitorsHealth));
34762
+ const batch = monitors.command("batch").description("Submit or inspect durable bulk monitor operations.");
34763
+ const batchSelectorOptions = (command) => command.option(
34764
+ "--target <target>",
34765
+ "One of: all, all-active, billing-suspended, needs-repair"
34766
+ ).option("--fleet <fleet-id>", "Select one Fleet").option("--keys <keys>", "Up to 100 comma-separated monitor keys").option(
34767
+ "--dry-run",
34768
+ "Record the same bounded plan without provider changes"
34769
+ ).option(
34770
+ "--idempotency-key <key>",
34771
+ "Stable retry key (generated when omitted)"
34772
+ );
34773
+ withJsonOption(
34774
+ batch.command("deploy").description(
34775
+ "Durably create complete NDJSON monitor definitions through provider bulk create."
34776
+ ).requiredOption(
34777
+ "--file <path>",
34778
+ "NDJSON file of complete monitor definitions"
34779
+ ).option("--dry-run", "Validate and record the durable deploy plan").option("--yes", "Confirm asynchronous provider creation").option(
34780
+ "--idempotency-key <key>",
34781
+ "Stable retry key (generated when omitted)"
34782
+ )
34783
+ ).action(
34784
+ monitorsAction(
34785
+ (options) => handleMonitorBatchDeploy(options)
34786
+ )
34787
+ );
34788
+ withJsonOption(
34789
+ batchSelectorOptions(
34790
+ batch.command("audit").description("Durably audit a selection; defaults to --target all.")
34791
+ )
34792
+ ).action(
34793
+ monitorsAction(
34794
+ (options) => handleMonitorBatch("audit", options)
34795
+ )
34796
+ );
34797
+ withJsonOption(
34798
+ batchSelectorOptions(
34799
+ batch.command("repair").description(
34800
+ "Durably re-audit and converge a selection through the existing lifecycle fences."
34801
+ )
34802
+ ).option("--yes", "Confirm the asynchronous provider repair")
34803
+ ).action(
34804
+ monitorsAction(
34805
+ (options) => handleMonitorBatch("repair", options)
34806
+ )
34807
+ );
34808
+ withJsonOption(
34809
+ batchSelectorOptions(
34810
+ batch.command("deactivate").description(
34811
+ "Durably deactivate a selection through provider batch deletes."
34812
+ )
34813
+ ).requiredOption("--reason <reason>", "Declared deactivation reason").option("--yes", "Confirm the asynchronous provider deactivation")
34814
+ ).action(
34815
+ monitorsAction(
34816
+ (options) => handleMonitorBatch("deactivate", options)
34817
+ )
34818
+ );
34819
+ withJsonOption(
34820
+ batchSelectorOptions(
34821
+ batch.command("reactivate").description("Durably reactivate a selection after a suspension.")
34822
+ ).option("--yes", "Confirm provider reactivation and possible billing")
34823
+ ).action(
34824
+ monitorsAction(
34825
+ (options) => handleMonitorBatch("reactivate", options)
34826
+ )
34827
+ );
34828
+ withJsonOption(
34829
+ batch.command("get <operation-id>").description("Read batch progress and one bounded evidence page.").option("--cursor <cursor>", "Read the next retained evidence page")
34830
+ ).action(monitorsAction(handleMonitorBatchGet));
34035
34831
  withJsonOption(
34036
34832
  monitors.command("status").description("Check whether you have access to Deepline monitors.").addHelpText(
34037
34833
  "after",
@@ -34049,7 +34845,7 @@ Examples:
34049
34845
  )
34050
34846
  ).action(monitorsAction(handleMonitorsStatus));
34051
34847
  withJsonOption(
34052
- monitors.command("available [tool-id]").alias("tools").description(
34848
+ monitors.command("available [tool-id]", { hidden: true }).alias("tools").description(
34053
34849
  "List the monitor types you can deploy, or describe one by tool id."
34054
34850
  ).addHelpText(
34055
34851
  "after",
@@ -34083,8 +34879,9 @@ Examples:
34083
34879
  "after",
34084
34880
  `
34085
34881
  Notes:
34086
- Read-only. --status filters by monitor status: active (default), disabled, or
34087
- all. The output echoes the status filter that was applied. The response reports
34882
+ Read-only. A monitor state is active or inactive; provider disagreement is
34883
+ reported separately by audit/health. The output echoes the state filter that
34884
+ was applied. The response reports
34088
34885
  total (the true registry count, not the page size), returned, has_more, and
34089
34886
  next_cursor. When has_more is true, page with --cursor <next_cursor> until it
34090
34887
  is false \u2014 a "no matching
@@ -34102,7 +34899,7 @@ Examples:
34102
34899
  `
34103
34900
  ).option(
34104
34901
  "--status <status>",
34105
- "Filter by monitor status: active (default), disabled, or all"
34902
+ "Filter by monitor state: active (default), inactive, or all"
34106
34903
  ).option("--limit <n>", "Limit the number of deployed monitors returned").option(
34107
34904
  "--cursor <cursor>",
34108
34905
  "Page past a truncated result using the next_cursor from a prior list response"
@@ -34165,7 +34962,7 @@ Notes:
34165
34962
  Play delivery. The JSON response reports this validation scope explicitly.
34166
34963
  Provider-specific grammar, precedence, enum applicability, examples, and
34167
34964
  update semantics are returned by the live monitor contract. Inspect it with
34168
- \`deepline monitors available deepline_native.company_radar --json\`.
34965
+ \`deepline tools get deepline_native.company_radar --json\`.
34169
34966
  For a bounded urgent Deepline Native preview, use
34170
34967
  controls.execution_type="priority". Deepline injects the upstream marker;
34171
34968
  do not author custom_fields yourself. Priority is capped at ten upstream or
@@ -34203,6 +35000,10 @@ Notes:
34203
35000
  no separate repair command or retry queue: rerun the same deploy/update.
34204
35001
  Scheduled reconciliation can also finish that exact saved cleanup; it never
34205
35002
  creates another monitor.
35003
+ For TamRadar, deliberately changing payload.updates_since is an explicit
35004
+ historical replay request, including when redeploying a disabled monitor.
35005
+ Deepline preserves that selected boundary; filter-only deploys and ordinary
35006
+ reactivation start from a fresh future-only source boundary instead.
34206
35007
  For a bounded urgent Deepline Native preview, set
34207
35008
  controls.execution_type="priority". Deepline injects the provider custom
34208
35009
  field and enforces a ten-slot per-org cap; do not use it for regular or bulk
@@ -34232,6 +35033,11 @@ positionally, via --file <path>, or from stdin with --file -.
34232
35033
  When <patch> includes payload, its top-level fields are merged with the stored
34233
35034
  payload before validation. This lets you change one filter without resending
34234
35035
  domain or radar_type; nested values and arrays replace atomically.
35036
+ For TamRadar, changing payload.updates_since is an explicit historical replay
35037
+ request. Deepline passes that requested boundary upstream, but previously
35038
+ delivered canonical findings are deduplicated before dispatch and billing; only
35039
+ previously unseen historical findings are chargeable. Filter-only updates,
35040
+ repair, and reactivation instead use a fresh future-only source boundary.
34235
35041
  Use {"controls":{"execution_type":"priority"}} only for a bounded urgent
34236
35042
  Deepline Native preview. The ten-radar priority cap applies on update too.
34237
35043
  Use {"controls":{"execution_type":null}} to release priority without
@@ -34318,7 +35124,7 @@ ${MONITOR_FLEET_HELP_EPILOG}`
34318
35124
  Creates a ready-to-review JSON definition. Fleets always sync ${MONITOR_FLEET_DOCUMENTATION.fixedPolicy.cadence} and retain
34319
35125
  a missing member for ${MONITOR_FLEET_DOCUMENTATION.fixedPolicy.removalGrace} before it is ${MONITOR_FLEET_DOCUMENTATION.fixedPolicy.onRemoved}d; those
34320
35126
  policies are not authored in the file. ${MONITOR_FLEET_DOCUMENTATION.stickyMembership} The payload remains the actual monitor payload for the chosen tool, so use
34321
- \`deepline monitors available\` to discover a tool first.
35127
+ \`deepline tools list --categories monitors\` to discover a tool first.
34322
35128
 
34323
35129
  Example:
34324
35130
  deepline monitors fleets init account-job-openings \\
@@ -34495,7 +35301,7 @@ Example:
34495
35301
  const deployed = withJsonOption(
34496
35302
  monitors.command("deployed", { hidden: true }).description("Alias of `monitors list` (plus get/update/delete aliases).").option(
34497
35303
  "--status <status>",
34498
- "Filter by monitor status: active (default), disabled, or all"
35304
+ "Filter by monitor state: active (default), inactive, or all"
34499
35305
  ).option("--limit <n>", "Limit the number of deployed monitors returned").option(
34500
35306
  "--cursor <cursor>",
34501
35307
  "Page past a truncated result using the next_cursor from a prior list response"
@@ -34637,7 +35443,8 @@ function organizationAuthValues(input2) {
34637
35443
  [HOST_URL_ENV]: input2.baseUrl,
34638
35444
  [API_KEY_ENV]: input2.apiKey,
34639
35445
  [ACTIVE_ORG_ID_ENV]: input2.orgId,
34640
- [ACTIVE_ORG_NAME_ENV]: input2.orgName
35446
+ [ACTIVE_ORG_NAME_ENV]: input2.orgName,
35447
+ ...featureEnvValues(input2)
34641
35448
  };
34642
35449
  }
34643
35450
  function organizationCommandContext(input2) {
@@ -34853,6 +35660,7 @@ async function handleOrgSwitch(selection, options) {
34853
35660
  if (!selection && !options.orgId && authScope === "folder" && provenance.scope === "global" && provenance.project.state === "project") {
34854
35661
  const commandContext = resolveCliCommandContext(config);
34855
35662
  const activeOrganization = commandContext.active_organization;
35663
+ const pinnedFeatures = storedHostFeatures(config.baseUrl);
34856
35664
  const project_env_paths2 = saveProjectDeeplineEnvValues(
34857
35665
  {
34858
35666
  [HOST_URL_ENV]: config.baseUrl,
@@ -34860,7 +35668,8 @@ async function handleOrgSwitch(selection, options) {
34860
35668
  ...activeOrganization ? {
34861
35669
  [ACTIVE_ORG_ID_ENV]: activeOrganization.org_id,
34862
35670
  [ACTIVE_ORG_NAME_ENV]: activeOrganization.org_name ?? ""
34863
- } : {}
35671
+ } : {},
35672
+ ...pinnedFeatures !== void 0 ? { [FEATURES_ENV]: pinnedFeatures } : {}
34864
35673
  },
34865
35674
  provenance.project.dir
34866
35675
  );
@@ -34940,11 +35749,15 @@ async function handleOrgSwitch(selection, options) {
34940
35749
  const authTarget = resolveOrgSwitchAuthTarget(authScope, config);
34941
35750
  if (target.is_current) {
34942
35751
  let project_env_paths2 = [];
35752
+ const features = await http.post("/api/v2/auth/cli/status", {
35753
+ api_key: config.apiKey
35754
+ }).then((status) => status.features).catch(() => void 0);
34943
35755
  const authValues2 = organizationAuthValues({
34944
35756
  baseUrl: config.baseUrl,
34945
35757
  apiKey: config.apiKey,
34946
35758
  orgId: target.org_id,
34947
- orgName: target.name
35759
+ orgName: target.name,
35760
+ features
34948
35761
  });
34949
35762
  if (authTarget.kind === "folder") {
34950
35763
  project_env_paths2 = saveProjectDeeplineEnvValues(authValues2);
@@ -35011,7 +35824,8 @@ async function handleOrgSwitch(selection, options) {
35011
35824
  baseUrl: config.baseUrl,
35012
35825
  apiKey: switched.api_key,
35013
35826
  orgId: switched.org_id,
35014
- orgName: switched.org_name
35827
+ orgName: switched.org_name,
35828
+ features: switched.features
35015
35829
  });
35016
35830
  if (authTarget.kind === "folder") {
35017
35831
  project_env_paths = saveProjectDeeplineEnvValues(authValues);
@@ -35083,14 +35897,20 @@ async function handleOrgCreate(name, options) {
35083
35897
  const identity = await fetchWorkspaceCreationIdentity(http, config.apiKey);
35084
35898
  let created;
35085
35899
  let workspaceApiKey;
35900
+ let workspaceFeatures;
35086
35901
  let pendingIntentPath = null;
35087
35902
  if (!identity.orgId) {
35088
35903
  const firstWorkspace = await http.post("/api/v2/auth/cli/org-create", {
35089
35904
  api_key: config.apiKey,
35090
35905
  name: normalizedName
35091
35906
  });
35092
- const { api_key: apiKey, ...publicFirstWorkspace } = firstWorkspace;
35907
+ const {
35908
+ api_key: apiKey,
35909
+ features,
35910
+ ...publicFirstWorkspace
35911
+ } = firstWorkspace;
35093
35912
  workspaceApiKey = apiKey;
35913
+ workspaceFeatures = features;
35094
35914
  created = publicFirstWorkspace;
35095
35915
  } else {
35096
35916
  const pending = loadOrCreatePendingOrgCreate({
@@ -35111,6 +35931,7 @@ async function handleOrgCreate(name, options) {
35111
35931
  org_id: workspace.org_id
35112
35932
  });
35113
35933
  workspaceApiKey = switched.api_key;
35934
+ workspaceFeatures = switched.features;
35114
35935
  created = { ...workspace };
35115
35936
  pendingIntentPath = pending.path;
35116
35937
  }
@@ -35118,7 +35939,8 @@ async function handleOrgCreate(name, options) {
35118
35939
  baseUrl: config.baseUrl,
35119
35940
  apiKey: workspaceApiKey,
35120
35941
  orgId: created.org_id,
35121
- orgName: created.org_name
35942
+ orgName: created.org_name,
35943
+ features: workspaceFeatures
35122
35944
  });
35123
35945
  saveHostEnvValues(config.baseUrl, authValues);
35124
35946
  if (pendingIntentPath) (0, import_node_fs16.unlinkSync)(pendingIntentPath);
@@ -35289,7 +36111,7 @@ Examples:
35289
36111
  )
35290
36112
  ).action(handleOrgSwitch);
35291
36113
  addOrgSetOptions(
35292
- org.command("switch [selection]").description(
36114
+ org.command("switch [selection]", { hidden: true }).description(
35293
36115
  "Deprecated alias for org set. Set the organization for the selected auth scope."
35294
36116
  ).addHelpText(
35295
36117
  "after",
@@ -35315,6 +36137,10 @@ Examples:
35315
36137
  }
35316
36138
 
35317
36139
  // src/cli/commands/git.ts
36140
+ var GIT_DEPLOY_FEATURE = "github-deploy";
36141
+ function shellQuote3(value) {
36142
+ return `'${value.replace(/'/g, `'\\''`)}'`;
36143
+ }
35318
36144
  function renderProject(project) {
35319
36145
  const root = project.rootDirectory ? ` (${project.rootDirectory})` : "";
35320
36146
  const flags = [
@@ -35353,6 +36179,98 @@ async function handleList(options) {
35353
36179
  { json: options.json }
35354
36180
  );
35355
36181
  }
36182
+ function describeInstallationStatus(installation) {
36183
+ switch (installation.status) {
36184
+ case "active":
36185
+ return [
36186
+ `Connected to ${installation.accountLogin} (installation ${installation.installationId}).`
36187
+ ];
36188
+ case "uninstalled":
36189
+ return [
36190
+ `The Deepline GitHub App was uninstalled from ${installation.accountLogin}${installation.uninstalledAt ? ` at ${new Date(installation.uninstalledAt).toISOString()}` : ""}.`,
36191
+ "Deploys are paused for every linked repository. Run `deepline git connect`, then `deepline git link <owner/repo>` again for each repository; links from the old installation cannot be resumed."
36192
+ ];
36193
+ case "suspended":
36194
+ return [
36195
+ `GitHub suspended the Deepline App on ${installation.accountLogin}${installation.suspendedAt ? ` at ${new Date(installation.suspendedAt).toISOString()}` : ""}.`,
36196
+ "Pushes are not delivered while it is suspended; unsuspend it in GitHub settings."
36197
+ ];
36198
+ }
36199
+ }
36200
+ async function handleStatus2(options) {
36201
+ const { http } = getAuthedHttpClient();
36202
+ const payload = await http.get("/api/v2/github/installation");
36203
+ const installation = payload.installation;
36204
+ const others = (payload.installations ?? []).filter(
36205
+ (row) => row.installationId !== installation?.installationId
36206
+ );
36207
+ const lines = installation ? describeInstallationStatus(installation) : [
36208
+ "No GitHub installation is connected to this workspace. Run `deepline git connect`."
36209
+ ];
36210
+ for (const other of others) {
36211
+ lines.push(
36212
+ `Also: ${other.accountLogin} (installation ${other.installationId}) is ${other.status}.`
36213
+ );
36214
+ }
36215
+ const byId = new Map(
36216
+ (payload.installations ?? []).map((row) => [row.installationId, row])
36217
+ );
36218
+ const { projects } = await http.get(
36219
+ "/api/v2/play-projects?includeDisabled=1"
36220
+ );
36221
+ const affected_links = projects.map((project) => {
36222
+ const own = byId.get(project.installationId);
36223
+ const verdict = !own ? "unknown" : own.status === "active" ? null : own.status;
36224
+ return verdict ? {
36225
+ id: project.id,
36226
+ repoId: project.repoId,
36227
+ repoFullName: project.repoFullName,
36228
+ rootDirectory: project.rootDirectory,
36229
+ productionBranch: project.productionBranch,
36230
+ manifestPath: project.manifestPath,
36231
+ checkOnPullRequest: project.checkOnPullRequest,
36232
+ disabled: project.disabled,
36233
+ installationId: project.installationId,
36234
+ installationStatus: verdict
36235
+ } : null;
36236
+ }).filter((row) => row !== null);
36237
+ const activeInstallations = (payload.installations ?? []).filter(
36238
+ (row) => row.status === "active"
36239
+ );
36240
+ const relinkCommand = (link2) => [
36241
+ // The numeric repo id, not the name: a repository renamed while the
36242
+ // App was gone keeps a stale name here, and `git link` accepts the id.
36243
+ `deepline git link ${link2.repoId}`,
36244
+ // Stored values are admin-controlled text and this line is
36245
+ // copy-pasted into a shell by whoever reads it, so every
36246
+ // interpolated value below is quoted.
36247
+ `--branch ${shellQuote3(link2.productionBranch)}`,
36248
+ link2.rootDirectory ? `--root-dir ${shellQuote3(link2.rootDirectory)}` : "",
36249
+ link2.manifestPath && link2.manifestPath !== "deepline.json" ? `--manifest ${shellQuote3(link2.manifestPath)}` : "",
36250
+ link2.checkOnPullRequest ? "" : "--no-pr-checks",
36251
+ // With several active installations no id is guessed: the reader picks.
36252
+ activeInstallations.length > 1 ? "--installation <id>" : ""
36253
+ ].filter(Boolean).join(" ");
36254
+ for (const link2 of affected_links) {
36255
+ const label = `${link2.repoFullName}${link2.rootDirectory ? ` (${link2.rootDirectory})` : ""}`;
36256
+ lines.push(
36257
+ link2.installationStatus !== "suspended" ? `Link again: ${label} \u2014 its installation ${link2.installationId} is ${link2.installationStatus}; run \`${relinkCommand(link2)}\`${activeInstallations.length > 1 ? ` with <id> being the installation that reaches this repo: ${activeInstallations.map((row) => `${row.installationId} (${row.accountLogin})`).join(", ")}` : ""}.` : (
36258
+ // A link disabled during the suspension stays disabled; only an
36259
+ // enabled one picks up again on its own when GitHub unsuspends.
36260
+ link2.disabled ? `Paused and disabled: ${label} \u2014 its installation is suspended, and the link stays disabled after GitHub unsuspends it until you run \`deepline git set ${link2.id} --enabled\`.` : `Paused: ${label} \u2014 its installation is suspended; pushes resume when GitHub unsuspends it.`
36261
+ )
36262
+ );
36263
+ }
36264
+ printCommandEnvelope(
36265
+ {
36266
+ installation,
36267
+ installations: payload.installations ?? [],
36268
+ affected_links,
36269
+ render: { sections: [{ title: "github installation", lines }] }
36270
+ },
36271
+ { json: options.json }
36272
+ );
36273
+ }
35356
36274
  async function handleRepos(options) {
35357
36275
  const { http } = getAuthedHttpClient();
35358
36276
  const query = options.installation ? `?installationId=${encodeURIComponent(options.installation)}` : "";
@@ -35567,7 +36485,7 @@ async function handleDeploymentGet(id, options) {
35567
36485
  );
35568
36486
  }
35569
36487
  function registerGitCommands(program) {
35570
- const git = program.command("git").alias("projects").description(
36488
+ const git = program.command("git", { hidden: !cliFeatureEnabled(GIT_DEPLOY_FEATURE) }).alias("projects").description(
35571
36489
  "Git deploys: link repositories to this workspace and deploy plays on push."
35572
36490
  ).addHelpText(
35573
36491
  "after",
@@ -35583,6 +36501,9 @@ Examples:
35583
36501
  `
35584
36502
  );
35585
36503
  git.command("connect").description("Connect a GitHub installation (browser step).").option("--json", "Emit JSON output").action(async (options) => handleConnect(options));
36504
+ git.command("status").description(
36505
+ "Show the workspace's GitHub installation and whether it is still usable."
36506
+ ).option("--json", "Emit JSON output").action(async (options) => handleStatus2(options));
35586
36507
  git.command("repos").description("List repos the workspace's GitHub installation can reach.").option(
35587
36508
  "--installation <id>",
35588
36509
  "GitHub installation id (required when several are connected)"
@@ -35693,7 +36614,7 @@ async function handleQuickstart(options) {
35693
36614
  return 0;
35694
36615
  }
35695
36616
  function registerQuickstartCommands(program) {
35696
- program.command("quickstart").description(
36617
+ program.command("quickstart", { hidden: true }).description(
35697
36618
  "Deprecated compatibility command. Use /deepline-gtm in your agent."
35698
36619
  ).addHelpText(
35699
36620
  "after",
@@ -35888,18 +36809,25 @@ async function handleList2(options) {
35888
36809
  async function handleCheck(nameInput, options) {
35889
36810
  const name = normalizeSecretName(nameInput);
35890
36811
  const client2 = new DeeplineClient();
35891
- const secret = await client2.checkSecret(name);
36812
+ const check = options.play !== void 0 ? await client2.inspectSecret(name, { playName: options.play }) : null;
36813
+ const secret = check ? check.secret : await client2.checkSecret(name);
36814
+ const ok = check ? check.ok : Boolean(secret);
36815
+ const quotedPlay = `'${(options.play ?? "PLAY").replace(/'/g, `'"'"'`)}'`;
36816
+ const scopeHint = check ? secret?.scope === "play" ? check.reason === "disabled" ? `The Play-scoped secret is disabled and no active org fallback is available. Restore it with: deepline secrets set ${name} --scope play --play ${quotedPlay}` : `The active Play-scoped secret takes precedence over the org secret.${ok ? "" : ` Restore it with: deepline secrets set ${name} --scope play --play ${quotedPlay}`}` : `No Play override.${ok ? " The org secret is available." : ` Set or rotate the org secret with: deepline secrets set ${name}`}` : `Metadata only. Verify effective scope and active version with: deepline secrets check ${name} --play <play-name>`;
35892
36817
  printCommandEnvelope(
35893
36818
  {
35894
- ok: Boolean(secret),
36819
+ ok,
35895
36820
  name,
35896
36821
  secret: secret ?? null,
36822
+ check: check ? { playName: check.playName, reason: check.reason } : null,
36823
+ scopeHint,
35897
36824
  render: {
35898
36825
  sections: [
35899
36826
  {
35900
36827
  title: "secret check",
35901
36828
  lines: [
35902
- secret ? `${name}: active \u2014 ${renderSecret(secret)}` : `${name}: missing, disabled, or empty`
36829
+ check ? `${name}: ${check.reason} for ${check.playName}${secret ? ` (${secret.scope})` : ""}` : secret ? `${name}: active metadata \u2014 ${renderSecret(secret)}` : `${name}: missing, disabled, or empty`,
36830
+ scopeHint
35903
36831
  ]
35904
36832
  }
35905
36833
  ]
@@ -35907,7 +36835,7 @@ async function handleCheck(nameInput, options) {
35907
36835
  },
35908
36836
  { json: options.json }
35909
36837
  );
35910
- if (!secret) process.exitCode = 4;
36838
+ if (!ok) process.exitCode = 4;
35911
36839
  }
35912
36840
  async function handleSet2(nameInput, forbidden, options) {
35913
36841
  preventShellHistoryLeak(forbidden);
@@ -35961,6 +36889,7 @@ Notes:
35961
36889
  Examples:
35962
36890
  deepline secrets list
35963
36891
  deepline secrets check HUBSPOT_TOKEN
36892
+ deepline secrets check HUBSPOT_TOKEN --play hubspot-sync
35964
36893
  deepline secrets set HUBSPOT_TOKEN
35965
36894
  printf %s "$HUBSPOT_TOKEN" | deepline secrets set HUBSPOT_TOKEN --value-stdin
35966
36895
  deepline secrets set HUBSPOT_TOKEN --from-env HUBSPOT_TOKEN
@@ -35970,7 +36899,12 @@ Examples:
35970
36899
  secrets.command("list").description("List secret metadata only.").option("--json", "Emit JSON output").action(async (options) => {
35971
36900
  await handleList2(options);
35972
36901
  });
35973
- secrets.command("check").description("Check whether a secret exists and is active.").argument("<name>", "Secret name").option("--json", "Emit JSON output").action(async (name, options) => {
36902
+ secrets.command("check").description(
36903
+ "Check secret metadata, or effective availability with --play."
36904
+ ).argument("<name>", "Secret name").option(
36905
+ "--play <name>",
36906
+ "Validate the effective secret for a bare Play name and active version"
36907
+ ).option("--json", "Emit JSON output").action(async (name, options) => {
35974
36908
  await handleCheck(name, options);
35975
36909
  });
35976
36910
  secrets.command("set").description("Set or rotate a secret without exposing its value in argv.").argument("<name>", "Secret name").argument("[forbidden...]", "Do not pass secret values here").option("--json", "Emit JSON output").option("--scope <scope>", "Secret scope: org or play", "org").option("--play <name>", "Play name for play-scoped secrets").option("--value-stdin", "Read the value from piped stdin").option("--from-env <name>", "Read the value from an environment variable").option("--from-file <path>", "Read the value from a file").action(
@@ -36114,7 +37048,7 @@ var import_node_fs19 = require("fs");
36114
37048
  var import_node_os12 = require("os");
36115
37049
  var import_node_path21 = require("path");
36116
37050
 
36117
- // ../../shared_libs/cli/install-commands.json
37051
+ // contracts/install-commands.json
36118
37052
  var install_commands_default = {
36119
37053
  skills: {
36120
37054
  index_path: "/.well-known/skills/index.json",
@@ -36222,6 +37156,7 @@ function buildSkillsAddArgs(baseUrl, skillName, options = {}) {
36222
37156
 
36223
37157
  // src/cli/skills-sync.ts
36224
37158
  var import_node_child_process2 = require("child_process");
37159
+ var import_node_crypto10 = require("crypto");
36225
37160
  var import_node_fs18 = require("fs");
36226
37161
  var import_node_path20 = require("path");
36227
37162
 
@@ -36344,6 +37279,20 @@ function classifyNetworkFailure(error) {
36344
37279
  }
36345
37280
  return "network_error";
36346
37281
  }
37282
+ function classifyNetworkCauseCategory(error) {
37283
+ switch (classifyNetworkFailure(error)) {
37284
+ case "network_dns_resolution_failed":
37285
+ return "dns";
37286
+ case "network_ssl_error":
37287
+ return "tls";
37288
+ case "network_connection_reset":
37289
+ return "reset";
37290
+ case "network_timeout":
37291
+ return "connect_timeout";
37292
+ default:
37293
+ return "unknown";
37294
+ }
37295
+ }
36347
37296
  function isNetworkFailure(error) {
36348
37297
  if (!(error instanceof Error)) return false;
36349
37298
  if (error instanceof DeeplineError && error.statusCode) return false;
@@ -36513,6 +37462,7 @@ function buildFailureReport(input2) {
36513
37462
  });
36514
37463
  const code = failureCode({ exitCode: input2.exitCode, error: input2.error });
36515
37464
  const errorBody = input2.error === void 0 ? `SDK CLI command exited ${input2.exitCode ?? "unknown"}` : errorMessage3(input2.error);
37465
+ const networkCauseCategory = input2.error !== void 0 && isNetworkFailure(input2.error) ? classifyNetworkCauseCategory(input2.error) : null;
36516
37466
  return {
36517
37467
  command: sanitizeCommand(input2.argv),
36518
37468
  subcommand: subcommandFromArgv(input2.argv),
@@ -36534,7 +37484,8 @@ function buildFailureReport(input2) {
36534
37484
  failure_code: code,
36535
37485
  failure_stage: input2.error === void 0 ? "command_exit" : "cli_main",
36536
37486
  duration_ms: durationMs,
36537
- ...input2.exitCode !== null ? { exit_code: input2.exitCode } : {}
37487
+ ...input2.exitCode !== null ? { exit_code: input2.exitCode } : {},
37488
+ ...networkCauseCategory ? { network_cause_category: networkCauseCategory } : {}
36538
37489
  }
36539
37490
  };
36540
37491
  }
@@ -36562,6 +37513,7 @@ async function maybeReportSdkCliFailure(input2) {
36562
37513
 
36563
37514
  // src/cli/skills-sync.ts
36564
37515
  var CHECK_TIMEOUT_MS2 = 3e3;
37516
+ var SKILLS_SYNC_CLAIM_MAX_AGE_MS = 60 * 60 * 1e3;
36565
37517
  function shouldSkipSkillsSync() {
36566
37518
  if (detectAgentRuntime() === "claude_cowork") {
36567
37519
  return true;
@@ -36578,6 +37530,60 @@ function failedSkillsSyncPath(baseUrl, agents) {
36578
37530
  `skills-sync-failed-${agents.join("-")}-version`
36579
37531
  );
36580
37532
  }
37533
+ function inProgressSkillsSyncPath(baseUrl, agents) {
37534
+ return (0, import_node_path20.join)(
37535
+ sdkCliStateDirPath(baseUrl),
37536
+ `skills-sync-in-progress-${agents.join("-")}`
37537
+ );
37538
+ }
37539
+ function removeSkillsSyncClaim(path, token) {
37540
+ try {
37541
+ (0, import_node_fs18.unlinkSync)((0, import_node_path20.join)(path, token));
37542
+ (0, import_node_fs18.rmdirSync)(path);
37543
+ } catch {
37544
+ }
37545
+ }
37546
+ function claimSkillsSync(baseUrl, agents) {
37547
+ const path = inProgressSkillsSyncPath(baseUrl, agents);
37548
+ try {
37549
+ (0, import_node_fs18.mkdirSync)((0, import_node_path20.dirname)(path), { recursive: true });
37550
+ } catch {
37551
+ return { status: "unavailable" };
37552
+ }
37553
+ const token = (0, import_node_crypto10.randomUUID)();
37554
+ for (let attempt = 0; attempt < 2; attempt += 1) {
37555
+ const pendingPath = `${path}-pending-${token}`;
37556
+ try {
37557
+ (0, import_node_fs18.mkdirSync)(pendingPath);
37558
+ (0, import_node_fs18.writeFileSync)((0, import_node_path20.join)(pendingPath, token), "", { flag: "wx" });
37559
+ (0, import_node_fs18.renameSync)(pendingPath, path);
37560
+ return { status: "claimed", token };
37561
+ } catch {
37562
+ removeSkillsSyncClaim(pendingPath, token);
37563
+ if (!(0, import_node_fs18.existsSync)(path)) continue;
37564
+ }
37565
+ if (attempt > 0) return { status: "held" };
37566
+ try {
37567
+ if (Date.now() - (0, import_node_fs18.statSync)(path).mtimeMs <= SKILLS_SYNC_CLAIM_MAX_AGE_MS) {
37568
+ return { status: "held" };
37569
+ }
37570
+ const owners = (0, import_node_fs18.readdirSync)(path);
37571
+ if (owners.length === 0) {
37572
+ (0, import_node_fs18.rmdirSync)(path);
37573
+ continue;
37574
+ }
37575
+ if (owners.length !== 1) return { status: "held" };
37576
+ (0, import_node_fs18.renameSync)(path, `${path}-stale-${owners[0]}`);
37577
+ } catch {
37578
+ return { status: "held" };
37579
+ }
37580
+ }
37581
+ return { status: "held" };
37582
+ }
37583
+ function clearSkillsSyncClaim(baseUrl, agents, token) {
37584
+ const path = inProgressSkillsSyncPath(baseUrl, agents);
37585
+ removeSkillsSyncClaim(path, token);
37586
+ }
36581
37587
  function hasMarkedSkillsSyncVersion(path, version) {
36582
37588
  return Boolean(version) && readMarkedSkillsSyncVersion(path) === version;
36583
37589
  }
@@ -36713,7 +37719,7 @@ function hasCommand(command) {
36713
37719
  });
36714
37720
  return result.status === 0;
36715
37721
  }
36716
- function shellQuote3(arg) {
37722
+ function shellQuote4(arg) {
36717
37723
  return `'${arg.replace(/'/g, `'\\''`)}'`;
36718
37724
  }
36719
37725
  function temporarySkillsSyncSkipCommand() {
@@ -36732,7 +37738,7 @@ function resolveSkillsInstallCommands(baseUrl, skillNames = DEFAULT_SDK_SKILL_NA
36732
37738
  commands.push({
36733
37739
  command: "bunx",
36734
37740
  args: bunxArgs,
36735
- manualCommand: `bunx ${bunxArgs.map(shellQuote3).join(" ")}`
37741
+ manualCommand: `bunx ${bunxArgs.map(shellQuote4).join(" ")}`
36736
37742
  });
36737
37743
  }
36738
37744
  if (hasCommand("npx")) {
@@ -36740,7 +37746,7 @@ function resolveSkillsInstallCommands(baseUrl, skillNames = DEFAULT_SDK_SKILL_NA
36740
37746
  commands.push({
36741
37747
  command: "npx",
36742
37748
  args: npxArgs,
36743
- manualCommand: `npx ${npxArgs.map(shellQuote3).join(" ")}`
37749
+ manualCommand: `npx ${npxArgs.map(shellQuote4).join(" ")}`
36744
37750
  });
36745
37751
  }
36746
37752
  return commands;
@@ -36907,30 +37913,43 @@ async function syncSdkSkillsIfNeeded(baseUrl, options = {}) {
36907
37913
  writeUnavailableSkillsNotice(baseUrl, update.remoteVersion, skillNames);
36908
37914
  return;
36909
37915
  }
36910
- writeSdkSkillsStatusLine("Deepline skills changed; syncing agent skills...");
36911
- const installResult = await runSkillsInstall(installs, agents);
36912
- if (!installResult.installed) {
36913
- const markerWritten = markFailedSkillsSync(
36914
- baseUrl,
36915
- update.remoteVersion,
36916
- agents
37916
+ const claim = claimSkillsSync(baseUrl, agents);
37917
+ if (claim.status === "held") return;
37918
+ try {
37919
+ if (hasFailedSkillsSync(baseUrl, update.remoteVersion, agents) || readSdkSkillsLocalVersion(baseUrl) === update.remoteVersion) {
37920
+ return;
37921
+ }
37922
+ writeSdkSkillsStatusLine(
37923
+ "Deepline skills changed; syncing agent skills..."
36917
37924
  );
36918
- void maybeReportAutomaticSkillsSyncFailure({
36919
- baseUrl,
36920
- remoteSkillsVersion: update.remoteVersion,
36921
- agents,
36922
- skillsPackage: SKILLS_NPX_PACKAGE,
36923
- targetSkillCount: skillNames.length,
36924
- markerWritten,
36925
- attempts: installResult.attempts
36926
- });
36927
- return;
37925
+ const installResult = await runSkillsInstall(installs, agents);
37926
+ if (!installResult.installed) {
37927
+ const markerWritten = markFailedSkillsSync(
37928
+ baseUrl,
37929
+ update.remoteVersion,
37930
+ agents
37931
+ );
37932
+ void maybeReportAutomaticSkillsSyncFailure({
37933
+ baseUrl,
37934
+ remoteSkillsVersion: update.remoteVersion,
37935
+ agents,
37936
+ skillsPackage: SKILLS_NPX_PACKAGE,
37937
+ targetSkillCount: skillNames.length,
37938
+ markerWritten,
37939
+ attempts: installResult.attempts
37940
+ });
37941
+ return;
37942
+ }
37943
+ runLegacySkillsCleanup(agents);
37944
+ writeSdkSkillsLocalVersion(baseUrl, update.remoteVersion, agents);
37945
+ clearUnavailableSkillsNotice(baseUrl);
37946
+ clearFailedSkillsSync(baseUrl, agents);
37947
+ writeSdkSkillsStatusLine("Deepline agent skills are up to date.");
37948
+ } finally {
37949
+ if (claim.status === "claimed") {
37950
+ clearSkillsSyncClaim(baseUrl, agents, claim.token);
37951
+ }
36928
37952
  }
36929
- runLegacySkillsCleanup(agents);
36930
- writeSdkSkillsLocalVersion(baseUrl, update.remoteVersion, agents);
36931
- clearUnavailableSkillsNotice(baseUrl);
36932
- clearFailedSkillsSync(baseUrl, agents);
36933
- writeSdkSkillsStatusLine("Deepline agent skills are up to date.");
36934
37953
  }
36935
37954
 
36936
37955
  // src/cli/commands/skills.ts
@@ -38239,6 +39258,9 @@ Examples:
38239
39258
  });
38240
39259
  }
38241
39260
 
39261
+ // src/cli/commands/settings.ts
39262
+ var import_node_fs24 = require("fs");
39263
+
38242
39264
  // src/cli/update-preferences.ts
38243
39265
  var import_node_fs21 = require("fs");
38244
39266
  var import_node_os14 = require("os");
@@ -38372,6 +39394,8 @@ var SDK_SIDECAR_CRITICAL_PACKAGE_FILES = [
38372
39394
  "dist/index.d.ts",
38373
39395
  "dist/plays/bundle-play-file.mjs",
38374
39396
  "dist/bundling-sources/shared_libs/observability/telemetry.ts",
39397
+ "dist/bundling-sources/shared_libs/integration-contracts/bettercontact/execution-policy.ts",
39398
+ "dist/bundling-sources/shared_libs/integration-contracts/theirstack/execution-policy.ts",
38375
39399
  "dist/bundling-sources/shared_libs/play-runtime/backend.ts",
38376
39400
  "dist/bundling-sources/shared_libs/plays/bundling/index.ts",
38377
39401
  "dist/bundling-sources/shared_libs/product-notifications/contract.ts",
@@ -38549,14 +39573,14 @@ function posixShellQuote(value) {
38549
39573
  function windowsCmdQuote(value) {
38550
39574
  return `"${value.replace(/"/g, '""')}"`;
38551
39575
  }
38552
- function shellQuote4(value) {
39576
+ function shellQuote5(value) {
38553
39577
  if (process.platform === "win32") {
38554
39578
  return /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : windowsCmdQuote(value);
38555
39579
  }
38556
39580
  return posixShellQuote(value);
38557
39581
  }
38558
39582
  function buildSourceUpdateCommand(sourceRoot) {
38559
- const quotedRoot = shellQuote4(sourceRoot);
39583
+ const quotedRoot = shellQuote5(sourceRoot);
38560
39584
  const cdCommand = process.platform === "win32" ? `cd /d ${quotedRoot}` : `cd ${quotedRoot}`;
38561
39585
  return `${cdCommand} && git fetch origin main --tags && git merge --ff-only origin/main`;
38562
39586
  }
@@ -38568,7 +39592,7 @@ function buildSidecarProjectConfigCommand(versionDir, nodeBin) {
38568
39592
  "fs.mkdirSync(dir,{recursive:true});",
38569
39593
  `fs.writeFileSync(path.join(dir,'package.json'),${JSON.stringify(NPM_SDK_SIDECAR_PACKAGE_JSON)});`
38570
39594
  ].join("");
38571
- return `${shellQuote4(nodeBin)} -e ${shellQuote4(script)} ${shellQuote4(versionDir)}`;
39595
+ return `${shellQuote5(nodeBin)} -e ${shellQuote5(script)} ${shellQuote5(versionDir)}`;
38572
39596
  }
38573
39597
  function sidecarStateDir(input2) {
38574
39598
  const scope = input2.env.DEEPLINE_CONFIG_SCOPE?.trim();
@@ -38631,7 +39655,7 @@ function resolvePythonSidecarUpdatePlan(options) {
38631
39655
  const npmCommand = "npm";
38632
39656
  const registryUrl = sidecarRegistryUrl(hostUrl);
38633
39657
  const versionDir = (0, import_node_path25.join)(stateDir, "versions", "<version>");
38634
- const manualCommand = `${buildSidecarProjectConfigCommand(versionDir, nodeBin)} && ${npmCommand} install --prefix ${shellQuote4(versionDir)} --registry ${shellQuote4(registryUrl)} ${NPM_SDK_INSTALL_COMMON_FLAGS.map(shellQuote4).join(" ")} ${shellQuote4(packageSpec)}`;
39658
+ const manualCommand = `${buildSidecarProjectConfigCommand(versionDir, nodeBin)} && ${npmCommand} install --prefix ${shellQuote5(versionDir)} --registry ${shellQuote5(registryUrl)} ${NPM_SDK_INSTALL_COMMON_FLAGS.map(shellQuote5).join(" ")} ${shellQuote5(packageSpec)}`;
38635
39659
  return {
38636
39660
  kind: "python-sidecar",
38637
39661
  stateDir,
@@ -38778,7 +39802,7 @@ function resolveUpdatePlan(options = {}) {
38778
39802
  args,
38779
39803
  ...installPrefix ? { installPrefix } : {},
38780
39804
  ...fallbackRegistryUrl ? { fallbackRegistryUrl } : {},
38781
- manualCommand: `${command} ${args.map(shellQuote4).join(" ")}`
39805
+ manualCommand: `${command} ${args.map(shellQuote5).join(" ")}`
38782
39806
  };
38783
39807
  }
38784
39808
  var AUTO_UPDATE_FAILURE_FILE = ".auto-update-failure.json";
@@ -39046,23 +40070,23 @@ function writeSidecarLauncher(input2) {
39046
40070
  input2.path,
39047
40071
  [
39048
40072
  "#!/usr/bin/env sh",
39049
- `export DEEPLINE_HOST_URL=${shellQuote4(input2.hostUrl)}`,
39050
- `export DEEPLINE_CONFIG_SCOPE=${shellQuote4(input2.scope)}`,
39051
- `if ${criticalPaths.map((path) => `[ ! -f ${shellQuote4(path)} ]`).join(" || ")}; then`,
40073
+ `export DEEPLINE_HOST_URL=${shellQuote5(input2.hostUrl)}`,
40074
+ `export DEEPLINE_CONFIG_SCOPE=${shellQuote5(input2.scope)}`,
40075
+ `if ${criticalPaths.map((path) => `[ ! -f ${shellQuote5(path)} ]`).join(" || ")}; then`,
39052
40076
  ' if [ -n "${DEEPLINE_REAL_BINARY:-}" ] && [ -x "$DEEPLINE_REAL_BINARY" ]; then',
39053
40077
  ' exec "$DEEPLINE_REAL_BINARY" --version=v2 "$@"',
39054
40078
  " fi",
39055
40079
  ' printf "%s\\n" "Deepline SDK CLI install is incomplete. Run \\`deepline update\\` to repair it." >&2',
39056
40080
  " exit 1",
39057
40081
  "fi",
39058
- `if ! ${shellQuote4(input2.nodeBin)} -e ${shellQuote4(esbuildProbe)} ${shellQuote4(versionDir)} >/dev/null 2>&1; then`,
40082
+ `if ! ${shellQuote5(input2.nodeBin)} -e ${shellQuote5(esbuildProbe)} ${shellQuote5(versionDir)} >/dev/null 2>&1; then`,
39059
40083
  ' if [ -n "${DEEPLINE_REAL_BINARY:-}" ] && [ -x "$DEEPLINE_REAL_BINARY" ]; then',
39060
40084
  ' exec "$DEEPLINE_REAL_BINARY" --version=v2 "$@"',
39061
40085
  " fi",
39062
40086
  ' printf "%s\\n" "Deepline SDK CLI install is incomplete. Run \\`deepline update\\` to repair it." >&2',
39063
40087
  " exit 1",
39064
40088
  "fi",
39065
- `exec ${shellQuote4(input2.nodeBin)} ${shellQuote4(input2.entryPath)} "$@"`,
40089
+ `exec ${shellQuote5(input2.nodeBin)} ${shellQuote5(input2.entryPath)} "$@"`,
39066
40090
  ""
39067
40091
  ].join("\n"),
39068
40092
  { encoding: "utf8", mode: 493 }
@@ -39928,21 +40952,104 @@ function displaySlackTarget(target) {
39928
40952
  function collectEvent(value, previous = []) {
39929
40953
  return [...previous, value];
39930
40954
  }
40955
+ function collectNotificationTarget(value, previous = []) {
40956
+ return [...previous, value];
40957
+ }
40958
+ async function readNotificationConfigurationChange(source, read = (path, encoding) => (0, import_node_fs24.readFileSync)(path, encoding)) {
40959
+ const label = source === "-" ? "stdin" : source;
40960
+ let text;
40961
+ try {
40962
+ text = await read(source === "-" ? 0 : source, "utf8");
40963
+ } catch (error) {
40964
+ throw new Error(
40965
+ `Could not read notification configuration from ${label}: ${error instanceof Error ? error.message : String(error)}`
40966
+ );
40967
+ }
40968
+ let parsed;
40969
+ try {
40970
+ parsed = JSON.parse(text);
40971
+ } catch (error) {
40972
+ throw new Error(
40973
+ `Notification configuration from ${label} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
40974
+ );
40975
+ }
40976
+ if (typeof parsed.expectedWorkspaceVersion !== "number" || !Number.isInteger(parsed.expectedWorkspaceVersion) || !parsed.change || typeof parsed.change !== "object") {
40977
+ throw new Error(
40978
+ `Notification configuration from ${label} must contain an integer expectedWorkspaceVersion and a change object.`
40979
+ );
40980
+ }
40981
+ return {
40982
+ expectedWorkspaceVersion: parsed.expectedWorkspaceVersion,
40983
+ change: parsed.change
40984
+ };
40985
+ }
39931
40986
  function registerNotificationCommands(program) {
39932
- const notifications = program.command("notifications").description("Choose which Play outcomes notify which people and channels.").addHelpText(
40987
+ const notifications = program.command("notifications").description("Configure notification groups, destinations, and rules.").addHelpText(
39933
40988
  "after",
39934
40989
  `
39935
40990
  Examples:
40991
+ deepline notifications add oncall --to email:oncall@example.com --to slack:#oncall
39936
40992
  deepline notifications events
39937
40993
  deepline notifications slack channels --search pipeline
39938
40994
  deepline notifications add pipeline-watchdog --to slack:#pipeline-alerts --for play.cron.failed
39939
40995
  deepline notifications add owner-alerts --to slack:member:U0123456789 --for play.cron.failed
39940
40996
  deepline notifications test pipeline-watchdog
39941
40997
 
39942
- Slack connections are managed in Dashboard \u2192 Integrations. This command only
39943
- chooses the connected Slack channel or member and the events it receives.
40998
+ Each named notification is a group. Repeat --to to give it email and Slack
40999
+ destinations. Add rules from the dashboard or notification configuration. The
41000
+ existing one-Slack-target form with --for remains supported and creates the
41001
+ equivalent compatibility group and rules. Slack connections are managed in
41002
+ Dashboard \u2192 Integrations.
41003
+ `
41004
+ );
41005
+ const configuration = notifications.command("configuration").description("Read or atomically update notification groups and rules.").addHelpText(
41006
+ "after",
41007
+ `
41008
+ Examples:
41009
+ deepline notifications configuration get --json
41010
+ deepline notifications configuration apply change.json --dry-run --json
41011
+ jq '{expectedWorkspaceVersion: .configuration.workspaceVersion, change: {kind: "delete_group", groupKey: "oncall"}}' current.json | deepline notifications configuration apply - --json
39944
41012
  `
39945
41013
  );
41014
+ configuration.command("get").description("Show the effective group-based notification configuration.").option("--json", "Emit JSON output").action(async (options) => {
41015
+ const result = await new DeeplineClient().getNotificationConfiguration();
41016
+ printCommandEnvelope(
41017
+ {
41018
+ ...result,
41019
+ render: {
41020
+ sections: [
41021
+ {
41022
+ title: "notification groups",
41023
+ lines: result.configuration.groups.length ? result.configuration.groups.map(
41024
+ (group) => `${group.name}: ${group.enabled ? "active" : "paused"}`
41025
+ ) : ["No notification groups are configured."]
41026
+ }
41027
+ ]
41028
+ }
41029
+ },
41030
+ { json: options.json }
41031
+ );
41032
+ });
41033
+ configuration.command("apply").argument(
41034
+ "<file>",
41035
+ "JSON file, or - for stdin, containing expectedWorkspaceVersion and one desired change"
41036
+ ).description(
41037
+ "Atomically apply a configuration draft exported by an agent or UI."
41038
+ ).option("--dry-run", "Validate and display the file without saving it").option("--json", "Emit JSON output").action(async (file, options) => {
41039
+ const input2 = await readNotificationConfigurationChange(file);
41040
+ if (options.dryRun) {
41041
+ printCommandEnvelope(
41042
+ { dryRun: true, ...input2 },
41043
+ { json: options.json }
41044
+ );
41045
+ return;
41046
+ }
41047
+ const result = await new DeeplineClient().changeNotificationConfiguration(
41048
+ input2.expectedWorkspaceVersion,
41049
+ input2.change
41050
+ );
41051
+ printCommandEnvelope(result, { json: options.json });
41052
+ });
39946
41053
  notifications.command("events").description("List the Play events available to a notification.").option("--json", "Emit JSON output").action(async (options) => {
39947
41054
  const settings = await new DeeplineClient().getNotifications();
39948
41055
  printCommandEnvelope(
@@ -39998,42 +41105,82 @@ chooses the connected Slack channel or member and the events it receives.
39998
41105
  );
39999
41106
  printCommandEnvelope({ notification: rule }, { json: options.json });
40000
41107
  });
40001
- notifications.command("add").argument("<name>", "Short stable name, for example pipeline-watchdog").requiredOption(
41108
+ notifications.command("add").argument("<name>", "Group name, for example oncall").requiredOption(
40002
41109
  "--to <target>",
40003
- "Target, for example slack:#pipeline-alerts or slack:member:U0123456789"
41110
+ "Target; repeat email:name@example.com, slack:#channel, or slack:member:U0123456789",
41111
+ collectNotificationTarget,
41112
+ []
40004
41113
  ).option(
40005
41114
  "--for <event>",
40006
- "Event ID; repeat for more events. Run notifications events to list them",
41115
+ "Legacy one-Slack-target event ID; repeat for more events",
40007
41116
  collectEvent,
40008
41117
  []
40009
41118
  ).option("--dry-run", "Describe the notification without saving it").option("--json", "Emit JSON output").action(
40010
41119
  async (name, options) => {
40011
- if (!options.for.length) {
40012
- throw new Error("Choose at least one event with --for <event>.");
41120
+ const result = await new DeeplineClient().createNotificationGroup({
41121
+ name,
41122
+ targets: options.to,
41123
+ legacyEvents: options.for,
41124
+ dryRun: options.dryRun
41125
+ });
41126
+ if (result.mode === "dry_run") {
41127
+ printCommandEnvelope(
41128
+ result.compatibilityMode ? {
41129
+ dryRun: true,
41130
+ name: result.name,
41131
+ target: options.to[0],
41132
+ eventTypes: result.legacyEvents
41133
+ } : {
41134
+ dryRun: true,
41135
+ group: result.name,
41136
+ targets: result.targets,
41137
+ compatibilityMode: false
41138
+ },
41139
+ { json: options.json }
41140
+ );
41141
+ return;
40013
41142
  }
40014
- const target = parseSlackTarget(options.to);
40015
- if (options.dryRun) {
41143
+ if (result.mode === "legacy") {
40016
41144
  printCommandEnvelope(
40017
- { dryRun: true, name, target: options.to, eventTypes: options.for },
41145
+ {
41146
+ notification: result.notification,
41147
+ render: {
41148
+ sections: [
41149
+ {
41150
+ title: "notification saved",
41151
+ lines: [name]
41152
+ }
41153
+ ],
41154
+ actions: [
41155
+ {
41156
+ label: "Send a test",
41157
+ command: `deepline notifications test ${name}`
41158
+ }
41159
+ ]
41160
+ }
41161
+ },
40018
41162
  { json: options.json }
40019
41163
  );
40020
41164
  return;
40021
41165
  }
40022
- const result = await new DeeplineClient().createNotification({
40023
- name,
40024
- provider: "slack",
40025
- ...target,
40026
- eventTypes: options.for
40027
- });
40028
41166
  printCommandEnvelope(
40029
41167
  {
40030
- notification: result,
41168
+ ...result,
40031
41169
  render: {
40032
- sections: [{ title: "notification saved", lines: [name] }],
41170
+ sections: [
41171
+ {
41172
+ title: "notification group saved",
41173
+ lines: [
41174
+ result.group.name,
41175
+ `${result.destinations.length} destination${result.destinations.length === 1 ? "" : "s"}`,
41176
+ "0 rules"
41177
+ ]
41178
+ }
41179
+ ],
40033
41180
  actions: [
40034
41181
  {
40035
- label: "Send a test",
40036
- command: `deepline notifications test ${name}`
41182
+ label: "View configuration",
41183
+ command: "deepline notifications configuration get"
40037
41184
  }
40038
41185
  ]
40039
41186
  }
@@ -40147,12 +41294,12 @@ chooses the connected Slack channel or member and the events it receives.
40147
41294
 
40148
41295
  // src/cli/commands/tools.ts
40149
41296
  var import_commander4 = require("commander");
40150
- var import_node_fs25 = require("fs");
41297
+ var import_node_fs26 = require("fs");
40151
41298
  var import_node_os17 = require("os");
40152
41299
  var import_node_path27 = require("path");
40153
41300
 
40154
41301
  // src/tool-output.ts
40155
- var import_node_fs24 = require("fs");
41302
+ var import_node_fs25 = require("fs");
40156
41303
  var import_node_os16 = require("os");
40157
41304
  var import_node_path26 = require("path");
40158
41305
  function isPlainObject(value) {
@@ -40282,18 +41429,18 @@ function projectRowOutput(conversion) {
40282
41429
  }
40283
41430
  function ensureOutputDir() {
40284
41431
  const outputDir = (0, import_node_path26.join)((0, import_node_os16.homedir)(), ".local", "share", "deepline", "data");
40285
- (0, import_node_fs24.mkdirSync)(outputDir, { recursive: true });
41432
+ (0, import_node_fs25.mkdirSync)(outputDir, { recursive: true });
40286
41433
  return outputDir;
40287
41434
  }
40288
41435
  function writeJsonOutputFile(payload, stem) {
40289
41436
  const outputDir = ensureOutputDir();
40290
41437
  const outputPath = (0, import_node_path26.join)(outputDir, `${stem}_${Date.now()}.json`);
40291
- (0, import_node_fs24.writeFileSync)(outputPath, JSON.stringify(payload, null, 2), "utf-8");
41438
+ (0, import_node_fs25.writeFileSync)(outputPath, JSON.stringify(payload, null, 2), "utf-8");
40292
41439
  return outputPath;
40293
41440
  }
40294
41441
  function writeCsvOutputFile(rows, stem, options) {
40295
41442
  const outputPath = options?.outPath ? options.outPath : (0, import_node_path26.join)(ensureOutputDir(), `${stem}_${Date.now()}.csv`);
40296
- (0, import_node_fs24.mkdirSync)((0, import_node_path26.dirname)(outputPath), { recursive: true });
41443
+ (0, import_node_fs25.mkdirSync)((0, import_node_path26.dirname)(outputPath), { recursive: true });
40297
41444
  const columns = columnsForRows(rows);
40298
41445
  const escapeCell = (value) => {
40299
41446
  const normalized = value == null ? "" : typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : JSON.stringify(value);
@@ -40302,19 +41449,19 @@ function writeCsvOutputFile(rows, stem, options) {
40302
41449
  }
40303
41450
  return normalized;
40304
41451
  };
40305
- const fd = (0, import_node_fs24.openSync)(outputPath, "w");
41452
+ const fd = (0, import_node_fs25.openSync)(outputPath, "w");
40306
41453
  try {
40307
- (0, import_node_fs24.writeSync)(fd, `${columns.map(escapeCell).join(",")}
41454
+ (0, import_node_fs25.writeSync)(fd, `${columns.map(escapeCell).join(",")}
40308
41455
  `);
40309
41456
  for (const row of rows) {
40310
- (0, import_node_fs24.writeSync)(
41457
+ (0, import_node_fs25.writeSync)(
40311
41458
  fd,
40312
41459
  `${columns.map((column) => escapeCell(row[column])).join(",")}
40313
41460
  `
40314
41461
  );
40315
41462
  }
40316
41463
  } finally {
40317
- (0, import_node_fs24.closeSync)(fd);
41464
+ (0, import_node_fs25.closeSync)(fd);
40318
41465
  }
40319
41466
  const previewRows = rows.slice(0, 5);
40320
41467
  const previewColumns = columns.slice(0, 5);
@@ -41008,6 +42155,9 @@ async function findPlayForToolId(client2, toolId) {
41008
42155
  const plays = await client2.searchPlays({ query: requested, compact: true });
41009
42156
  return plays.find((play) => playIdentifiers(play).includes(requested)) ?? null;
41010
42157
  }
42158
+ function isMissingToolMetadataError(error) {
42159
+ return error instanceof DeeplineError && error.statusCode === 404;
42160
+ }
41011
42161
  function playAliasToolErrorMessage(toolId, play) {
41012
42162
  const playName = play.reference ?? play.name;
41013
42163
  return `${toolId} is a play, not a tool.
@@ -41289,10 +42439,12 @@ async function getTool(toolId, options = {}) {
41289
42439
  try {
41290
42440
  tool = await client2.getTool(toolId);
41291
42441
  } catch (error) {
41292
- const play = await findPlayForToolId(client2, toolId);
41293
- if (play) {
41294
- printPlayAliasToolError(toolId, play);
41295
- return 2;
42442
+ if (isMissingToolMetadataError(error)) {
42443
+ const play = await findPlayForToolId(client2, toolId);
42444
+ if (play) {
42445
+ printPlayAliasToolError(toolId, play);
42446
+ return 2;
42447
+ }
41296
42448
  }
41297
42449
  throw error;
41298
42450
  }
@@ -42192,9 +43344,9 @@ function apifySyncRecoveryNext(rawResponse) {
42192
43344
  const getDatasetItemsTool = stringField2(getDatasetItems, "tool");
42193
43345
  const getDatasetItemsPayload = recordField2(getDatasetItems, "payload");
42194
43346
  return {
42195
- getActorRun: `deepline tools execute ${getActorRunTool} --input ${shellQuote5(JSON.stringify(getActorRunPayload))} --json`,
43347
+ getActorRun: `deepline tools execute ${getActorRunTool} --input ${shellQuote6(JSON.stringify(getActorRunPayload))} --json`,
42196
43348
  ...getDatasetItemsTool && Object.keys(getDatasetItemsPayload).length > 0 ? {
42197
- getDatasetItems: `deepline tools execute ${getDatasetItemsTool} --input ${shellQuote5(JSON.stringify(getDatasetItemsPayload))} --json`
43349
+ getDatasetItems: `deepline tools execute ${getDatasetItemsTool} --input ${shellQuote6(JSON.stringify(getDatasetItemsPayload))} --json`
42198
43350
  } : {}
42199
43351
  };
42200
43352
  }
@@ -42266,10 +43418,10 @@ function normalizeOutputFormat(raw) {
42266
43418
  function resolveAtFilePath(rawPath) {
42267
43419
  const trimmed = rawPath.trim();
42268
43420
  const resolved = (0, import_node_path27.resolve)(trimmed);
42269
- if ((0, import_node_fs25.existsSync)(resolved)) return resolved;
43421
+ if ((0, import_node_fs26.existsSync)(resolved)) return resolved;
42270
43422
  if (process.platform !== "win32" && trimmed.includes("\\")) {
42271
43423
  const normalized = (0, import_node_path27.resolve)(trimmed.replace(/\\/g, "/"));
42272
- if ((0, import_node_fs25.existsSync)(normalized)) return normalized;
43424
+ if ((0, import_node_fs26.existsSync)(normalized)) return normalized;
42273
43425
  }
42274
43426
  return resolved;
42275
43427
  }
@@ -42280,7 +43432,7 @@ function readJsonArgument(raw, flagName) {
42280
43432
  throw new Error(`Invalid ${flagName} value: empty @file path.`);
42281
43433
  }
42282
43434
  try {
42283
- return (0, import_node_fs25.readFileSync)(resolveAtFilePath(filePath), "utf8").replace(
43435
+ return (0, import_node_fs26.readFileSync)(resolveAtFilePath(filePath), "utf8").replace(
42284
43436
  /^\uFEFF/,
42285
43437
  ""
42286
43438
  );
@@ -42367,7 +43519,7 @@ function parseExecuteOptions(args) {
42367
43519
  function safeFileStem(value) {
42368
43520
  return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "tool";
42369
43521
  }
42370
- function shellQuote5(value) {
43522
+ function shellQuote6(value) {
42371
43523
  return `'${value.replace(/'/g, `'\\''`)}'`;
42372
43524
  }
42373
43525
  function powerShellQuote(value) {
@@ -42387,8 +43539,8 @@ function starterScriptJson(script) {
42387
43539
  function seedToolListScript(input2) {
42388
43540
  const stem = safeFileStem(input2.toolId);
42389
43541
  const fileName = `${stem}-workflow-seed-${Date.now()}.play.ts`;
42390
- const scriptDir = (0, import_node_fs25.mkdtempSync)((0, import_node_path27.join)((0, import_node_os17.tmpdir)(), "deepline-workflow-seed-"));
42391
- (0, import_node_fs25.chmodSync)(scriptDir, 448);
43542
+ const scriptDir = (0, import_node_fs26.mkdtempSync)((0, import_node_path27.join)((0, import_node_os17.tmpdir)(), "deepline-workflow-seed-"));
43543
+ (0, import_node_fs26.chmodSync)(scriptDir, 448);
42392
43544
  const scriptPath = (0, import_node_path27.join)(scriptDir, fileName);
42393
43545
  const projectDir = `deepline/projects/${stem}-workflow`;
42394
43546
  const playName = `${stem}-workflow`;
@@ -42432,12 +43584,12 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
42432
43584
  description: ${JSON.stringify(`Seed ${input2.toolId} rows into a Deepline workflow-ready dataset.`)},
42433
43585
  });
42434
43586
  `;
42435
- (0, import_node_fs25.writeFileSync)(scriptPath, script, { encoding: "utf-8", mode: 384 });
43587
+ (0, import_node_fs26.writeFileSync)(scriptPath, script, { encoding: "utf-8", mode: 384 });
42436
43588
  return {
42437
43589
  path: scriptPath,
42438
43590
  sourceCode: script,
42439
43591
  projectDir,
42440
- macCopyCommand: `mkdir -p ${shellQuote5(projectDir)} && cp ${shellQuote5(scriptPath)} ${shellQuote5(`${projectDir}/${fileName}`)}`,
43592
+ macCopyCommand: `mkdir -p ${shellQuote6(projectDir)} && cp ${shellQuote6(scriptPath)} ${shellQuote6(`${projectDir}/${fileName}`)}`,
42441
43593
  windowsCopyCommand: `New-Item -ItemType Directory -Force -Path ${powerShellQuote(projectDir.replace(/\//g, "\\"))} | Out-Null; Copy-Item -LiteralPath ${powerShellQuote(scriptPath)} -Destination ${powerShellQuote(`${projectDir.replace(/\//g, "\\")}\\${fileName}`)}`
42442
43594
  };
42443
43595
  }
@@ -42464,7 +43616,7 @@ function buildToolExecuteBaseEnvelope(input2) {
42464
43616
  envelope,
42465
43617
  "output"
42466
43618
  );
42467
- const inspectCommand = `deepline tools execute ${input2.toolId} --input ${shellQuote5(JSON.stringify(input2.params))} --json`;
43619
+ const inspectCommand = `deepline tools execute ${input2.toolId} --input ${shellQuote6(JSON.stringify(input2.params))} --json`;
42468
43620
  const actions = input2.listConversion ? [
42469
43621
  {
42470
43622
  label: "next",
@@ -42631,19 +43783,18 @@ async function executeTool(args) {
42631
43783
  try {
42632
43784
  metadata = await client2.getTool(parsed.toolId);
42633
43785
  } catch (error) {
42634
- const play = await findPlayForToolId(client2, parsed.toolId);
42635
- if (play) {
42636
- if (argsWantJson(args)) {
42637
- printJsonError(
42638
- new Error(playAliasToolErrorMessage(parsed.toolId, play))
42639
- );
42640
- } else {
42641
- printPlayAliasToolError(parsed.toolId, play);
43786
+ if (isMissingToolMetadataError(error)) {
43787
+ const play = await findPlayForToolId(client2, parsed.toolId);
43788
+ if (play) {
43789
+ if (argsWantJson(args)) {
43790
+ printJsonError(
43791
+ new Error(playAliasToolErrorMessage(parsed.toolId, play))
43792
+ );
43793
+ } else {
43794
+ printPlayAliasToolError(parsed.toolId, play);
43795
+ }
43796
+ return 2;
42642
43797
  }
42643
- return 2;
42644
- }
42645
- if (error instanceof DeeplineError) {
42646
- throw error;
42647
43798
  }
42648
43799
  throw error;
42649
43800
  }
@@ -42861,7 +44012,7 @@ var import_promises10 = require("fs/promises");
42861
44012
  var import_node_path28 = require("path");
42862
44013
 
42863
44014
  // src/cli/workflow-to-play.ts
42864
- var import_node_crypto10 = require("crypto");
44015
+ var import_node_crypto11 = require("crypto");
42865
44016
  var HITL_WAIT_FOR_SIGNAL_TOOL = "deepline_workflow_wait_for_signal";
42866
44017
  var HITL_SLACK_TOOL = "slack_message_with_hitl";
42867
44018
  var SUB_WORKFLOW_TOOL_PREFIX = "deepline_workflow_";
@@ -42967,7 +44118,7 @@ function sanitizePlayNameSegment(value) {
42967
44118
  }
42968
44119
  function deriveWorkflowPlayName(workflowName) {
42969
44120
  const base = sanitizePlayNameSegment(workflowName) || "workflow";
42970
- const suffix = (0, import_node_crypto10.createHash)("sha256").update(workflowName).digest("hex").slice(0, 8);
44121
+ const suffix = (0, import_node_crypto11.createHash)("sha256").update(workflowName).digest("hex").slice(0, 8);
42971
44122
  const reserved = suffix.length + 1;
42972
44123
  const allowedBase = Math.max(1, MAX_PLAY_NAME_LENGTH - reserved);
42973
44124
  let name = `${base.slice(0, allowedBase)}_${suffix}`;
@@ -43285,7 +44436,7 @@ async function handleTail(id, runId, options) {
43285
44436
  }
43286
44437
  }
43287
44438
  function registerWorkflowCommands(program) {
43288
- const workflows = program.command("workflows").description(
44439
+ const workflows = program.command("workflows", { hidden: true }).description(
43289
44440
  "Legacy cloud workflows (now becoming plays). Commands still work; use `workflows transform` to migrate one to a play. " + TRIGGER_REDIRECT
43290
44441
  ).addHelpText(
43291
44442
  "after",
@@ -44105,4 +45256,11 @@ Examples:
44105
45256
  }
44106
45257
  process.exitCode = process.exitCode ?? 0;
44107
45258
  }
44108
- main();
45259
+ var DEV_CLI_AWAIT_MAIN = Symbol.for("@deepline/sdk/dev-cli-await-main");
45260
+ if (globalThis[DEV_CLI_AWAIT_MAIN] !== true) {
45261
+ void main();
45262
+ }
45263
+ // Annotate the CommonJS export names for ESM import in node:
45264
+ 0 && (module.exports = {
45265
+ main
45266
+ });