primitive-admin 1.0.63 → 1.0.64

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 (328) hide show
  1. package/README.md +132 -42
  2. package/assets/skill/skills/primitive-platform/SKILL.md +142 -661
  3. package/dist/bin/primitive.js +121 -41
  4. package/dist/bin/primitive.js.map +1 -1
  5. package/dist/src/commands/admins.js +37 -50
  6. package/dist/src/commands/admins.js.map +1 -1
  7. package/dist/src/commands/analytics.js +178 -9
  8. package/dist/src/commands/analytics.js.map +1 -1
  9. package/dist/src/commands/apps.js +12 -5
  10. package/dist/src/commands/apps.js.map +1 -1
  11. package/dist/src/commands/auth-sessions.d.ts +7 -0
  12. package/dist/src/commands/auth-sessions.js +144 -0
  13. package/dist/src/commands/auth-sessions.js.map +1 -0
  14. package/dist/src/commands/auth.js +76 -116
  15. package/dist/src/commands/auth.js.map +1 -1
  16. package/dist/src/commands/blob-buckets.js +2 -1
  17. package/dist/src/commands/blob-buckets.js.map +1 -1
  18. package/dist/src/commands/catalog.js +26 -10
  19. package/dist/src/commands/catalog.js.map +1 -1
  20. package/dist/src/commands/collection-type-configs.js +3 -10
  21. package/dist/src/commands/collection-type-configs.js.map +1 -1
  22. package/dist/src/commands/collections.js +736 -19
  23. package/dist/src/commands/collections.js.map +1 -1
  24. package/dist/src/commands/config.js +13 -27
  25. package/dist/src/commands/config.js.map +1 -1
  26. package/dist/src/commands/connections.js +8 -9
  27. package/dist/src/commands/connections.js.map +1 -1
  28. package/dist/src/commands/cron-triggers.js +2 -1
  29. package/dist/src/commands/cron-triggers.js.map +1 -1
  30. package/dist/src/commands/database-type-configs.js +5 -12
  31. package/dist/src/commands/database-type-configs.js.map +1 -1
  32. package/dist/src/commands/databases.js +59 -72
  33. package/dist/src/commands/databases.js.map +1 -1
  34. package/dist/src/commands/documents.d.ts +58 -0
  35. package/dist/src/commands/documents.js +1623 -95
  36. package/dist/src/commands/documents.js.map +1 -1
  37. package/dist/src/commands/email-templates.js +2 -1
  38. package/dist/src/commands/email-templates.js.map +1 -1
  39. package/dist/src/commands/env.d.ts +1 -1
  40. package/dist/src/commands/env.js +89 -43
  41. package/dist/src/commands/env.js.map +1 -1
  42. package/dist/src/commands/feature-flags.js +2 -1
  43. package/dist/src/commands/feature-flags.js.map +1 -1
  44. package/dist/src/commands/functions.d.ts +20 -0
  45. package/dist/src/commands/functions.js +1630 -0
  46. package/dist/src/commands/functions.js.map +1 -0
  47. package/dist/src/commands/group-type-configs.js +2 -1
  48. package/dist/src/commands/group-type-configs.js.map +1 -1
  49. package/dist/src/commands/groups.js +30 -17
  50. package/dist/src/commands/groups.js.map +1 -1
  51. package/dist/src/commands/guides.js +17 -11
  52. package/dist/src/commands/guides.js.map +1 -1
  53. package/dist/src/commands/init.d.ts +12 -15
  54. package/dist/src/commands/init.js +44 -92
  55. package/dist/src/commands/init.js.map +1 -1
  56. package/dist/src/commands/integrations.js +35 -12
  57. package/dist/src/commands/integrations.js.map +1 -1
  58. package/dist/src/commands/locks.js +17 -2
  59. package/dist/src/commands/locks.js.map +1 -1
  60. package/dist/src/commands/metadata-category-configs.js +2 -1
  61. package/dist/src/commands/metadata-category-configs.js.map +1 -1
  62. package/dist/src/commands/metadata.js +8 -1
  63. package/dist/src/commands/metadata.js.map +1 -1
  64. package/dist/src/commands/prompts.js +106 -59
  65. package/dist/src/commands/prompts.js.map +1 -1
  66. package/dist/src/commands/rule-sets.js +3 -10
  67. package/dist/src/commands/rule-sets.js.map +1 -1
  68. package/dist/src/commands/scripts.js +33 -28
  69. package/dist/src/commands/scripts.js.map +1 -1
  70. package/dist/src/commands/secrets.js +2 -1
  71. package/dist/src/commands/secrets.js.map +1 -1
  72. package/dist/src/commands/sessions.js +10 -9
  73. package/dist/src/commands/sessions.js.map +1 -1
  74. package/dist/src/commands/sync-app-settings.js +3 -5
  75. package/dist/src/commands/sync-app-settings.js.map +1 -1
  76. package/dist/src/commands/sync.d.ts +390 -13
  77. package/dist/src/commands/sync.js +2613 -451
  78. package/dist/src/commands/sync.js.map +1 -1
  79. package/dist/src/commands/tokens.js +2 -1
  80. package/dist/src/commands/tokens.js.map +1 -1
  81. package/dist/src/commands/users.js +19 -10
  82. package/dist/src/commands/users.js.map +1 -1
  83. package/dist/src/commands/vars.js +2 -1
  84. package/dist/src/commands/vars.js.map +1 -1
  85. package/dist/src/commands/waitlist.js +2 -1
  86. package/dist/src/commands/waitlist.js.map +1 -1
  87. package/dist/src/commands/webhooks.d.ts +29 -0
  88. package/dist/src/commands/webhooks.js +135 -64
  89. package/dist/src/commands/webhooks.js.map +1 -1
  90. package/dist/src/commands/workflows.d.ts +1 -29
  91. package/dist/src/commands/workflows.js +113 -179
  92. package/dist/src/commands/workflows.js.map +1 -1
  93. package/dist/src/lib/api-client.d.ts +628 -14
  94. package/dist/src/lib/api-client.js +776 -70
  95. package/dist/src/lib/api-client.js.map +1 -1
  96. package/dist/src/lib/app-settings-descriptor.d.ts +1 -1
  97. package/dist/src/lib/app-settings-descriptor.js +9 -1
  98. package/dist/src/lib/app-settings-descriptor.js.map +1 -1
  99. package/dist/src/lib/block-selector.d.ts +58 -0
  100. package/dist/src/lib/block-selector.js +92 -0
  101. package/dist/src/lib/block-selector.js.map +1 -0
  102. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +24 -54
  103. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +31 -153
  104. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -1
  105. package/dist/src/lib/collection-export.d.ts +184 -0
  106. package/dist/src/lib/collection-export.js +252 -0
  107. package/dist/src/lib/collection-export.js.map +1 -0
  108. package/dist/src/lib/config-json-field.d.ts +28 -0
  109. package/dist/src/lib/config-json-field.js +56 -0
  110. package/dist/src/lib/config-json-field.js.map +1 -0
  111. package/dist/src/lib/config-object-descriptor.js +88 -6
  112. package/dist/src/lib/config-object-descriptor.js.map +1 -1
  113. package/dist/src/lib/config-payload.d.ts +8 -1
  114. package/dist/src/lib/config-payload.js +49 -4
  115. package/dist/src/lib/config-payload.js.map +1 -1
  116. package/dist/src/lib/config-surface.d.ts +12 -1
  117. package/dist/src/lib/config-surface.js +71 -3
  118. package/dist/src/lib/config-surface.js.map +1 -1
  119. package/dist/src/lib/config.d.ts +31 -13
  120. package/dist/src/lib/config.js +34 -70
  121. package/dist/src/lib/config.js.map +1 -1
  122. package/dist/src/lib/credentials-store.d.ts +37 -22
  123. package/dist/src/lib/credentials-store.js +103 -97
  124. package/dist/src/lib/credentials-store.js.map +1 -1
  125. package/dist/src/lib/db-codegen/dbTypeIR.js +8 -0
  126. package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -1
  127. package/dist/src/lib/deprecation.d.ts +22 -0
  128. package/dist/src/lib/deprecation.js +43 -0
  129. package/dist/src/lib/deprecation.js.map +1 -0
  130. package/dist/src/lib/document-export-permissions.d.ts +30 -0
  131. package/dist/src/lib/document-export-permissions.js +54 -0
  132. package/dist/src/lib/document-export-permissions.js.map +1 -0
  133. package/dist/src/lib/document-ingest-artifact.d.ts +120 -0
  134. package/dist/src/lib/document-ingest-artifact.js +505 -0
  135. package/dist/src/lib/document-ingest-artifact.js.map +1 -0
  136. package/dist/src/lib/document-ingest-input.d.ts +51 -0
  137. package/dist/src/lib/document-ingest-input.js +132 -0
  138. package/dist/src/lib/document-ingest-input.js.map +1 -0
  139. package/dist/src/lib/document-ingest-rows.d.ts +137 -0
  140. package/dist/src/lib/document-ingest-rows.js +181 -0
  141. package/dist/src/lib/document-ingest-rows.js.map +1 -0
  142. package/dist/src/lib/document-ingest.d.ts +101 -0
  143. package/dist/src/lib/document-ingest.js +384 -0
  144. package/dist/src/lib/document-ingest.js.map +1 -0
  145. package/dist/src/lib/env-resolver-core.d.ts +89 -18
  146. package/dist/src/lib/env-resolver-core.js +145 -23
  147. package/dist/src/lib/env-resolver-core.js.map +1 -1
  148. package/dist/src/lib/env-resolver.d.ts +18 -3
  149. package/dist/src/lib/env-resolver.js +23 -3
  150. package/dist/src/lib/env-resolver.js.map +1 -1
  151. package/dist/src/lib/function-bundle.d.ts +147 -0
  152. package/dist/src/lib/function-bundle.js +341 -0
  153. package/dist/src/lib/function-bundle.js.map +1 -0
  154. package/dist/src/lib/function-collect.d.ts +123 -0
  155. package/dist/src/lib/function-collect.js +610 -0
  156. package/dist/src/lib/function-collect.js.map +1 -0
  157. package/dist/src/lib/function-db-types.d.ts +202 -0
  158. package/dist/src/lib/function-db-types.js +870 -0
  159. package/dist/src/lib/function-db-types.js.map +1 -0
  160. package/dist/src/lib/function-document-types.d.ts +144 -0
  161. package/dist/src/lib/function-document-types.js +370 -0
  162. package/dist/src/lib/function-document-types.js.map +1 -0
  163. package/dist/src/lib/function-grants-preflight.d.ts +64 -0
  164. package/dist/src/lib/function-grants-preflight.js +105 -0
  165. package/dist/src/lib/function-grants-preflight.js.map +1 -0
  166. package/dist/src/lib/function-log-lines.d.ts +76 -0
  167. package/dist/src/lib/function-log-lines.js +160 -0
  168. package/dist/src/lib/function-log-lines.js.map +1 -0
  169. package/dist/src/lib/function-log-row.d.ts +29 -0
  170. package/dist/src/lib/function-log-row.js +73 -0
  171. package/dist/src/lib/function-log-row.js.map +1 -0
  172. package/dist/src/lib/function-log-tail.d.ts +132 -0
  173. package/dist/src/lib/function-log-tail.js +262 -0
  174. package/dist/src/lib/function-log-tail.js.map +1 -0
  175. package/dist/src/lib/function-run.d.ts +289 -0
  176. package/dist/src/lib/function-run.js +389 -0
  177. package/dist/src/lib/function-run.js.map +1 -0
  178. package/dist/src/lib/function-schema-codegen.d.ts +143 -0
  179. package/dist/src/lib/function-schema-codegen.js +420 -0
  180. package/dist/src/lib/function-schema-codegen.js.map +1 -0
  181. package/dist/src/lib/function-sync.d.ts +459 -0
  182. package/dist/src/lib/function-sync.js +1258 -0
  183. package/dist/src/lib/function-sync.js.map +1 -0
  184. package/dist/src/lib/function-trigger-listing.d.ts +27 -0
  185. package/dist/src/lib/function-trigger-listing.js +70 -0
  186. package/dist/src/lib/function-trigger-listing.js.map +1 -0
  187. package/dist/src/lib/function-typecheck.d.ts +86 -0
  188. package/dist/src/lib/function-typecheck.js +370 -0
  189. package/dist/src/lib/function-typecheck.js.map +1 -0
  190. package/dist/src/lib/function-versions.d.ts +122 -0
  191. package/dist/src/lib/function-versions.js +182 -0
  192. package/dist/src/lib/function-versions.js.map +1 -0
  193. package/dist/src/lib/generated-allowlist.js +4 -0
  194. package/dist/src/lib/generated-allowlist.js.map +1 -1
  195. package/dist/src/lib/generated-config-surfaces.d.ts +2332 -78
  196. package/dist/src/lib/generated-config-surfaces.js +6979 -468
  197. package/dist/src/lib/generated-config-surfaces.js.map +1 -1
  198. package/dist/src/lib/generated-sdk-types.d.ts +12 -0
  199. package/dist/src/lib/generated-sdk-types.js +13 -0
  200. package/dist/src/lib/generated-sdk-types.js.map +1 -0
  201. package/dist/src/lib/init-adopt.js +2 -2
  202. package/dist/src/lib/init-adopt.js.map +1 -1
  203. package/dist/src/lib/init-assets.js +3 -3
  204. package/dist/src/lib/init-config.d.ts +1 -1
  205. package/dist/src/lib/init-ios-links.d.ts +6 -5
  206. package/dist/src/lib/init-ios-links.js +9 -8
  207. package/dist/src/lib/init-ios-links.js.map +1 -1
  208. package/dist/src/lib/init-plan.d.ts +1 -1
  209. package/dist/src/lib/init-plan.js +1 -1
  210. package/dist/src/lib/init-xcode.d.ts +6 -5
  211. package/dist/src/lib/init-xcode.js +34 -10
  212. package/dist/src/lib/init-xcode.js.map +1 -1
  213. package/dist/src/lib/ios-app-id.d.ts +34 -0
  214. package/dist/src/lib/ios-app-id.js +69 -0
  215. package/dist/src/lib/ios-app-id.js.map +1 -0
  216. package/dist/src/lib/list-options.d.ts +68 -0
  217. package/dist/src/lib/list-options.js +89 -0
  218. package/dist/src/lib/list-options.js.map +1 -0
  219. package/dist/src/lib/local-state.d.ts +1 -1
  220. package/dist/src/lib/local-state.js +1 -1
  221. package/dist/src/lib/local-test-cases.d.ts +1 -1
  222. package/dist/src/lib/local-test-cases.js +2 -1
  223. package/dist/src/lib/local-test-cases.js.map +1 -1
  224. package/dist/src/lib/log-inspection.d.ts +152 -5
  225. package/dist/src/lib/log-inspection.js +179 -2
  226. package/dist/src/lib/log-inspection.js.map +1 -1
  227. package/dist/src/lib/logout-admin-session.d.ts +33 -0
  228. package/dist/src/lib/logout-admin-session.js +70 -0
  229. package/dist/src/lib/logout-admin-session.js.map +1 -0
  230. package/dist/src/lib/output.d.ts +15 -0
  231. package/dist/src/lib/output.js +28 -0
  232. package/dist/src/lib/output.js.map +1 -1
  233. package/dist/src/lib/paginate.d.ts +15 -0
  234. package/dist/src/lib/paginate.js +17 -0
  235. package/dist/src/lib/paginate.js.map +1 -1
  236. package/dist/src/lib/project-config.d.ts +38 -21
  237. package/dist/src/lib/project-config.js +57 -47
  238. package/dist/src/lib/project-config.js.map +1 -1
  239. package/dist/src/lib/prompt-cost-format.d.ts +11 -0
  240. package/dist/src/lib/prompt-cost-format.js +41 -0
  241. package/dist/src/lib/prompt-cost-format.js.map +1 -0
  242. package/dist/src/lib/prompt-schema-codegen.d.ts +147 -0
  243. package/dist/src/lib/prompt-schema-codegen.js +462 -0
  244. package/dist/src/lib/prompt-schema-codegen.js.map +1 -0
  245. package/dist/src/lib/refresh-admin-credentials.d.ts +9 -1
  246. package/dist/src/lib/refresh-admin-credentials.js +22 -2
  247. package/dist/src/lib/refresh-admin-credentials.js.map +1 -1
  248. package/dist/src/lib/resolve-owner.d.ts +19 -0
  249. package/dist/src/lib/resolve-owner.js +20 -0
  250. package/dist/src/lib/resolve-owner.js.map +1 -0
  251. package/dist/src/lib/skill-installer.d.ts +49 -3
  252. package/dist/src/lib/skill-installer.js +275 -100
  253. package/dist/src/lib/skill-installer.js.map +1 -1
  254. package/dist/src/lib/snapshot-audit-source.d.ts +45 -0
  255. package/dist/src/lib/snapshot-audit-source.js +58 -0
  256. package/dist/src/lib/snapshot-audit-source.js.map +1 -0
  257. package/dist/src/lib/snapshot-audit-store.d.ts +52 -0
  258. package/dist/src/lib/snapshot-audit-store.js +196 -0
  259. package/dist/src/lib/snapshot-audit-store.js.map +1 -0
  260. package/dist/src/lib/snapshot-audit.d.ts +207 -0
  261. package/dist/src/lib/snapshot-audit.js +431 -0
  262. package/dist/src/lib/snapshot-audit.js.map +1 -0
  263. package/dist/src/lib/snapshot-build-rows.d.ts +60 -0
  264. package/dist/src/lib/snapshot-build-rows.js +87 -0
  265. package/dist/src/lib/snapshot-build-rows.js.map +1 -0
  266. package/dist/src/lib/snapshot-build.d.ts +50 -0
  267. package/dist/src/lib/snapshot-build.js +111 -0
  268. package/dist/src/lib/snapshot-build.js.map +1 -0
  269. package/dist/src/lib/snapshot-manifest-layout.d.ts +61 -0
  270. package/dist/src/lib/snapshot-manifest-layout.js +70 -0
  271. package/dist/src/lib/snapshot-manifest-layout.js.map +1 -0
  272. package/dist/src/lib/snapshots.d.ts +8 -9
  273. package/dist/src/lib/snapshots.js +20 -83
  274. package/dist/src/lib/snapshots.js.map +1 -1
  275. package/dist/src/lib/step-run-table.d.ts +43 -0
  276. package/dist/src/lib/step-run-table.js +129 -0
  277. package/dist/src/lib/step-run-table.js.map +1 -0
  278. package/dist/src/lib/storage-pending-retry.d.ts +26 -0
  279. package/dist/src/lib/storage-pending-retry.js +42 -0
  280. package/dist/src/lib/storage-pending-retry.js.map +1 -0
  281. package/dist/src/lib/swift-codegen/agentGenerator.d.ts +42 -0
  282. package/dist/src/lib/swift-codegen/agentGenerator.js +118 -0
  283. package/dist/src/lib/swift-codegen/agentGenerator.js.map +1 -0
  284. package/dist/src/lib/swift-codegen/banners.d.ts +24 -0
  285. package/dist/src/lib/swift-codegen/banners.js +25 -0
  286. package/dist/src/lib/swift-codegen/banners.js.map +1 -0
  287. package/dist/src/lib/swift-codegen/dbGenerator.js +19 -7
  288. package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -1
  289. package/dist/src/lib/swift-codegen/functionGenerator.d.ts +139 -0
  290. package/dist/src/lib/swift-codegen/functionGenerator.js +462 -0
  291. package/dist/src/lib/swift-codegen/functionGenerator.js.map +1 -0
  292. package/dist/src/lib/swift-codegen/generator.d.ts +7 -1
  293. package/dist/src/lib/swift-codegen/generator.js +28 -11
  294. package/dist/src/lib/swift-codegen/generator.js.map +1 -1
  295. package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +15 -0
  296. package/dist/src/lib/swift-codegen/schemaToSwift.js +21 -4
  297. package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -1
  298. package/dist/src/lib/sync-paths.d.ts +72 -55
  299. package/dist/src/lib/sync-paths.js +97 -100
  300. package/dist/src/lib/sync-paths.js.map +1 -1
  301. package/dist/src/lib/sync-resource-types.d.ts +23 -4
  302. package/dist/src/lib/sync-resource-types.js +106 -8
  303. package/dist/src/lib/sync-resource-types.js.map +1 -1
  304. package/dist/src/lib/sync-selectors.d.ts +45 -2
  305. package/dist/src/lib/sync-selectors.js +66 -5
  306. package/dist/src/lib/sync-selectors.js.map +1 -1
  307. package/dist/src/lib/template.d.ts +2 -2
  308. package/dist/src/lib/template.js +2 -2
  309. package/dist/src/lib/test-case-keys.d.ts +1 -1
  310. package/dist/src/lib/test-case-keys.js +1 -1
  311. package/dist/src/lib/test-case-variables.d.ts +14 -0
  312. package/dist/src/lib/test-case-variables.js +42 -0
  313. package/dist/src/lib/test-case-variables.js.map +1 -1
  314. package/dist/src/lib/toml-database-config.js +19 -2
  315. package/dist/src/lib/toml-database-config.js.map +1 -1
  316. package/dist/src/lib/webhook-deliver.d.ts +209 -0
  317. package/dist/src/lib/webhook-deliver.js +519 -0
  318. package/dist/src/lib/webhook-deliver.js.map +1 -0
  319. package/dist/src/lib/workflow-codegen/generator.d.ts +1 -1
  320. package/dist/src/lib/workflow-codegen/generator.js +1 -1
  321. package/dist/src/lib/workflow-toml-validator.d.ts +17 -8
  322. package/dist/src/lib/workflow-toml-validator.js +25 -12
  323. package/dist/src/lib/workflow-toml-validator.js.map +1 -1
  324. package/dist/src/lib/workflow-usage.d.ts +196 -0
  325. package/dist/src/lib/workflow-usage.js +310 -0
  326. package/dist/src/lib/workflow-usage.js.map +1 -0
  327. package/dist/src/types/index.d.ts +12 -2
  328. package/package.json +5 -4
@@ -1,4 +1,3 @@
1
- import { Option } from "commander";
2
1
  import { readFileSync, writeFileSync, readdirSync, } from "fs";
3
2
  import * as path from "path";
4
3
  import { stringifyConfigToml } from "../lib/config-toml.js";
@@ -6,17 +5,18 @@ import { generateWorkflowTypes, } from "../lib/workflow-codegen/generator.js";
6
5
  import { generateWorkflowSwiftTypes } from "../lib/swift-codegen/generator.js";
7
6
  import { ApiClient } from "../lib/api-client.js";
8
7
  import { resolveAppId } from "../lib/config.js";
8
+ import { resolveWorkflowSelector } from "../lib/block-selector.js";
9
9
  import { formatAccessRuleForDisplay } from "../lib/access-rule-display.js";
10
10
  import { resolveCodegenSourceDir } from "../lib/codegen-shared/resolveCodegenSourceDir.js";
11
- import { resolveSyncDirOverride } from "../lib/sync-dir-selector.js";
12
11
  import { expandWorkflow } from "../lib/workflow-fragments.js";
13
12
  import { confirmPrompt } from "../lib/confirm-prompt.js";
14
13
  import { parseStatusFilter } from "../lib/object-status-filter.js";
15
- import { buildWorkflowRunEnvelope, buildWorkflowStepEnvelope, collectWorkflowFailures, describeEmptyRunPage, describeFailureSweep, formatFailureCodeCell, formatFailureErrorCell, formatFailureStepCell, selectFailedStepRow, toWorkflowRunInspectionRow, DEFAULT_FAILURE_SWEEP_MAX_SCAN, FAILURE_SWEEP_MAX_REQUESTS, SERVER_SCAN_ROWS_PER_REQUEST, } from "../lib/log-inspection.js";
14
+ import { buildWorkflowRunEnvelope, buildWorkflowRunListEnvelope, buildWorkflowStepEnvelope, collectWorkflowFailures, describeEmptyRunPage, describeFailureSweep, formatFailureCodeCell, formatFailureErrorCell, formatFailureStepCell, selectFailedStepRow, toWorkflowRunInspectionRow, DEFAULT_FAILURE_SWEEP_MAX_SCAN, FAILURE_SWEEP_MAX_REQUESTS, SERVER_SCAN_ROWS_PER_REQUEST, } from "../lib/log-inspection.js";
16
15
  import chalk from "chalk";
17
16
  import { success, error, info, warn, keyValue, result as printResult, formatTable, formatId, formatDate, formatStatus, formatDuration, json, jsonLine, divider, dim, progress, progressEnd, } from "../lib/output.js";
18
17
  import { pollUntil, followLoop, watchLoop, parseIntervalSeconds, } from "../lib/watch.js";
19
18
  import { APPLY_RUN_STATUSES, RUN_STATUS_LIST, SETTLED_RUN_STATUSES, } from "../lib/run-status.js";
19
+ import { requireTestCaseInputVariables } from "../lib/test-case-variables.js";
20
20
  /**
21
21
  * Render the `Step Results` section of `workflows runs status`. Pure
22
22
  * formatting helper — returns lines as strings so it's unit-testable
@@ -121,25 +121,15 @@ export function renderRunStatusStepResults(stepResults) {
121
121
  }
122
122
  return lines;
123
123
  }
124
- /**
125
- * #2771 — how long a step row has been going.
126
- *
127
- * A step that is still executing is persisted with status `running` and a
128
- * `durationMs` of 0 (the column is required; the real value is written when the
129
- * step ends). Printing that 0 hides the one number that matters while a run is
130
- * in flight — how long the current step has been stuck — so a running row is
131
- * measured from its `startedAt` instead. Finished rows keep their stored
132
- * duration. Exported for testability.
133
- */
134
- export function stepRunDisplayDurationMs(step, now = Date.now()) {
135
- const stored = typeof step?.durationMs === "number" ? step.durationMs : null;
136
- if (step?.status !== "running")
137
- return stored;
138
- const started = step?.startedAt ? new Date(step.startedAt).getTime() : NaN;
139
- if (Number.isNaN(started))
140
- return stored;
141
- return Math.max(0, now - started);
142
- }
124
+ // #3348 — the step table, the gap annotation and the running-step duration now
125
+ // live in `lib/step-run-table.ts`, shared with `functions runs steps`: a
126
+ // function run's steps are the same rows, and the two views must not be two
127
+ // tables that merely resemble each other. Re-exported from here because this is
128
+ // where the two helpers were first exported for testability.
129
+ export { annotateStepGaps, stepRunDisplayDurationMs, } from "../lib/step-run-table.js";
130
+ import { renderStepRunTable, stepRunDisplayDurationMs, } from "../lib/step-run-table.js";
131
+ import { normalizeCliListEnvelope, wholeListEnvelope, } from "../lib/paginate.js";
132
+ import { pageCursorOption, pageLimitOption, parsePageLimit, printEmptyPage, printPageHint, } from "../lib/list-options.js";
143
133
  /**
144
134
  * #2771 — the step lines `workflows runs status` should render.
145
135
  *
@@ -163,53 +153,6 @@ export function stepResultsForRunStatus(instanceStepResults, persistedStepRuns,
163
153
  output: row?.output,
164
154
  }));
165
155
  }
166
- /**
167
- * #1367 — annotate step-run rows with the inter-step gap: the wall-clock time
168
- * between the previous step's `endedAt` and this step's `startedAt`. Sorts by
169
- * `startedAt` when every row has a parseable one (tie-break `stepIndex`),
170
- * otherwise by `stepIndex` for the whole list — the ordering mode must be
171
- * chosen once for all rows, not per pair: a per-pair timestamp-vs-index
172
- * fallback is not transitive when some rows lack a timestamp (A<B by index,
173
- * B<C by index, C<A by time), and a non-transitive comparator leaves
174
- * Array.prototype.sort free to return an arbitrary order, computing gaps
175
- * against the wrong predecessor. The first step has no predecessor, so its
176
- * `gapMs` is `null`. A gap is only meaningful when both adjacent timestamps
177
- * parse and the gap is positive. When the immediate predecessor lacks a valid
178
- * `endedAt`, the gap is left `null` rather than measured from an older,
179
- * non-adjacent step — otherwise a later step could raise a false stall
180
- * warning against a step it does not actually follow. Exported for
181
- * testability.
182
- */
183
- export function annotateStepGaps(steps) {
184
- const rows = [...(steps || [])];
185
- const allTimed = rows.every((r) => r?.startedAt && !Number.isNaN(new Date(r.startedAt).getTime()));
186
- rows.sort((a, b) => {
187
- if (allTimed) {
188
- const sa = new Date(a.startedAt).getTime();
189
- const sb = new Date(b.startedAt).getTime();
190
- if (sa !== sb)
191
- return sa - sb;
192
- }
193
- return (a?.stepIndex ?? 0) - (b?.stepIndex ?? 0);
194
- });
195
- let prevEnded = null;
196
- for (const row of rows) {
197
- const started = row?.startedAt ? new Date(row.startedAt).getTime() : NaN;
198
- if (prevEnded != null && !Number.isNaN(started)) {
199
- const gap = started - prevEnded;
200
- row.gapMs = gap > 0 ? gap : 0;
201
- }
202
- else {
203
- row.gapMs = null;
204
- }
205
- // Only genuinely adjacent steps yield a gap: if this step lacks a valid
206
- // `endedAt`, clear the predecessor so the NEXT step's gap is left null
207
- // instead of being measured from a non-adjacent earlier step.
208
- const ended = row?.endedAt ? new Date(row.endedAt).getTime() : NaN;
209
- prevEnded = Number.isNaN(ended) ? null : ended;
210
- }
211
- return rows;
212
- }
213
156
  /**
214
157
  * #1367 — compute min / p50 / p95 / max over a set of latency samples (ms).
215
158
  * Ignores non-finite values. Returns `null` when there are no valid samples.
@@ -253,6 +196,17 @@ export function formatConfigSlotLabel(slot, configName, configId, prefixLength =
253
196
  }
254
197
  return slot;
255
198
  }
199
+ /**
200
+ * Resolve a workflow reference — its server id, or the author-chosen key its
201
+ * TOML file is named for — for the `tests` verbs (#3249).
202
+ *
203
+ * The key is what an operator has: `workflows/<key>.toml` is the object and
204
+ * `workflows/<key>.tests/` its cases. The resolution lives in
205
+ * `block-selector.ts`, shared with `prompts`.
206
+ */
207
+ async function resolveWorkflowId(client, appId, ref) {
208
+ return (await resolveWorkflowSelector(client, appId, ref)).blockId;
209
+ }
256
210
  export function registerWorkflowsCommands(program) {
257
211
  const workflows = program
258
212
  .command("workflows")
@@ -296,8 +250,11 @@ overview — the endpoint behind the retired overview command never existed.
296
250
  .argument("[app-id]", "App ID (uses current app if not specified)")
297
251
  .option("--app <app-id>", "App ID")
298
252
  .option("--status <status>", "Filter by status: active, inactive, archived")
253
+ .addOption(pageLimitOption())
254
+ .addOption(pageCursorOption())
299
255
  .option("--json", "Output as JSON")
300
256
  .action(async (appId, options) => {
257
+ const limit = parsePageLimit(options.limit);
301
258
  const resolvedAppId = resolveAppId(appId, options);
302
259
  const client = new ApiClient();
303
260
  try {
@@ -305,19 +262,22 @@ overview — the endpoint behind the retired overview command never existed.
305
262
  // empty list a canonical filter would produce. #2887 added `archived`:
306
263
  // the delete flow writes one now, so asking for the retired rows is a
307
264
  // question with an answer rather than a 400.
308
- const { items } = await client.listWorkflows(resolvedAppId, {
265
+ const page = normalizeCliListEnvelope(await client.listWorkflows(resolvedAppId, {
309
266
  status: parseStatusFilter("workflows", options.status, [
310
267
  "active",
311
268
  "inactive",
312
269
  "archived",
313
270
  ]),
314
- });
271
+ limit,
272
+ cursor: options.cursor,
273
+ }));
274
+ const items = page.items;
315
275
  if (options.json) {
316
- json(items);
276
+ json(page);
317
277
  return;
318
278
  }
319
- if (!items || items.length === 0) {
320
- info("No workflows found.");
279
+ if (items.length === 0) {
280
+ printEmptyPage(page, "workflows");
321
281
  return;
322
282
  }
323
283
  console.log(formatTable(items, [
@@ -327,6 +287,7 @@ overview — the endpoint behind the retired overview command never existed.
327
287
  { header: "STATUS", key: "status", format: formatStatus },
328
288
  { header: "MODIFIED", key: "modifiedAt", format: formatDate },
329
289
  ]));
290
+ printPageHint(page);
330
291
  }
331
292
  catch (err) {
332
293
  error(err.message);
@@ -988,10 +949,11 @@ Reclaiming the key means hard-deleting the row and its cascade:
988
949
  });
989
950
  const { items } = page;
990
951
  if (options.json) {
991
- // #1316 envelope with the shared inspection item shape — never a
992
- // bare array — preserving nextCursor/hasMore + the deprecated
993
- // `cursor` dual-emit alias (behaviors 6, 7).
994
- json(buildWorkflowRunEnvelope(items, page));
952
+ // The CLI's one list envelope with the shared inspection item
953
+ // shape — never a bare array, and since #3646 no `nextCursor:
954
+ // null` and no deprecated `cursor` alias either, because this is a
955
+ // `list` verb like any other (behaviors 6, 7).
956
+ json(buildWorkflowRunListEnvelope(items, page));
995
957
  return;
996
958
  }
997
959
  // The cursor hint prints whether or not this page had rows: a
@@ -1105,7 +1067,7 @@ Reclaiming the key means hard-deleting the row and its cascade:
1105
1067
  signal: controller.signal,
1106
1068
  render: (items) => {
1107
1069
  if (options.json) {
1108
- json(buildWorkflowRunEnvelope(items, lastPage));
1070
+ json(buildWorkflowRunListEnvelope(items, lastPage));
1109
1071
  return;
1110
1072
  }
1111
1073
  // Renderer owns the redraw: clear the screen, then print.
@@ -1225,49 +1187,7 @@ Reclaiming the key means hard-deleting the row and its cascade:
1225
1187
  info("No step runs found.");
1226
1188
  return;
1227
1189
  }
1228
- // #1367 — inter-step gap: idle time between one step ending and the
1229
- // next starting. Only surfaced above a 1s threshold so normal
1230
- // back-to-back steps stay quiet; a large gap flags a mid-run stall.
1231
- const STEP_GAP_THRESHOLD_MS = 1000;
1232
- // #2771 — an in-flight run also returns the step executing right now
1233
- // (status `running`, duration not written yet). Show its elapsed time
1234
- // so a step stuck retrying is visible as such.
1235
- const rows = annotateStepGaps(items).map((row) => ({
1236
- ...row,
1237
- displayDurationMs: stepRunDisplayDurationMs(row),
1238
- }));
1239
- console.log(formatTable(rows, [
1240
- { header: "STEP", key: "stepId" },
1241
- { header: "KIND", key: "stepKind" },
1242
- {
1243
- header: "STATUS",
1244
- key: "status",
1245
- format: (v) => v === "completed" ? chalk.green(v) :
1246
- v === "failed" ? chalk.red(v) :
1247
- v === "skipped" ? chalk.gray(v) :
1248
- v === "error_captured" ? chalk.yellow(v) :
1249
- // #2771 — the step executing right now.
1250
- v === "running" ? chalk.cyan(v) : v,
1251
- },
1252
- {
1253
- header: "GAP",
1254
- key: "gapMs",
1255
- format: (v) => typeof v === "number" && v >= STEP_GAP_THRESHOLD_MS
1256
- ? chalk.yellow(formatDuration(v))
1257
- : chalk.dim("-"),
1258
- },
1259
- { header: "DURATION", key: "displayDurationMs", format: formatDuration },
1260
- {
1261
- header: "TOKENS",
1262
- key: "totalTokens",
1263
- format: (v) => v ? String(v).replace(/\B(?=(\d{3})+(?!\d))/g, ",") : "-",
1264
- },
1265
- {
1266
- header: "ERROR",
1267
- key: "error",
1268
- format: (v) => v ? (v.length > 60 ? v.slice(0, 57) + "..." : v) : "",
1269
- },
1270
- ]));
1190
+ console.log(renderStepRunTable(items));
1271
1191
  }
1272
1192
  catch (err) {
1273
1193
  error(err.message);
@@ -1617,7 +1537,7 @@ Reclaiming the key means hard-deleting the row and its cascade:
1617
1537
  try {
1618
1538
  const { items } = await client.listIterations(resolvedAppId);
1619
1539
  if (options.json) {
1620
- json(items);
1540
+ json(wholeListEnvelope(items));
1621
1541
  return;
1622
1542
  }
1623
1543
  if (!items || items.length === 0) {
@@ -1735,7 +1655,7 @@ activeConfigName in workflows/<key>.toml, then run 'primitive config push'.
1735
1655
  try {
1736
1656
  const { items } = await client.listWorkflowConfigs(resolvedAppId, workflowId);
1737
1657
  if (options.json) {
1738
- json(items);
1658
+ json(wholeListEnvelope(items));
1739
1659
  return;
1740
1660
  }
1741
1661
  if (!items || items.length === 0) {
@@ -1809,29 +1729,44 @@ Test cases are authored in TOML at workflows/<key>.tests/<name>.toml and applied
1809
1729
  with \`primitive config push\` (issue #2769); \`config fields workflow\` lists the
1810
1730
  fields. Deleting a case is removing its file and running \`config push --prune\`.
1811
1731
 
1732
+ Every verb here takes the workflow's KEY or its ID (#3249). An identifier
1733
+ that names no workflow in the app fails; "No test cases found." only ever
1734
+ means the workflow exists and has no cases.
1735
+
1812
1736
  Examples:
1813
- $ primitive workflows tests list <workflow-id>
1814
- $ primitive workflows tests run <workflow-id> <test-case-id>
1815
- $ primitive workflows tests run-all <workflow-id>
1816
- $ primitive workflows tests run-all <workflow-id> --test-cases 01ABC,01DEF,01GHI
1737
+ $ primitive workflows tests list <workflow>
1738
+ $ primitive workflows tests run <workflow> <test-case-id>
1739
+ $ primitive workflows tests run-all <workflow>
1740
+ $ primitive workflows tests run-all <workflow> --test-cases 01ABC,01DEF,01GHI
1817
1741
  `);
1818
1742
  tests
1819
1743
  .command("list")
1820
1744
  .description("List test cases for a workflow")
1821
- .argument("<workflow-id>", "Workflow ID")
1745
+ .argument("<workflow>", "Workflow key or ID")
1822
1746
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1747
+ .addOption(pageLimitOption())
1748
+ .addOption(pageCursorOption())
1823
1749
  .option("--json", "Output as JSON")
1824
- .action(async (workflowId, options) => {
1750
+ .action(async (workflow, options) => {
1751
+ const limit = parsePageLimit(options.limit);
1825
1752
  const resolvedAppId = resolveAppId(undefined, options);
1826
1753
  const client = new ApiClient();
1827
1754
  try {
1828
- const { items } = await client.listTestCases(resolvedAppId, "workflow", workflowId);
1755
+ // #3249 — resolve BEFORE listing: the test-case endpoint answers an
1756
+ // unknown block with an empty page, so an unresolved identifier would
1757
+ // print "No test cases found." and exit 0.
1758
+ const workflowId = await resolveWorkflowId(client, resolvedAppId, workflow);
1759
+ const page = normalizeCliListEnvelope(await client.listTestCases(resolvedAppId, "workflow", workflowId, {
1760
+ limit,
1761
+ cursor: options.cursor,
1762
+ }));
1763
+ const items = page.items;
1829
1764
  if (options.json) {
1830
- json(items);
1765
+ json(page);
1831
1766
  return;
1832
1767
  }
1833
- if (!items || items.length === 0) {
1834
- info("No test cases found.");
1768
+ if (items.length === 0) {
1769
+ printEmptyPage(page, "test cases");
1835
1770
  return;
1836
1771
  }
1837
1772
  console.log(formatTable(items, [
@@ -1839,6 +1774,7 @@ Examples:
1839
1774
  { header: "NAME", key: "name" },
1840
1775
  { header: "CREATED", key: "createdAt", format: formatDate },
1841
1776
  ]));
1777
+ printPageHint(page);
1842
1778
  }
1843
1779
  catch (err) {
1844
1780
  error(err.message);
@@ -1849,14 +1785,15 @@ Examples:
1849
1785
  tests
1850
1786
  .command("get")
1851
1787
  .description("Get test case details")
1852
- .argument("<workflow-id>", "Workflow ID")
1788
+ .argument("<workflow>", "Workflow key or ID")
1853
1789
  .argument("<test-case-id>", "Test Case ID")
1854
1790
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1855
1791
  .option("--json", "Output as JSON")
1856
- .action(async (workflowId, testCaseId, options) => {
1792
+ .action(async (workflow, testCaseId, options) => {
1857
1793
  const resolvedAppId = resolveAppId(undefined, options);
1858
1794
  const client = new ApiClient();
1859
1795
  try {
1796
+ const workflowId = await resolveWorkflowId(client, resolvedAppId, workflow);
1860
1797
  const result = await client.getTestCase(resolvedAppId, "workflow", workflowId, testCaseId);
1861
1798
  if (options.json) {
1862
1799
  json(result);
@@ -1916,23 +1853,21 @@ Examples:
1916
1853
  tests
1917
1854
  .command("run")
1918
1855
  .description("Run a single test case")
1919
- .argument("<workflow-id>", "Workflow ID")
1856
+ .argument("<workflow>", "Workflow key or ID")
1920
1857
  .argument("<test-case-id>", "Test Case ID")
1921
1858
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1922
1859
  .option("--json", "Output as JSON")
1923
- .action(async (workflowId, testCaseId, options) => {
1860
+ .action(async (workflow, testCaseId, options) => {
1924
1861
  const resolvedAppId = resolveAppId(undefined, options);
1925
1862
  const client = new ApiClient();
1926
1863
  try {
1864
+ const workflowId = await resolveWorkflowId(client, resolvedAppId, workflow);
1927
1865
  // Get test case to retrieve its variables
1928
1866
  const testCase = await client.getTestCase(resolvedAppId, "workflow", workflowId, testCaseId);
1929
- let variables = {};
1930
- try {
1931
- variables = JSON.parse(testCase.inputVariables || "{}");
1932
- }
1933
- catch {
1934
- // Use empty object if parsing fails
1935
- }
1867
+ // #3158 — the case's stored input IS the run's input, and the server
1868
+ // hands it back parsed; `JSON.parse` on that object threw and the run
1869
+ // went ahead with `{}`, disagreeing with `run-all` over the same case.
1870
+ const variables = requireTestCaseInputVariables(testCase.inputVariables, testCaseId);
1936
1871
  const result = await client.executeBlockTest(resolvedAppId, "workflow", workflowId, {
1937
1872
  variables,
1938
1873
  testCaseId,
@@ -1985,14 +1920,17 @@ Examples:
1985
1920
  tests
1986
1921
  .command("run-all")
1987
1922
  .description("Run all test cases for a workflow, or a specific subset")
1988
- .argument("<workflow-id>", "Workflow ID")
1923
+ .argument("<workflow>", "Workflow key or ID")
1989
1924
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1990
1925
  .option("--test-cases <ids>", "Comma-separated list of test case IDs to run (runs only these)")
1991
1926
  .option("--json", "Output as JSON")
1992
- .action(async (workflowId, options) => {
1927
+ .action(async (workflow, options) => {
1993
1928
  const resolvedAppId = resolveAppId(undefined, options);
1994
1929
  const client = new ApiClient();
1995
1930
  try {
1931
+ // #3249 — resolve first, so `Total Tests: 0` can only ever mean the
1932
+ // workflow exists and has no cases, never that the identifier was wrong.
1933
+ const workflowId = await resolveWorkflowId(client, resolvedAppId, workflow);
1996
1934
  // Parse test case IDs if provided
1997
1935
  let testCaseIds;
1998
1936
  if (options.testCases) {
@@ -2049,15 +1987,16 @@ Examples:
2049
1987
  tests
2050
1988
  .command("runs")
2051
1989
  .description("List recent test runs for a workflow")
2052
- .argument("<workflow-id>", "Workflow ID")
1990
+ .argument("<workflow>", "Workflow key or ID")
2053
1991
  .option("--app <app-id>", "App ID (uses current app if not specified)")
2054
1992
  .option("--limit <n>", "Maximum number of runs to show", "20")
2055
1993
  .option("--group <group>", "Filter by comparison group")
2056
1994
  .option("--json", "Output as JSON")
2057
- .action(async (workflowId, options) => {
1995
+ .action(async (workflow, options) => {
2058
1996
  const resolvedAppId = resolveAppId(undefined, options);
2059
1997
  const client = new ApiClient();
2060
1998
  try {
1999
+ const workflowId = await resolveWorkflowId(client, resolvedAppId, workflow);
2061
2000
  const { items } = await client.listTestRuns(resolvedAppId, "workflow", workflowId, {
2062
2001
  limit: parseInt(options.limit),
2063
2002
  comparisonGroup: options.group,
@@ -2099,23 +2038,24 @@ Examples:
2099
2038
  .description("Run tests in parallel using workflow-based execution")
2100
2039
  .addHelpText("after", `
2101
2040
  Examples:
2102
- $ primitive workflows tests batch start <workflow-id>
2103
- $ primitive workflows tests batch start <workflow-id> --test-cases 01ABC,01DEF
2104
- $ primitive workflows tests batch status <workflow-id> <batch-id>
2105
- $ primitive workflows tests batch cancel <workflow-id> <batch-id>
2041
+ $ primitive workflows tests batch start <workflow>
2042
+ $ primitive workflows tests batch start <workflow> --test-cases 01ABC,01DEF
2043
+ $ primitive workflows tests batch status <workflow> <batch-id>
2044
+ $ primitive workflows tests batch cancel <workflow> <batch-id>
2106
2045
  `);
2107
2046
  // Start batch tests
2108
2047
  batch
2109
2048
  .command("start")
2110
2049
  .description("Start parallel batch test execution")
2111
- .argument("<workflow-id>", "Workflow ID")
2050
+ .argument("<workflow>", "Workflow key or ID")
2112
2051
  .option("--app <app-id>", "App ID (uses current app if not specified)")
2113
2052
  .option("--test-cases <ids>", "Comma-separated list of test case IDs to run")
2114
2053
  .option("--json", "Output as JSON")
2115
- .action(async (workflowId, options) => {
2054
+ .action(async (workflow, options) => {
2116
2055
  const resolvedAppId = resolveAppId(undefined, options);
2117
2056
  const client = new ApiClient();
2118
2057
  try {
2058
+ const workflowId = await resolveWorkflowId(client, resolvedAppId, workflow);
2119
2059
  let testCaseIds;
2120
2060
  if (options.testCases) {
2121
2061
  testCaseIds = options.testCases.split(",").map((id) => id.trim()).filter(Boolean);
@@ -2151,15 +2091,16 @@ Examples:
2151
2091
  batch
2152
2092
  .command("status")
2153
2093
  .description("Get status of a batch test execution")
2154
- .argument("<workflow-id>", "Workflow ID")
2094
+ .argument("<workflow>", "Workflow key or ID")
2155
2095
  .argument("<batch-id>", "Batch ID")
2156
2096
  .option("--app <app-id>", "App ID (uses current app if not specified)")
2157
2097
  .option("--wait", "Wait for completion (polls every 2 seconds)")
2158
2098
  .option("--json", "Output as JSON")
2159
- .action(async (workflowId, batchId, options) => {
2099
+ .action(async (workflow, batchId, options) => {
2160
2100
  const resolvedAppId = resolveAppId(undefined, options);
2161
2101
  const client = new ApiClient();
2162
2102
  try {
2103
+ const workflowId = await resolveWorkflowId(client, resolvedAppId, workflow);
2163
2104
  const fetchStatus = async () => {
2164
2105
  return client.getBatchTestStatus(resolvedAppId, "workflow", workflowId, batchId);
2165
2106
  };
@@ -2216,12 +2157,12 @@ Examples:
2216
2157
  batch
2217
2158
  .command("cancel")
2218
2159
  .description("Cancel a running batch test execution")
2219
- .argument("<workflow-id>", "Workflow ID")
2160
+ .argument("<workflow>", "Workflow key or ID")
2220
2161
  .argument("<batch-id>", "Batch ID")
2221
2162
  .option("--app <app-id>", "App ID (uses current app if not specified)")
2222
2163
  .option("-y, --yes", "Skip confirmation prompt")
2223
2164
  .option("--json", "Output as JSON")
2224
- .action(async (workflowId, batchId, options) => {
2165
+ .action(async (workflow, batchId, options) => {
2225
2166
  const resolvedAppId = resolveAppId(undefined, options);
2226
2167
  if (!options.yes) {
2227
2168
  let confirm;
@@ -2239,6 +2180,7 @@ Examples:
2239
2180
  }
2240
2181
  const client = new ApiClient();
2241
2182
  try {
2183
+ const workflowId = await resolveWorkflowId(client, resolvedAppId, workflow);
2242
2184
  // First get current status to get instance IDs
2243
2185
  const status = await client.getBatchTestStatus(resolvedAppId, "workflow", workflowId, batchId);
2244
2186
  // Get instance IDs that are still running
@@ -2278,24 +2220,25 @@ with \`primitive config push\` (#2769); removing one and running
2278
2220
  \`config push --prune\` deletes it server-side. These verbs read server state.
2279
2221
 
2280
2222
  Examples:
2281
- $ primitive workflows tests attachments list <workflow-id> <test-case-id>
2282
- $ primitive workflows tests attachments download <workflow-id> <test-case-id> document.pdf
2223
+ $ primitive workflows tests attachments list <workflow> <test-case-id>
2224
+ $ primitive workflows tests attachments download <workflow> <test-case-id> document.pdf
2283
2225
  `);
2284
2226
  // List attachments
2285
2227
  attachments
2286
2228
  .command("list")
2287
2229
  .description("List attachments for a test case")
2288
- .argument("<workflow-id>", "Workflow ID")
2230
+ .argument("<workflow>", "Workflow key or ID")
2289
2231
  .argument("<test-case-id>", "Test Case ID")
2290
2232
  .option("--app <app-id>", "App ID (uses current app if not specified)")
2291
2233
  .option("--json", "Output as JSON")
2292
- .action(async (workflowId, testCaseId, options) => {
2234
+ .action(async (workflow, testCaseId, options) => {
2293
2235
  const resolvedAppId = resolveAppId(undefined, options);
2294
2236
  const client = new ApiClient();
2295
2237
  try {
2238
+ const workflowId = await resolveWorkflowId(client, resolvedAppId, workflow);
2296
2239
  const result = await client.listTestCaseAttachments(resolvedAppId, "workflow", workflowId, testCaseId);
2297
2240
  if (options.json) {
2298
- json(result.attachments);
2241
+ json(wholeListEnvelope(result.attachments));
2299
2242
  return;
2300
2243
  }
2301
2244
  if (!result.attachments || result.attachments.length === 0) {
@@ -2321,15 +2264,16 @@ Examples:
2321
2264
  attachments
2322
2265
  .command("download")
2323
2266
  .description("Download a file attachment from a test case")
2324
- .argument("<workflow-id>", "Workflow ID")
2267
+ .argument("<workflow>", "Workflow key or ID")
2325
2268
  .argument("<test-case-id>", "Test Case ID")
2326
2269
  .argument("<filename>", "Name of the file to download")
2327
2270
  .argument("[output-path]", "Output path (defaults to current directory)")
2328
2271
  .option("--app <app-id>", "App ID (uses current app if not specified)")
2329
- .action(async (workflowId, testCaseId, filename, outputPath, options) => {
2272
+ .action(async (workflow, testCaseId, filename, outputPath, options) => {
2330
2273
  const resolvedAppId = resolveAppId(undefined, options);
2331
2274
  const client = new ApiClient();
2332
2275
  try {
2276
+ const workflowId = await resolveWorkflowId(client, resolvedAppId, workflow);
2333
2277
  const result = await client.downloadTestCaseAttachment(resolvedAppId, "workflow", workflowId, testCaseId, filename);
2334
2278
  const finalPath = outputPath || filename;
2335
2279
  writeFileSync(finalPath, result.data);
@@ -2349,9 +2293,6 @@ Examples:
2349
2293
  .command("codegen")
2350
2294
  .description("Generate typed client signatures from the local workflows/*.toml schemas. TypeScript (default): one <key>.generated.ts per workflow (<Key>Input/<Key>Output types + a <key>(client) invoker factory). Swift (--lang swift): one <key>.generated.swift per workflow (<Key>Input/<Key>Output Codable types).\n\nFormatting: the emitted TypeScript is already formatted the way Prettier's DEFAULT config formats it, so a `codegen` -> `prettier --write` -> `codegen --check` pipeline stays green. If your project uses a NON-default Prettier config (a different printWidth, trailingComma, or quote style), Prettier will reformat the generated files and `--check` will then fail: add the generated directory to `.prettierignore` and commit the raw emission instead.")
2351
2295
  .argument("[workflow-key]", "Generate for a single workflow (defaults to every workflows/*.toml found)")
2352
- .option("--app <app-id>", "App ID")
2353
- .option("--dir <path>", "Config directory (overrides the auto-resolved per-env path)")
2354
- .addOption(new Option("--sync-dir <path>", "Deprecated alias of --dir").hideHelp())
2355
2296
  .option("-o, --output <dir>", "Output directory for the generated files (defaults to <config-dir>/workflows/generated/)")
2356
2297
  .option("--lang <lang>", "Target language for the generated types: 'ts' (default) or 'swift'.", "ts")
2357
2298
  .option("--check", "Exit non-zero if generated output is out of date (CI guard); does not write. Compares byte-for-byte, so the file on disk must match the emission exactly.")
@@ -2365,17 +2306,10 @@ Examples:
2365
2306
  error(`Unknown --lang "${options.lang}". Use "ts" or "swift".`);
2366
2307
  process.exit(1);
2367
2308
  }
2368
- // 1. Resolve the single source workflows/ directory via the shared
2369
- // active-environment resolver (issue #1510). Honors --dir /
2370
- // --app overrides, resolves the active env
2371
- // (--env → PRIMITIVE_ENV → defaultEnvironment → single-env), and
2372
- // preserves the legacy per-app scan fallback in bare-dir mode.
2309
+ // 1. The single source workflows/ directory: the environment's, and
2310
+ // only ever the environment's (#1510, #3154).
2373
2311
  const workflowsSourceDir = resolveCodegenSourceDir({
2374
2312
  subdir: "workflows",
2375
- options: {
2376
- app: options.app,
2377
- syncDir: resolveSyncDirOverride(options, "workflows codegen"),
2378
- },
2379
2313
  });
2380
2314
  // 2. Collect the source .toml files (one per workflow), filtered to a
2381
2315
  // single workflow when an argument is given. Match on the file stem;
@@ -2396,7 +2330,7 @@ Examples:
2396
2330
  }
2397
2331
  if (inputs.length === 0) {
2398
2332
  error(workflowKey
2399
- ? `No workflows/${workflowKey}.toml found under .primitive/sync/.`
2333
+ ? `No workflows/${workflowKey}.toml found under primitive/<env>/.`
2400
2334
  : "No workflows/*.toml files found to generate from.");
2401
2335
  process.exit(1);
2402
2336
  }