primitive-admin 1.1.0-alpha.8 → 1.1.0-alpha.80

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 (522) hide show
  1. package/README.md +444 -106
  2. package/assets/skill/skills/primitive-platform/SKILL.md +811 -0
  3. package/dist/bin/primitive.d.ts +2 -0
  4. package/dist/bin/primitive.js +373 -21
  5. package/dist/bin/primitive.js.map +1 -1
  6. package/dist/src/commands/admins.d.ts +2 -0
  7. package/dist/src/commands/admins.js +128 -19
  8. package/dist/src/commands/admins.js.map +1 -1
  9. package/dist/src/commands/analytics.d.ts +2 -0
  10. package/dist/src/commands/analytics.js +700 -55
  11. package/dist/src/commands/analytics.js.map +1 -1
  12. package/dist/src/commands/apps.d.ts +2 -0
  13. package/dist/src/commands/apps.js +54 -96
  14. package/dist/src/commands/apps.js.map +1 -1
  15. package/dist/src/commands/auth.d.ts +2 -0
  16. package/dist/src/commands/auth.js +196 -105
  17. package/dist/src/commands/auth.js.map +1 -1
  18. package/dist/src/commands/blob-buckets.d.ts +2 -0
  19. package/dist/src/commands/blob-buckets.js +330 -0
  20. package/dist/src/commands/blob-buckets.js.map +1 -0
  21. package/dist/src/commands/catalog.d.ts +2 -0
  22. package/dist/src/commands/catalog.js +37 -38
  23. package/dist/src/commands/catalog.js.map +1 -1
  24. package/dist/src/commands/collection-type-configs.d.ts +2 -0
  25. package/dist/src/commands/collection-type-configs.js +84 -0
  26. package/dist/src/commands/collection-type-configs.js.map +1 -0
  27. package/dist/src/commands/collections.d.ts +2 -0
  28. package/dist/src/commands/collections.js +565 -0
  29. package/dist/src/commands/collections.js.map +1 -0
  30. package/dist/src/commands/comparisons.d.ts +2 -0
  31. package/dist/src/commands/comparisons.js +6 -6
  32. package/dist/src/commands/comparisons.js.map +1 -1
  33. package/dist/src/commands/config.d.ts +46 -0
  34. package/dist/src/commands/config.js +465 -0
  35. package/dist/src/commands/config.js.map +1 -0
  36. package/dist/src/commands/connections.d.ts +2 -0
  37. package/dist/src/commands/connections.js +100 -0
  38. package/dist/src/commands/connections.js.map +1 -0
  39. package/dist/src/commands/cron-triggers.d.ts +2 -0
  40. package/dist/src/commands/cron-triggers.js +265 -0
  41. package/dist/src/commands/cron-triggers.js.map +1 -0
  42. package/dist/src/commands/database-type-configs.d.ts +2 -0
  43. package/dist/src/commands/database-type-configs.js +163 -0
  44. package/dist/src/commands/database-type-configs.js.map +1 -0
  45. package/dist/src/commands/database-types.d.ts +2 -0
  46. package/dist/src/commands/database-types.js +171 -0
  47. package/dist/src/commands/database-types.js.map +1 -0
  48. package/dist/src/commands/databases.d.ts +65 -0
  49. package/dist/src/commands/databases.js +1859 -238
  50. package/dist/src/commands/databases.js.map +1 -1
  51. package/dist/src/commands/documents.d.ts +2 -0
  52. package/dist/src/commands/documents.js +2062 -19
  53. package/dist/src/commands/documents.js.map +1 -1
  54. package/dist/src/commands/email-templates.d.ts +2 -0
  55. package/dist/src/commands/email-templates.js +174 -0
  56. package/dist/src/commands/email-templates.js.map +1 -0
  57. package/dist/src/commands/env.d.ts +23 -0
  58. package/dist/src/commands/env.js +385 -0
  59. package/dist/src/commands/env.js.map +1 -0
  60. package/dist/src/commands/feature-flags.d.ts +14 -0
  61. package/dist/src/commands/feature-flags.js +116 -0
  62. package/dist/src/commands/feature-flags.js.map +1 -0
  63. package/dist/src/commands/functions.d.ts +20 -0
  64. package/dist/src/commands/functions.js +672 -0
  65. package/dist/src/commands/functions.js.map +1 -0
  66. package/dist/src/commands/group-type-configs.d.ts +2 -0
  67. package/dist/src/commands/group-type-configs.js +86 -0
  68. package/dist/src/commands/group-type-configs.js.map +1 -0
  69. package/dist/src/commands/groups.d.ts +2 -0
  70. package/dist/src/commands/groups.js +39 -108
  71. package/dist/src/commands/groups.js.map +1 -1
  72. package/dist/src/commands/guides.d.ts +223 -0
  73. package/dist/src/commands/guides.js +617 -65
  74. package/dist/src/commands/guides.js.map +1 -1
  75. package/dist/src/commands/init.d.ts +37 -0
  76. package/dist/src/commands/init.js +1693 -208
  77. package/dist/src/commands/init.js.map +1 -1
  78. package/dist/src/commands/integrations.d.ts +2 -0
  79. package/dist/src/commands/integrations.js +402 -182
  80. package/dist/src/commands/integrations.js.map +1 -1
  81. package/dist/src/commands/llm.d.ts +2 -0
  82. package/dist/src/commands/llm.js +13 -8
  83. package/dist/src/commands/llm.js.map +1 -1
  84. package/dist/src/commands/locks.d.ts +8 -0
  85. package/dist/src/commands/locks.js +160 -0
  86. package/dist/src/commands/locks.js.map +1 -0
  87. package/dist/src/commands/metadata-category-configs.d.ts +12 -0
  88. package/dist/src/commands/metadata-category-configs.js +112 -0
  89. package/dist/src/commands/metadata-category-configs.js.map +1 -0
  90. package/dist/src/commands/metadata.d.ts +2 -0
  91. package/dist/src/commands/metadata.js +281 -0
  92. package/dist/src/commands/metadata.js.map +1 -0
  93. package/dist/src/commands/prompts.d.ts +2 -0
  94. package/dist/src/commands/prompts.js +275 -621
  95. package/dist/src/commands/prompts.js.map +1 -1
  96. package/dist/src/commands/rule-sets.d.ts +3 -0
  97. package/dist/src/commands/rule-sets.js +137 -147
  98. package/dist/src/commands/rule-sets.js.map +1 -1
  99. package/dist/src/commands/scripts.d.ts +30 -0
  100. package/dist/src/commands/scripts.js +679 -0
  101. package/dist/src/commands/scripts.js.map +1 -0
  102. package/dist/src/commands/secrets.d.ts +2 -0
  103. package/dist/src/commands/secrets.js +108 -0
  104. package/dist/src/commands/secrets.js.map +1 -0
  105. package/dist/src/commands/sessions.d.ts +2 -0
  106. package/dist/src/commands/sessions.js +75 -0
  107. package/dist/src/commands/sessions.js.map +1 -0
  108. package/dist/src/commands/settings.d.ts +18 -0
  109. package/dist/src/commands/settings.js +54 -0
  110. package/dist/src/commands/settings.js.map +1 -0
  111. package/dist/src/commands/skill.d.ts +2 -0
  112. package/dist/src/commands/skill.js +29 -0
  113. package/dist/src/commands/skill.js.map +1 -0
  114. package/dist/src/commands/sync-app-settings.d.ts +158 -0
  115. package/dist/src/commands/sync-app-settings.js +328 -0
  116. package/dist/src/commands/sync-app-settings.js.map +1 -0
  117. package/dist/src/commands/sync.d.ts +2514 -0
  118. package/dist/src/commands/sync.js +16106 -848
  119. package/dist/src/commands/sync.js.map +1 -1
  120. package/dist/src/commands/tokens.d.ts +2 -0
  121. package/dist/src/commands/tokens.js +130 -21
  122. package/dist/src/commands/tokens.js.map +1 -1
  123. package/dist/src/commands/users.d.ts +2 -0
  124. package/dist/src/commands/users.js +532 -23
  125. package/dist/src/commands/users.js.map +1 -1
  126. package/dist/src/commands/vars.d.ts +8 -0
  127. package/dist/src/commands/vars.js +96 -0
  128. package/dist/src/commands/vars.js.map +1 -0
  129. package/dist/src/commands/waitlist.d.ts +2 -0
  130. package/dist/src/commands/waitlist.js +10 -10
  131. package/dist/src/commands/waitlist.js.map +1 -1
  132. package/dist/src/commands/webhooks.d.ts +2 -0
  133. package/dist/src/commands/webhooks.js +562 -0
  134. package/dist/src/commands/webhooks.js.map +1 -0
  135. package/dist/src/commands/workflows.d.ts +116 -0
  136. package/dist/src/commands/workflows.js +1638 -729
  137. package/dist/src/commands/workflows.js.map +1 -1
  138. package/dist/src/lib/access-rule-display.d.ts +21 -0
  139. package/dist/src/lib/access-rule-display.js +34 -0
  140. package/dist/src/lib/access-rule-display.js.map +1 -0
  141. package/dist/src/lib/api-client.d.ts +2147 -0
  142. package/dist/src/lib/api-client.js +2042 -160
  143. package/dist/src/lib/api-client.js.map +1 -1
  144. package/dist/src/lib/app-match-guard.d.ts +44 -0
  145. package/dist/src/lib/app-match-guard.js +72 -0
  146. package/dist/src/lib/app-match-guard.js.map +1 -0
  147. package/dist/src/lib/app-settings-descriptor.d.ts +263 -0
  148. package/dist/src/lib/app-settings-descriptor.js +584 -0
  149. package/dist/src/lib/app-settings-descriptor.js.map +1 -0
  150. package/dist/src/lib/auth-flow.d.ts +8 -0
  151. package/dist/src/lib/batch.d.ts +26 -0
  152. package/dist/src/lib/batch.js +32 -0
  153. package/dist/src/lib/batch.js.map +1 -0
  154. package/dist/src/lib/block-layout.d.ts +160 -0
  155. package/dist/src/lib/block-layout.js +451 -0
  156. package/dist/src/lib/block-layout.js.map +1 -0
  157. package/dist/src/lib/block-selector.d.ts +58 -0
  158. package/dist/src/lib/block-selector.js +92 -0
  159. package/dist/src/lib/block-selector.js.map +1 -0
  160. package/dist/src/lib/canonical-json.d.ts +12 -0
  161. package/dist/src/lib/canonical-json.js +35 -0
  162. package/dist/src/lib/canonical-json.js.map +1 -0
  163. package/dist/src/lib/channel.d.ts +30 -0
  164. package/dist/src/lib/channel.js +68 -0
  165. package/dist/src/lib/channel.js.map +1 -0
  166. package/dist/src/lib/cli-manifest.d.ts +68 -0
  167. package/dist/src/lib/cli-manifest.js +71 -0
  168. package/dist/src/lib/cli-manifest.js.map +1 -0
  169. package/dist/src/lib/codegen-shared/generatedFiles.d.ts +101 -0
  170. package/dist/src/lib/codegen-shared/generatedFiles.js +191 -0
  171. package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -0
  172. package/dist/src/lib/codegen-shared/prettierStable.d.ts +262 -0
  173. package/dist/src/lib/codegen-shared/prettierStable.js +610 -0
  174. package/dist/src/lib/codegen-shared/prettierStable.js.map +1 -0
  175. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +38 -0
  176. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +46 -0
  177. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -0
  178. package/dist/src/lib/config-json-field.d.ts +28 -0
  179. package/dist/src/lib/config-json-field.js +56 -0
  180. package/dist/src/lib/config-json-field.js.map +1 -0
  181. package/dist/src/lib/config-object-descriptor.d.ts +127 -0
  182. package/dist/src/lib/config-object-descriptor.js +696 -0
  183. package/dist/src/lib/config-object-descriptor.js.map +1 -0
  184. package/dist/src/lib/config-payload.d.ts +85 -0
  185. package/dist/src/lib/config-payload.js +116 -0
  186. package/dist/src/lib/config-payload.js.map +1 -0
  187. package/dist/src/lib/config-surface.d.ts +140 -0
  188. package/dist/src/lib/config-surface.js +317 -0
  189. package/dist/src/lib/config-surface.js.map +1 -0
  190. package/dist/src/lib/config-toml.d.ts +10 -0
  191. package/dist/src/lib/config-toml.js +42 -0
  192. package/dist/src/lib/config-toml.js.map +1 -0
  193. package/dist/src/lib/config.d.ts +71 -0
  194. package/dist/src/lib/config.js +71 -68
  195. package/dist/src/lib/config.js.map +1 -1
  196. package/dist/src/lib/confirm-prompt.d.ts +83 -0
  197. package/dist/src/lib/confirm-prompt.js +110 -0
  198. package/dist/src/lib/confirm-prompt.js.map +1 -0
  199. package/dist/src/lib/constants.d.ts +11 -0
  200. package/dist/src/lib/constants.js +12 -0
  201. package/dist/src/lib/constants.js.map +1 -0
  202. package/dist/src/lib/crash-handlers.d.ts +20 -0
  203. package/dist/src/lib/crash-handlers.js +49 -0
  204. package/dist/src/lib/crash-handlers.js.map +1 -0
  205. package/dist/src/lib/credentials-store.d.ts +104 -0
  206. package/dist/src/lib/credentials-store.js +336 -0
  207. package/dist/src/lib/credentials-store.js.map +1 -0
  208. package/dist/src/lib/csv.d.ts +47 -0
  209. package/dist/src/lib/csv.js +172 -0
  210. package/dist/src/lib/csv.js.map +1 -0
  211. package/dist/src/lib/data-input.d.ts +23 -0
  212. package/dist/src/lib/data-input.js +50 -0
  213. package/dist/src/lib/data-input.js.map +1 -0
  214. package/dist/src/lib/db-codegen/dbFingerprint.d.ts +10 -0
  215. package/dist/src/lib/db-codegen/dbFingerprint.js +17 -0
  216. package/dist/src/lib/db-codegen/dbFingerprint.js.map +1 -0
  217. package/dist/src/lib/db-codegen/dbGenerator.d.ts +67 -0
  218. package/dist/src/lib/db-codegen/dbGenerator.js +170 -0
  219. package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -0
  220. package/dist/src/lib/db-codegen/dbNaming.d.ts +87 -0
  221. package/dist/src/lib/db-codegen/dbNaming.js +180 -0
  222. package/dist/src/lib/db-codegen/dbNaming.js.map +1 -0
  223. package/dist/src/lib/db-codegen/dbTemplates.d.ts +272 -0
  224. package/dist/src/lib/db-codegen/dbTemplates.js +480 -0
  225. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -0
  226. package/dist/src/lib/db-codegen/dbTsTypes.d.ts +73 -0
  227. package/dist/src/lib/db-codegen/dbTsTypes.js +139 -0
  228. package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -0
  229. package/dist/src/lib/db-codegen/dbTypeIR.d.ts +146 -0
  230. package/dist/src/lib/db-codegen/dbTypeIR.js +517 -0
  231. package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -0
  232. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.d.ts +112 -0
  233. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js +211 -0
  234. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js.map +1 -0
  235. package/dist/src/lib/document-export-permissions.d.ts +30 -0
  236. package/dist/src/lib/document-export-permissions.js +54 -0
  237. package/dist/src/lib/document-export-permissions.js.map +1 -0
  238. package/dist/src/lib/env-resolver-core.d.ts +253 -0
  239. package/dist/src/lib/env-resolver-core.js +459 -0
  240. package/dist/src/lib/env-resolver-core.js.map +1 -0
  241. package/dist/src/lib/env-resolver.d.ts +99 -0
  242. package/dist/src/lib/env-resolver.js +153 -0
  243. package/dist/src/lib/env-resolver.js.map +1 -0
  244. package/dist/src/lib/fetch.d.ts +5 -0
  245. package/dist/src/lib/function-bundle.d.ts +141 -0
  246. package/dist/src/lib/function-bundle.js +333 -0
  247. package/dist/src/lib/function-bundle.js.map +1 -0
  248. package/dist/src/lib/function-collect.d.ts +123 -0
  249. package/dist/src/lib/function-collect.js +594 -0
  250. package/dist/src/lib/function-collect.js.map +1 -0
  251. package/dist/src/lib/function-db-types.d.ts +187 -0
  252. package/dist/src/lib/function-db-types.js +782 -0
  253. package/dist/src/lib/function-db-types.js.map +1 -0
  254. package/dist/src/lib/function-document-types.d.ts +50 -0
  255. package/dist/src/lib/function-document-types.js +163 -0
  256. package/dist/src/lib/function-document-types.js.map +1 -0
  257. package/dist/src/lib/function-grants-preflight.d.ts +64 -0
  258. package/dist/src/lib/function-grants-preflight.js +100 -0
  259. package/dist/src/lib/function-grants-preflight.js.map +1 -0
  260. package/dist/src/lib/function-log-tail.d.ts +83 -0
  261. package/dist/src/lib/function-log-tail.js +115 -0
  262. package/dist/src/lib/function-log-tail.js.map +1 -0
  263. package/dist/src/lib/function-schema-codegen.d.ts +118 -0
  264. package/dist/src/lib/function-schema-codegen.js +402 -0
  265. package/dist/src/lib/function-schema-codegen.js.map +1 -0
  266. package/dist/src/lib/function-sync.d.ts +263 -0
  267. package/dist/src/lib/function-sync.js +604 -0
  268. package/dist/src/lib/function-sync.js.map +1 -0
  269. package/dist/src/lib/function-triggers.d.ts +66 -0
  270. package/dist/src/lib/function-triggers.js +285 -0
  271. package/dist/src/lib/function-triggers.js.map +1 -0
  272. package/dist/src/lib/function-typecheck.d.ts +86 -0
  273. package/dist/src/lib/function-typecheck.js +370 -0
  274. package/dist/src/lib/function-typecheck.js.map +1 -0
  275. package/dist/src/lib/generated-allowlist.d.ts +28 -0
  276. package/dist/src/lib/generated-allowlist.js +281 -0
  277. package/dist/src/lib/generated-allowlist.js.map +1 -0
  278. package/dist/src/lib/generated-config-surfaces.d.ts +1445 -0
  279. package/dist/src/lib/generated-config-surfaces.js +5836 -0
  280. package/dist/src/lib/generated-config-surfaces.js.map +1 -0
  281. package/dist/src/lib/generated-sdk-types.d.ts +12 -0
  282. package/dist/src/lib/generated-sdk-types.js +13 -0
  283. package/dist/src/lib/generated-sdk-types.js.map +1 -0
  284. package/dist/src/lib/generated-template-lint.d.ts +212 -0
  285. package/dist/src/lib/generated-template-lint.js +624 -0
  286. package/dist/src/lib/generated-template-lint.js.map +1 -0
  287. package/dist/src/lib/generated-workflow-model-fields.d.ts +20 -0
  288. package/dist/src/lib/generated-workflow-model-fields.js +53 -0
  289. package/dist/src/lib/generated-workflow-model-fields.js.map +1 -0
  290. package/dist/src/lib/google-client-secret-status.d.ts +35 -0
  291. package/dist/src/lib/google-client-secret-status.js +56 -0
  292. package/dist/src/lib/google-client-secret-status.js.map +1 -0
  293. package/dist/src/lib/init-adopt.d.ts +16 -0
  294. package/dist/src/lib/init-adopt.js +34 -0
  295. package/dist/src/lib/init-adopt.js.map +1 -0
  296. package/dist/src/lib/init-assets.d.ts +39 -0
  297. package/dist/src/lib/init-assets.js +97 -0
  298. package/dist/src/lib/init-assets.js.map +1 -0
  299. package/dist/src/lib/init-client-platforms.d.ts +14 -0
  300. package/dist/src/lib/init-client-platforms.js +71 -0
  301. package/dist/src/lib/init-client-platforms.js.map +1 -0
  302. package/dist/src/lib/init-config.d.ts +98 -0
  303. package/dist/src/lib/init-config.js +186 -0
  304. package/dist/src/lib/init-config.js.map +1 -0
  305. package/dist/src/lib/init-email-redirect-uris.d.ts +37 -0
  306. package/dist/src/lib/init-email-redirect-uris.js +46 -0
  307. package/dist/src/lib/init-email-redirect-uris.js.map +1 -0
  308. package/dist/src/lib/init-ios-links.d.ts +91 -0
  309. package/dist/src/lib/init-ios-links.js +219 -0
  310. package/dist/src/lib/init-ios-links.js.map +1 -0
  311. package/dist/src/lib/init-plan.d.ts +80 -0
  312. package/dist/src/lib/init-plan.js +95 -0
  313. package/dist/src/lib/init-plan.js.map +1 -0
  314. package/dist/src/lib/init-production-env.d.ts +48 -0
  315. package/dist/src/lib/init-production-env.js +59 -0
  316. package/dist/src/lib/init-production-env.js.map +1 -0
  317. package/dist/src/lib/init-schema.d.ts +74 -0
  318. package/dist/src/lib/init-schema.js +358 -0
  319. package/dist/src/lib/init-schema.js.map +1 -0
  320. package/dist/src/lib/init-xcode.d.ts +34 -0
  321. package/dist/src/lib/init-xcode.js +138 -0
  322. package/dist/src/lib/init-xcode.js.map +1 -0
  323. package/dist/src/lib/integration-request-config.d.ts +30 -0
  324. package/dist/src/lib/integration-request-config.js +145 -0
  325. package/dist/src/lib/integration-request-config.js.map +1 -0
  326. package/dist/src/lib/integration-selector.d.ts +42 -0
  327. package/dist/src/lib/integration-selector.js +46 -0
  328. package/dist/src/lib/integration-selector.js.map +1 -0
  329. package/dist/src/lib/ios-app-id.d.ts +34 -0
  330. package/dist/src/lib/ios-app-id.js +69 -0
  331. package/dist/src/lib/ios-app-id.js.map +1 -0
  332. package/dist/src/lib/local-state.d.ts +55 -0
  333. package/dist/src/lib/local-state.js +167 -0
  334. package/dist/src/lib/local-state.js.map +1 -0
  335. package/dist/src/lib/local-test-cases.d.ts +63 -0
  336. package/dist/src/lib/local-test-cases.js +136 -0
  337. package/dist/src/lib/local-test-cases.js.map +1 -0
  338. package/dist/src/lib/log-inspection.d.ts +680 -0
  339. package/dist/src/lib/log-inspection.js +784 -0
  340. package/dist/src/lib/log-inspection.js.map +1 -0
  341. package/dist/src/lib/migration-nag.d.ts +49 -0
  342. package/dist/src/lib/migration-nag.js +164 -0
  343. package/dist/src/lib/migration-nag.js.map +1 -0
  344. package/dist/src/lib/object-status-filter.d.ts +22 -0
  345. package/dist/src/lib/object-status-filter.js +45 -0
  346. package/dist/src/lib/object-status-filter.js.map +1 -0
  347. package/dist/src/lib/output.d.ts +109 -0
  348. package/dist/src/lib/output.js +191 -8
  349. package/dist/src/lib/output.js.map +1 -1
  350. package/dist/src/lib/package-manager.d.ts +140 -0
  351. package/dist/src/lib/package-manager.js +305 -0
  352. package/dist/src/lib/package-manager.js.map +1 -0
  353. package/dist/src/lib/paginate.d.ts +83 -0
  354. package/dist/src/lib/paginate.js +95 -0
  355. package/dist/src/lib/paginate.js.map +1 -0
  356. package/dist/src/lib/platform-owned.d.ts +63 -0
  357. package/dist/src/lib/platform-owned.js +85 -0
  358. package/dist/src/lib/platform-owned.js.map +1 -0
  359. package/dist/src/lib/project-config.d.ts +122 -0
  360. package/dist/src/lib/project-config.js +244 -0
  361. package/dist/src/lib/project-config.js.map +1 -0
  362. package/dist/src/lib/query-operators.d.ts +43 -0
  363. package/dist/src/lib/query-operators.js +80 -0
  364. package/dist/src/lib/query-operators.js.map +1 -0
  365. package/dist/src/lib/record-filter.d.ts +18 -0
  366. package/dist/src/lib/record-filter.js +55 -0
  367. package/dist/src/lib/record-filter.js.map +1 -0
  368. package/dist/src/lib/refresh-admin-credentials.d.ts +65 -0
  369. package/dist/src/lib/refresh-admin-credentials.js +103 -0
  370. package/dist/src/lib/refresh-admin-credentials.js.map +1 -0
  371. package/dist/src/lib/resolve-init-dev-port.d.ts +56 -0
  372. package/dist/src/lib/resolve-init-dev-port.js +55 -0
  373. package/dist/src/lib/resolve-init-dev-port.js.map +1 -0
  374. package/dist/src/lib/resolve-init-server.d.ts +64 -0
  375. package/dist/src/lib/resolve-init-server.js +77 -0
  376. package/dist/src/lib/resolve-init-server.js.map +1 -0
  377. package/dist/src/lib/resolve-platform.d.ts +74 -0
  378. package/dist/src/lib/resolve-platform.js +105 -0
  379. package/dist/src/lib/resolve-platform.js.map +1 -0
  380. package/dist/src/lib/run-status.d.ts +19 -0
  381. package/dist/src/lib/run-status.generated.d.ts +39 -0
  382. package/dist/src/lib/run-status.generated.js +66 -0
  383. package/dist/src/lib/run-status.generated.js.map +1 -0
  384. package/dist/src/lib/run-status.js +19 -0
  385. package/dist/src/lib/run-status.js.map +1 -0
  386. package/dist/src/lib/server-text-normalization.d.ts +51 -0
  387. package/dist/src/lib/server-text-normalization.js +90 -0
  388. package/dist/src/lib/server-text-normalization.js.map +1 -0
  389. package/dist/src/lib/server-url.d.ts +22 -0
  390. package/dist/src/lib/server-url.js +33 -0
  391. package/dist/src/lib/server-url.js.map +1 -0
  392. package/dist/src/lib/signing-secret-status.d.ts +81 -0
  393. package/dist/src/lib/signing-secret-status.js +116 -0
  394. package/dist/src/lib/signing-secret-status.js.map +1 -0
  395. package/dist/src/lib/skill-installer.d.ts +25 -0
  396. package/dist/src/lib/skill-installer.js +266 -0
  397. package/dist/src/lib/skill-installer.js.map +1 -0
  398. package/dist/src/lib/snapshots.d.ts +98 -0
  399. package/dist/src/lib/snapshots.js +294 -0
  400. package/dist/src/lib/snapshots.js.map +1 -0
  401. package/dist/src/lib/swift-codegen/dbGenerator.d.ts +113 -0
  402. package/dist/src/lib/swift-codegen/dbGenerator.js +914 -0
  403. package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -0
  404. package/dist/src/lib/swift-codegen/dbSwiftTypes.d.ts +42 -0
  405. package/dist/src/lib/swift-codegen/dbSwiftTypes.js +100 -0
  406. package/dist/src/lib/swift-codegen/dbSwiftTypes.js.map +1 -0
  407. package/dist/src/lib/swift-codegen/generator.d.ts +94 -0
  408. package/dist/src/lib/swift-codegen/generator.js +440 -0
  409. package/dist/src/lib/swift-codegen/generator.js.map +1 -0
  410. package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +72 -0
  411. package/dist/src/lib/swift-codegen/schemaToSwift.js +644 -0
  412. package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -0
  413. package/dist/src/lib/swift-codegen/siblingSymbols.d.ts +94 -0
  414. package/dist/src/lib/swift-codegen/siblingSymbols.js +155 -0
  415. package/dist/src/lib/swift-codegen/siblingSymbols.js.map +1 -0
  416. package/dist/src/lib/swift-codegen/swiftNaming.d.ts +85 -0
  417. package/dist/src/lib/swift-codegen/swiftNaming.js +198 -0
  418. package/dist/src/lib/swift-codegen/swiftNaming.js.map +1 -0
  419. package/dist/src/lib/sync-dir-selector.d.ts +21 -0
  420. package/dist/src/lib/sync-dir-selector.js +30 -0
  421. package/dist/src/lib/sync-dir-selector.js.map +1 -0
  422. package/dist/src/lib/sync-paths.d.ts +123 -0
  423. package/dist/src/lib/sync-paths.js +193 -0
  424. package/dist/src/lib/sync-paths.js.map +1 -0
  425. package/dist/src/lib/sync-resource-types.d.ts +563 -0
  426. package/dist/src/lib/sync-resource-types.js +1073 -0
  427. package/dist/src/lib/sync-resource-types.js.map +1 -0
  428. package/dist/src/lib/sync-selectors.d.ts +95 -0
  429. package/dist/src/lib/sync-selectors.js +228 -0
  430. package/dist/src/lib/sync-selectors.js.map +1 -0
  431. package/dist/src/lib/template.d.ts +170 -0
  432. package/dist/src/lib/template.js +484 -68
  433. package/dist/src/lib/template.js.map +1 -1
  434. package/dist/src/lib/test-case-file-names.d.ts +40 -0
  435. package/dist/src/lib/test-case-file-names.js +91 -0
  436. package/dist/src/lib/test-case-file-names.js.map +1 -0
  437. package/dist/src/lib/test-case-keys.d.ts +29 -0
  438. package/dist/src/lib/test-case-keys.js +55 -0
  439. package/dist/src/lib/test-case-keys.js.map +1 -0
  440. package/dist/src/lib/test-case-variables.d.ts +29 -0
  441. package/dist/src/lib/test-case-variables.js +71 -0
  442. package/dist/src/lib/test-case-variables.js.map +1 -0
  443. package/dist/src/lib/token-inject.d.ts +56 -0
  444. package/dist/src/lib/token-inject.js +204 -0
  445. package/dist/src/lib/token-inject.js.map +1 -0
  446. package/dist/src/lib/toml-database-config.d.ts +123 -0
  447. package/dist/src/lib/toml-database-config.js +544 -0
  448. package/dist/src/lib/toml-database-config.js.map +1 -0
  449. package/dist/src/lib/toml-metadata-config.d.ts +151 -0
  450. package/dist/src/lib/toml-metadata-config.js +476 -0
  451. package/dist/src/lib/toml-metadata-config.js.map +1 -0
  452. package/dist/src/lib/toml-native-form.d.ts +46 -0
  453. package/dist/src/lib/toml-native-form.js +78 -0
  454. package/dist/src/lib/toml-native-form.js.map +1 -0
  455. package/dist/src/lib/toml-params-validator.d.ts +129 -0
  456. package/dist/src/lib/toml-params-validator.js +298 -0
  457. package/dist/src/lib/toml-params-validator.js.map +1 -0
  458. package/dist/src/lib/toml-scalar-edit.d.ts +43 -0
  459. package/dist/src/lib/toml-scalar-edit.js +283 -0
  460. package/dist/src/lib/toml-scalar-edit.js.map +1 -0
  461. package/dist/src/lib/user-selector.d.ts +24 -0
  462. package/dist/src/lib/user-selector.js +33 -0
  463. package/dist/src/lib/user-selector.js.map +1 -0
  464. package/dist/src/lib/version-check.d.ts +35 -0
  465. package/dist/src/lib/version-check.js +241 -0
  466. package/dist/src/lib/version-check.js.map +1 -0
  467. package/dist/src/lib/watch.d.ts +121 -0
  468. package/dist/src/lib/watch.js +169 -0
  469. package/dist/src/lib/watch.js.map +1 -0
  470. package/dist/src/lib/web-url.d.ts +40 -0
  471. package/dist/src/lib/web-url.js +76 -0
  472. package/dist/src/lib/web-url.js.map +1 -0
  473. package/dist/src/lib/workflow-apply.d.ts +110 -0
  474. package/dist/src/lib/workflow-apply.js +164 -0
  475. package/dist/src/lib/workflow-apply.js.map +1 -0
  476. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.d.ts +129 -0
  477. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js +269 -0
  478. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js.map +1 -0
  479. package/dist/src/lib/workflow-codegen/generator.d.ts +96 -0
  480. package/dist/src/lib/workflow-codegen/generator.js +361 -0
  481. package/dist/src/lib/workflow-codegen/generator.js.map +1 -0
  482. package/dist/src/lib/workflow-codegen/invokerIR.d.ts +94 -0
  483. package/dist/src/lib/workflow-codegen/invokerIR.js +76 -0
  484. package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -0
  485. package/dist/src/lib/workflow-codegen/naming.d.ts +33 -0
  486. package/dist/src/lib/workflow-codegen/naming.js +81 -0
  487. package/dist/src/lib/workflow-codegen/naming.js.map +1 -0
  488. package/dist/src/lib/workflow-codegen/schemaToTs.d.ts +80 -0
  489. package/dist/src/lib/workflow-codegen/schemaToTs.js +303 -0
  490. package/dist/src/lib/workflow-codegen/schemaToTs.js.map +1 -0
  491. package/dist/src/lib/workflow-config-apply.d.ts +70 -0
  492. package/dist/src/lib/workflow-config-apply.js +137 -0
  493. package/dist/src/lib/workflow-config-apply.js.map +1 -0
  494. package/dist/src/lib/workflow-config-sidecar.d.ts +63 -0
  495. package/dist/src/lib/workflow-config-sidecar.js +96 -0
  496. package/dist/src/lib/workflow-config-sidecar.js.map +1 -0
  497. package/dist/src/lib/workflow-defaults.d.ts +29 -0
  498. package/dist/src/lib/workflow-defaults.js +41 -0
  499. package/dist/src/lib/workflow-defaults.js.map +1 -0
  500. package/dist/src/lib/workflow-field-descriptor.d.ts +91 -0
  501. package/dist/src/lib/workflow-field-descriptor.js +153 -0
  502. package/dist/src/lib/workflow-field-descriptor.js.map +1 -0
  503. package/dist/src/lib/workflow-fragments.d.ts +64 -0
  504. package/dist/src/lib/workflow-fragments.js +342 -0
  505. package/dist/src/lib/workflow-fragments.js.map +1 -0
  506. package/dist/src/lib/workflow-include-preserve.d.ts +76 -0
  507. package/dist/src/lib/workflow-include-preserve.js +286 -0
  508. package/dist/src/lib/workflow-include-preserve.js.map +1 -0
  509. package/dist/src/lib/workflow-payload.d.ts +98 -0
  510. package/dist/src/lib/workflow-payload.js +178 -0
  511. package/dist/src/lib/workflow-payload.js.map +1 -0
  512. package/dist/src/lib/workflow-toml-validator.d.ts +207 -0
  513. package/dist/src/lib/workflow-toml-validator.js +762 -0
  514. package/dist/src/lib/workflow-toml-validator.js.map +1 -0
  515. package/dist/src/lib/workflow-usage.d.ts +198 -0
  516. package/dist/src/lib/workflow-usage.js +312 -0
  517. package/dist/src/lib/workflow-usage.js.map +1 -0
  518. package/dist/src/types/index.d.ts +591 -0
  519. package/dist/src/validators.d.ts +65 -0
  520. package/dist/src/validators.js +64 -0
  521. package/dist/src/validators.js.map +1 -0
  522. package/package.json +34 -9
@@ -1,11 +1,27 @@
1
- import { readFileSync, writeFileSync, statSync } from "fs";
2
- import { basename } from "path";
3
- import * as TOML from "@iarna/toml";
4
- import { lookup as mimeLookup } from "mime-types";
1
+ import { writeFileSync } from "fs";
5
2
  import { ApiClient } from "../lib/api-client.js";
6
3
  import { resolveAppId } from "../lib/config.js";
7
4
  import chalk from "chalk";
8
- import { success, error, info, warn, keyValue, formatTable, formatId, formatDate, formatStatus, formatDuration, json, divider, } from "../lib/output.js";
5
+ import { success, error, info, warn, keyValue, result as printResult, formatTable, formatId, formatDate, formatStatus, formatDuration, json, divider, progress, progressEnd, } from "../lib/output.js";
6
+ import { pollUntil } from "../lib/watch.js";
7
+ import { confirmPrompt } from "../lib/confirm-prompt.js";
8
+ import { parseStatusFilter } from "../lib/object-status-filter.js";
9
+ import { formatAccessRuleForDisplay } from "../lib/access-rule-display.js";
10
+ import { requireTestCaseInputVariables } from "../lib/test-case-variables.js";
11
+ import { resolvePromptSelector } from "../lib/block-selector.js";
12
+ /**
13
+ * Resolve a prompt reference — its server id, or the author-chosen key its
14
+ * TOML file is named for.
15
+ *
16
+ * Under TOML-only authoring the key is what an operator has: `prompts/<key>.toml`
17
+ * is the object, and the id is a server detail they never wrote down. Mirrors
18
+ * `resolveScriptId` in `scripts.ts`. The resolution itself lives in
19
+ * `block-selector.ts` (#3249) so the `tests` verbs and `configs get` agree on
20
+ * it — and drain every page of the list, where this used to read one.
21
+ */
22
+ async function resolvePromptId(client, appId, ref) {
23
+ return (await resolvePromptSelector(client, appId, ref)).blockId;
24
+ }
9
25
  export function registerPromptsCommands(program) {
10
26
  const prompts = program
11
27
  .command("prompts")
@@ -13,9 +29,19 @@ export function registerPromptsCommands(program) {
13
29
  .addHelpText("after", `
14
30
  Examples:
15
31
  $ primitive prompts list
16
- $ primitive prompts create --from-file summarizer.toml
32
+ $ primitive config create prompt summarizer # scaffold prompts/summarizer.toml
33
+ $ primitive config push --only prompt/summarizer # apply it
17
34
  $ primitive prompts execute 01HXY... --vars '{"text":"Hello world"}'
18
- $ primitive prompts configs activate 01HXY... 01ABC...
35
+ $ primitive prompts configs list summarizer # what the server runs
36
+ `);
37
+ // Server-side configuration is authored in TOML and applied with `config push`
38
+ // (issue #2645) — this group no longer carries config-setting flags.
39
+ prompts.addHelpText("after", `
40
+ Configuration lives in prompts/<key>.toml:
41
+ $ primitive config fields prompt # the file's keys, types and defaults
42
+ $ primitive config create prompt <key> # scaffold one locally
43
+ $ primitive config set prompt/<key> <path>=<value>
44
+ $ primitive config push --only prompt/<key>
19
45
  `);
20
46
  // List prompts
21
47
  prompts
@@ -23,14 +49,22 @@ Examples:
23
49
  .description("List prompts")
24
50
  .argument("[app-id]", "App ID (uses current app if not specified)")
25
51
  .option("--app <app-id>", "App ID")
26
- .option("--status <status>", "Filter by status: draft, active, archived")
52
+ .option("--status <status>", "Filter by status: active, inactive, archived")
27
53
  .option("--json", "Output as JSON")
28
54
  .action(async (appId, options) => {
29
55
  const resolvedAppId = resolveAppId(appId, options);
30
56
  const client = new ApiClient();
31
57
  try {
58
+ // #2803 — `draft` is gone; refuse it by name rather than returning the
59
+ // empty list a canonical filter would produce. #2887 added `archived`:
60
+ // the delete flow writes one now, so asking for the retired rows is a
61
+ // question with an answer rather than a 400.
32
62
  const { items } = await client.listPrompts(resolvedAppId, {
33
- status: options.status,
63
+ status: parseStatusFilter("prompts", options.status, [
64
+ "active",
65
+ "inactive",
66
+ "archived",
67
+ ]),
34
68
  });
35
69
  if (options.json) {
36
70
  json(items);
@@ -53,209 +87,154 @@ Examples:
53
87
  process.exit(1);
54
88
  }
55
89
  });
56
- // Create prompt
90
+ // #2645 criterion 9 / #2803 — the operational verbs. They are the only
91
+ // writers of `status`, which is server-owned and no longer a TOML key.
57
92
  prompts
58
- .command("create")
59
- .description("Create a new prompt")
60
- .argument("[app-id]", "App ID (uses current app if not specified)")
61
- .option("--app <app-id>", "App ID")
62
- .option("--key <key>", "Prompt key (unique identifier)")
63
- .option("--name <name>", "Display name")
64
- .option("--description <desc>", "Description")
65
- .option("--provider <provider>", "LLM provider: openrouter, gemini", "openrouter")
66
- .option("--model <model>", "Model name")
67
- .option("--system-prompt <prompt>", "System prompt")
68
- .option("--user-template <template>", "User prompt template")
69
- .option("--temperature <temp>", "Temperature (0-1)")
70
- .option("--max-tokens <n>", "Max output tokens")
71
- .option("--output-format <format>", "Output format: text, json")
72
- .option("--input-schema <json>", "Input schema as JSON")
73
- .option("--output-schema <json>", "Output schema as JSON")
74
- .option("--from-file <path>", "Load prompt from TOML file")
93
+ .command("disable")
94
+ .description("Take prompt out of service now. Operational, not configuration: `status` is server-owned and 'config push' can neither set nor clear it.")
95
+ .argument("<prompt-id>", "Prompt ID")
96
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
75
97
  .option("--json", "Output as JSON")
76
- .action(async (appId, options) => {
77
- const resolvedAppId = resolveAppId(appId, options);
98
+ .action(async (id, options) => {
99
+ const resolvedAppId = resolveAppId(undefined, options);
78
100
  const client = new ApiClient();
79
- let payload;
80
- if (options.fromFile) {
81
- try {
82
- const content = readFileSync(options.fromFile, "utf-8");
83
- const tomlData = TOML.parse(content);
84
- const prompt = tomlData.prompt || tomlData;
85
- const config = tomlData.configs?.[0] || {};
86
- payload = {
87
- promptKey: prompt.key || prompt.promptKey,
88
- displayName: prompt.displayName || prompt.name,
89
- description: prompt.description,
90
- provider: config.provider || prompt.provider || "openrouter",
91
- model: config.model || prompt.model,
92
- systemPrompt: config.systemPrompt || prompt.systemPrompt,
93
- userPromptTemplate: config.userPromptTemplate || prompt.userPromptTemplate,
94
- temperature: config.temperature || prompt.temperature,
95
- maxTokens: config.maxTokens || prompt.maxTokens,
96
- outputFormat: config.outputFormat || prompt.outputFormat,
97
- inputSchema: prompt.inputSchema,
98
- outputSchema: prompt.outputSchema,
99
- };
100
- }
101
- catch (err) {
102
- error(`Failed to read TOML file: ${err.message}`);
103
- process.exit(1);
104
- }
105
- }
106
- else {
107
- if (!options.key || !options.name || !options.model || !options.userTemplate) {
108
- error("Required: --key, --name, --model, --user-template (or use --from-file)");
109
- process.exit(1);
110
- }
111
- payload = {
112
- promptKey: options.key,
113
- displayName: options.name,
114
- description: options.description,
115
- provider: options.provider,
116
- model: options.model,
117
- systemPrompt: options.systemPrompt,
118
- userPromptTemplate: options.userTemplate,
119
- temperature: options.temperature,
120
- maxTokens: options.maxTokens ? parseInt(options.maxTokens) : undefined,
121
- outputFormat: options.outputFormat,
122
- inputSchema: options.inputSchema,
123
- outputSchema: options.outputSchema,
124
- };
125
- }
126
101
  try {
127
- const result = await client.createPrompt(resolvedAppId, payload);
102
+ const result = await client.setPromptAvailability(resolvedAppId, id, "disable");
128
103
  if (options.json) {
129
104
  json(result);
130
105
  return;
131
106
  }
132
- success(`Prompt created: ${result.displayName}`);
133
- keyValue("Prompt ID", result.promptId);
134
- keyValue("Key", result.promptKey);
107
+ success(`Prompt disabled: ${result.promptKey || id}`);
135
108
  }
136
109
  catch (err) {
137
110
  error(err.message);
138
111
  process.exit(1);
139
112
  }
140
113
  });
141
- // Get prompt
142
114
  prompts
143
- .command("get")
144
- .description("Get prompt details")
115
+ .command("enable")
116
+ .description("Put prompt back in service.")
145
117
  .argument("<prompt-id>", "Prompt ID")
146
118
  .option("--app <app-id>", "App ID (uses current app if not specified)")
147
119
  .option("--json", "Output as JSON")
148
- .action(async (promptId, options) => {
120
+ .action(async (id, options) => {
149
121
  const resolvedAppId = resolveAppId(undefined, options);
150
122
  const client = new ApiClient();
151
123
  try {
152
- const result = await client.getPrompt(resolvedAppId, promptId);
124
+ const result = await client.setPromptAvailability(resolvedAppId, id, "enable");
153
125
  if (options.json) {
154
126
  json(result);
155
127
  return;
156
128
  }
157
- keyValue("Prompt ID", result.promptId);
158
- keyValue("Key", result.promptKey);
159
- keyValue("Name", result.displayName);
160
- keyValue("Description", result.description);
161
- keyValue("Status", formatStatus(result.status));
162
- keyValue("Active Config", result.activeConfigId || "-");
163
- if (result.configs && result.configs.length > 0) {
164
- divider();
165
- info(`Configs (${result.configs.length}):`);
166
- console.log(formatTable(result.configs, [
167
- { header: "ID", key: "configId", format: formatId },
168
- { header: "NAME", key: "configName" },
169
- { header: "MODEL", key: "model" },
170
- { header: "STATUS", key: "status", format: formatStatus },
171
- ]));
172
- }
129
+ success(`Prompt enabled: ${result.promptKey || id}`);
173
130
  }
174
131
  catch (err) {
175
132
  error(err.message);
176
133
  process.exit(1);
177
134
  }
178
135
  });
179
- // Update prompt
136
+ // #2907 — the delete flow's CLI spelling. Not a second writer of `status`:
137
+ // this is the plain `DELETE` #2887 made retire-with-history, which until now
138
+ // only the admin API and the console could reach. Named `archive`, not
139
+ // `delete`, because a configuration noun gets no `delete` verb
140
+ // (docs/cli-design.md) and prune-by-push stays the only CLI hard-delete path.
180
141
  prompts
181
- .command("update")
182
- .description("Update a prompt")
142
+ .command("archive")
143
+ .description("Retire a prompt (soft delete). Delete-lifecycle, not `disable`: the row and its configs are kept so executions still resolve, it goes on holding its key, and there is no un-archive.")
183
144
  .argument("<prompt-id>", "Prompt ID")
184
145
  .option("--app <app-id>", "App ID (uses current app if not specified)")
185
- .option("--name <name>", "Display name")
186
- .option("--description <desc>", "Description")
187
- .option("--status <status>", "Status: draft, active, archived")
188
- .option("--input-schema <json>", "Input schema as JSON")
189
- .option("--output-schema <json>", "Output schema as JSON")
146
+ .option("-y, --yes", "Skip confirmation prompt")
190
147
  .option("--json", "Output as JSON")
191
- .action(async (promptId, options) => {
148
+ .addHelpText("after", `
149
+ What archiving a prompt costs (retire-with-history):
150
+ The member endpoint refuses it, and so does admin execution — the diagnostic
151
+ runs an INACTIVE prompt on purpose, but not an archived one.
152
+ Its configs, executions and analytics keep resolving what they point at.
153
+ The archived row still holds its promptKey.
154
+ \`primitive prompts enable\` refuses an archived prompt; there is no
155
+ un-archive.
156
+ The per-version \`[[configs]] status\` is a different key and is untouched.
157
+
158
+ Reclaiming the key means hard-deleting the row:
159
+ $ rm prompts/<key>.toml
160
+ $ primitive config push --prune # confirmed; hard-deletes the row
161
+ # or hard-delete it in the console, then re-add the file and push.
162
+ `)
163
+ .action(async (id, options) => {
192
164
  const resolvedAppId = resolveAppId(undefined, options);
193
- const payload = {};
194
- if (options.name)
195
- payload.displayName = options.name;
196
- if (options.description)
197
- payload.description = options.description;
198
- if (options.status)
199
- payload.status = options.status;
200
- if (options.inputSchema)
201
- payload.inputSchema = options.inputSchema;
202
- if (options.outputSchema)
203
- payload.outputSchema = options.outputSchema;
204
- if (Object.keys(payload).length === 0) {
205
- error("No update options specified.");
206
- process.exit(1);
165
+ if (!options.yes) {
166
+ let confirmed;
167
+ try {
168
+ confirmed = await confirmPrompt(`Archive prompt ${id}? It is retired, its configs and executions are ` +
169
+ `kept, it goes on holding its key, and there is no un-archive.`);
170
+ }
171
+ catch (err) {
172
+ error(err.message);
173
+ process.exit(1);
174
+ }
175
+ if (!confirmed) {
176
+ info("Cancelled.");
177
+ return;
178
+ }
207
179
  }
208
180
  const client = new ApiClient();
209
181
  try {
210
- const result = await client.updatePrompt(resolvedAppId, promptId, payload);
182
+ // The soft delete: no `hard`, so the configs and stored prompt bodies
183
+ // survive and every execution row keeps resolving.
184
+ const result = await client.deletePrompt(resolvedAppId, id);
211
185
  if (options.json) {
212
186
  json(result);
213
187
  return;
214
188
  }
215
- success("Prompt updated.");
189
+ success(`Prompt archived: ${id}`);
190
+ info("Its configs and executions keep resolving. The row still holds its key — reclaim it by removing its file and running a confirmed `primitive config push --prune`.");
216
191
  }
217
192
  catch (err) {
218
193
  error(err.message);
219
194
  process.exit(1);
220
195
  }
221
196
  });
222
- // Delete prompt
223
197
  prompts
224
- .command("delete")
225
- .description("Delete or archive a prompt")
198
+ .command("get")
199
+ .description("Get prompt details")
226
200
  .argument("<prompt-id>", "Prompt ID")
227
201
  .option("--app <app-id>", "App ID (uses current app if not specified)")
228
- .option("--hard", "Permanently delete instead of archive")
229
- .option("-y, --yes", "Skip confirmation prompt")
202
+ .option("--json", "Output as JSON")
230
203
  .action(async (promptId, options) => {
231
204
  const resolvedAppId = resolveAppId(undefined, options);
232
- if (!options.yes) {
233
- const action = options.hard ? "permanently delete" : "archive";
234
- const inquirer = await import("inquirer");
235
- const { confirm } = await inquirer.default.prompt([
236
- {
237
- type: "confirm",
238
- name: "confirm",
239
- message: `Are you sure you want to ${action} prompt ${promptId}?`,
240
- default: false,
241
- },
242
- ]);
243
- if (!confirm) {
244
- info("Cancelled.");
245
- return;
246
- }
247
- }
248
205
  const client = new ApiClient();
249
206
  try {
250
- await client.deletePrompt(resolvedAppId, promptId, options.hard);
251
- success(`Prompt ${options.hard ? "deleted" : "archived"}.`);
207
+ const result = await client.getPrompt(resolvedAppId, promptId);
208
+ if (options.json) {
209
+ json(result);
210
+ return;
211
+ }
212
+ printResult("Prompt ID", result.promptId);
213
+ printResult("Key", result.promptKey);
214
+ printResult("Name", result.displayName);
215
+ printResult("Description", result.description);
216
+ printResult("Status", formatStatus(result.status));
217
+ // #2652: no rule means every non-admin execution is denied. Print that
218
+ // in words — a blank or "-" would read as "unrestricted" and hide a
219
+ // prompt that was never given a rule.
220
+ printResult("Access Rule", formatAccessRuleForDisplay(result.accessRule, "prompt"));
221
+ printResult("Active Config", result.activeConfigId || "-");
222
+ if (result.configs && result.configs.length > 0) {
223
+ divider();
224
+ info(`Configs (${result.configs.length}):`);
225
+ console.log(formatTable(result.configs, [
226
+ { header: "ID", key: "configId", format: formatId },
227
+ { header: "NAME", key: "configName" },
228
+ { header: "MODEL", key: "model" },
229
+ { header: "STATUS", key: "status", format: formatStatus },
230
+ ]));
231
+ }
252
232
  }
253
233
  catch (err) {
254
234
  error(err.message);
255
235
  process.exit(1);
256
236
  }
257
237
  });
258
- // Execute prompt
259
238
  prompts
260
239
  .command("execute")
261
240
  .description("Execute a prompt with variables")
@@ -397,8 +376,25 @@ Examples:
397
376
  process.exit(1);
398
377
  }
399
378
  });
400
- // Configs subcommand
401
- const configs = prompts.command("configs").description("Manage prompt configs");
379
+ // Configs subcommand — reads only. A prompt's configs are `[[configs]]`
380
+ // entries in its own TOML file (#2645), so the verbs that used to write them
381
+ // were a second writer for state the file already owns.
382
+ const configs = prompts
383
+ .command("configs")
384
+ .description("Inspect a prompt's configs (authored in prompts/<key>.toml)")
385
+ .addHelpText("after", `
386
+ A prompt's configs are the \`[[configs]]\` entries of prompts/<key>.toml. Add,
387
+ edit, retire (\`status = "archived"\`) or mark one live (\`active = true\`) in that
388
+ file, then apply it:
389
+
390
+ $ primitive config push --only prompt/<key>
391
+
392
+ That per-config \`status\` says which VERSION is retired and is yours to author.
393
+ The PROMPT's own availability is server-owned and has no TOML key: use
394
+ 'primitive prompts disable' / 'enable'.
395
+
396
+ Duplicating a config is copying its \`[[configs]]\` block and renaming it.
397
+ `);
402
398
  // List configs
403
399
  configs
404
400
  .command("list")
@@ -432,154 +428,51 @@ Examples:
432
428
  process.exit(1);
433
429
  }
434
430
  });
435
- // Create config
431
+ // Get one config. `list` says which configs exist; this says what one of
432
+ // them actually runs — the body, provider and model the server would use.
433
+ // With `prompts configs create/update` gone, this is where that content is
434
+ // read back from.
436
435
  configs
437
- .command("create")
438
- .description("Create a new config for a prompt")
439
- .argument("<prompt-id>", "Prompt ID")
436
+ .command("get")
437
+ .description("Show one config of a prompt as the server has it")
438
+ .argument("<prompt>", "Prompt ID or key")
439
+ .argument("<config>", "Config ID or name")
440
440
  .option("--app <app-id>", "App ID (uses current app if not specified)")
441
- .option("--name <name>", "Config name")
442
- .option("--description <desc>", "Description")
443
- .option("--provider <provider>", "LLM provider: openrouter, gemini", "openrouter")
444
- .option("--model <model>", "Model name")
445
- .option("--system-prompt <prompt>", "System prompt")
446
- .option("--user-template <template>", "User prompt template")
447
- .option("--temperature <temp>", "Temperature (0-1)")
448
- .option("--max-tokens <n>", "Max output tokens")
449
- .option("--output-format <format>", "Output format: text, json")
450
441
  .option("--json", "Output as JSON")
451
- .action(async (promptId, options) => {
442
+ .action(async (prompt, config, options) => {
452
443
  const resolvedAppId = resolveAppId(undefined, options);
453
- if (!options.name || !options.model || !options.userTemplate) {
454
- error("Required: --name, --model, --user-template");
455
- process.exit(1);
456
- }
457
- const payload = {
458
- configName: options.name,
459
- description: options.description,
460
- provider: options.provider,
461
- model: options.model,
462
- systemPrompt: options.systemPrompt,
463
- userPromptTemplate: options.userTemplate,
464
- temperature: options.temperature,
465
- maxTokens: options.maxTokens ? parseInt(options.maxTokens) : undefined,
466
- outputFormat: options.outputFormat,
467
- };
468
444
  const client = new ApiClient();
469
445
  try {
470
- const result = await client.createPromptConfig(resolvedAppId, promptId, payload);
471
- if (options.json) {
472
- json(result);
473
- return;
446
+ const promptId = await resolvePromptId(client, resolvedAppId, prompt);
447
+ const { items } = await client.listPromptConfigs(resolvedAppId, promptId);
448
+ const summary = (items ?? []).find((c) => c.configId === config || c.configName === config);
449
+ if (!summary) {
450
+ error(`Prompt '${prompt}' has no config '${config}'. Run 'primitive prompts configs list ${prompt}' to see them.`);
451
+ process.exit(1);
474
452
  }
475
- success(`Config created: ${result.configName}`);
476
- keyValue("Config ID", result.configId);
477
- }
478
- catch (err) {
479
- error(err.message);
480
- process.exit(1);
481
- }
482
- });
483
- // Activate config
484
- configs
485
- .command("activate")
486
- .description("Set a config as the active config for a prompt")
487
- .argument("<prompt-id>", "Prompt ID")
488
- .argument("<config-id>", "Config ID")
489
- .option("--app <app-id>", "App ID (uses current app if not specified)")
490
- .action(async (promptId, configId, options) => {
491
- const resolvedAppId = resolveAppId(undefined, options);
492
- const client = new ApiClient();
493
- try {
494
- await client.activatePromptConfig(resolvedAppId, promptId, configId);
495
- success(`Config ${configId} activated.`);
496
- }
497
- catch (err) {
498
- error(err.message);
499
- process.exit(1);
500
- }
501
- });
502
- // Duplicate config
503
- configs
504
- .command("duplicate")
505
- .description("Duplicate a config")
506
- .argument("<prompt-id>", "Prompt ID")
507
- .argument("<config-id>", "Config ID to duplicate")
508
- .option("--app <app-id>", "App ID (uses current app if not specified)")
509
- .option("--name <name>", "New config name")
510
- .option("--json", "Output as JSON")
511
- .action(async (promptId, configId, options) => {
512
- const resolvedAppId = resolveAppId(undefined, options);
513
- const client = new ApiClient();
514
- try {
515
- const result = await client.duplicatePromptConfig(resolvedAppId, promptId, configId, {
516
- configName: options.name,
517
- });
453
+ const full = (await client.getPromptConfig(resolvedAppId, promptId, summary.configId)) ??
454
+ summary;
518
455
  if (options.json) {
519
- json(result);
456
+ json(full);
520
457
  return;
521
458
  }
522
- success(`Config duplicated: ${result.configName}`);
523
- keyValue("New Config ID", result.configId);
524
- }
525
- catch (err) {
526
- error(err.message);
527
- process.exit(1);
528
- }
529
- });
530
- // Update config
531
- configs
532
- .command("update")
533
- .description("Update a config")
534
- .argument("<prompt-id>", "Prompt ID")
535
- .argument("<config-id>", "Config ID")
536
- .option("--app <app-id>", "App ID (uses current app if not specified)")
537
- .option("--name <name>", "Config name")
538
- .option("--description <desc>", "Description")
539
- .option("--provider <provider>", "LLM provider: openrouter, gemini")
540
- .option("--model <model>", "Model name")
541
- .option("--system-prompt <prompt>", "System prompt")
542
- .option("--user-template <template>", "User prompt template")
543
- .option("--temperature <temp>", "Temperature (0-1)")
544
- .option("--max-tokens <n>", "Max output tokens")
545
- .option("--output-format <format>", "Output format: text, json")
546
- .option("--status <status>", "Status: active, archived")
547
- .option("--json", "Output as JSON")
548
- .action(async (promptId, configId, options) => {
549
- const resolvedAppId = resolveAppId(undefined, options);
550
- const payload = {};
551
- if (options.name)
552
- payload.configName = options.name;
553
- if (options.description)
554
- payload.description = options.description;
555
- if (options.provider)
556
- payload.provider = options.provider;
557
- if (options.model)
558
- payload.model = options.model;
559
- if (options.systemPrompt)
560
- payload.systemPrompt = options.systemPrompt;
561
- if (options.userTemplate)
562
- payload.userPromptTemplate = options.userTemplate;
563
- if (options.temperature)
564
- payload.temperature = options.temperature;
565
- if (options.maxTokens)
566
- payload.maxTokens = parseInt(options.maxTokens);
567
- if (options.outputFormat)
568
- payload.outputFormat = options.outputFormat;
569
- if (options.status)
570
- payload.status = options.status;
571
- if (Object.keys(payload).length === 0) {
572
- error("No update options specified.");
573
- process.exit(1);
574
- }
575
- const client = new ApiClient();
576
- try {
577
- const result = await client.updatePromptConfig(resolvedAppId, promptId, configId, payload);
578
- if (options.json) {
579
- json(result);
580
- return;
459
+ printResult("Config ID", full.configId);
460
+ printResult("Name", full.configName);
461
+ printResult("Status", formatStatus(full.status));
462
+ printResult("Provider", full.provider || "-");
463
+ printResult("Model", full.model || "-");
464
+ if (full.description)
465
+ printResult("Description", full.description);
466
+ if (full.systemPrompt) {
467
+ divider();
468
+ info("System prompt:");
469
+ console.log(full.systemPrompt);
470
+ }
471
+ if (full.userPrompt) {
472
+ divider();
473
+ info("User prompt:");
474
+ console.log(full.userPrompt);
581
475
  }
582
- success("Config updated.");
583
476
  }
584
477
  catch (err) {
585
478
  error(err.message);
@@ -591,26 +484,37 @@ Examples:
591
484
  // ============================================
592
485
  const tests = prompts
593
486
  .command("tests")
594
- .description("Manage and run prompt test cases")
487
+ .description("Run and inspect prompt test cases")
595
488
  .addHelpText("after", `
489
+ Test cases are authored in TOML at prompts/<key>.tests/<name>.toml and applied
490
+ with \`primitive config push\` (issue #2769); \`config fields prompt\` lists the
491
+ fields. Deleting a case is removing its file and running \`config push --prune\`.
492
+
493
+ Every verb here takes the prompt's KEY or its ID (#3249). An identifier that
494
+ names no prompt in the app fails; "No test cases found." only ever means the
495
+ prompt exists and has no cases.
496
+
596
497
  Examples:
597
- $ primitive prompts tests list <prompt-id>
598
- $ primitive prompts tests create <prompt-id> --name "Basic test" --vars '{"input":"hello"}'
599
- $ primitive prompts tests run <prompt-id> <test-case-id>
600
- $ primitive prompts tests run-all <prompt-id>
601
- $ primitive prompts tests run-all <prompt-id> --test-cases 01ABC,01DEF,01GHI
498
+ $ primitive prompts tests list <prompt>
499
+ $ primitive prompts tests run <prompt> <test-case-id>
500
+ $ primitive prompts tests run-all <prompt>
501
+ $ primitive prompts tests run-all <prompt> --test-cases 01ABC,01DEF,01GHI
602
502
  `);
603
503
  // List test cases
604
504
  tests
605
505
  .command("list")
606
506
  .description("List test cases for a prompt")
607
- .argument("<prompt-id>", "Prompt ID")
507
+ .argument("<prompt>", "Prompt key or ID")
608
508
  .option("--app <app-id>", "App ID (uses current app if not specified)")
609
509
  .option("--json", "Output as JSON")
610
- .action(async (promptId, options) => {
510
+ .action(async (prompt, options) => {
611
511
  const resolvedAppId = resolveAppId(undefined, options);
612
512
  const client = new ApiClient();
613
513
  try {
514
+ // #3249 — resolve BEFORE listing: the test-case endpoint answers an
515
+ // unknown block with an empty page, so an unresolved identifier would
516
+ // print "No test cases found." and exit 0.
517
+ const promptId = await resolvePromptId(client, resolvedAppId, prompt);
614
518
  const { items } = await client.listTestCases(resolvedAppId, "prompt", promptId);
615
519
  if (options.json) {
616
520
  json(items);
@@ -632,100 +536,28 @@ Examples:
632
536
  process.exit(1);
633
537
  }
634
538
  });
635
- // Create test case
636
- tests
637
- .command("create")
638
- .description("Create a test case for a prompt")
639
- .argument("<prompt-id>", "Prompt ID")
640
- .option("--app <app-id>", "App ID (uses current app if not specified)")
641
- .option("--name <name>", "Test case name (required)")
642
- .option("--vars <json>", "Input variables as JSON (required)")
643
- .option("--pattern <regex>", "Expected output pattern (regex)")
644
- .option("--contains <json>", "Expected strings to contain (JSON array)")
645
- .option("--json-subset <json>", "Expected JSON subset to match")
646
- .option("--config <config-id>", "Config ID to use for this test")
647
- .option("--evaluator-prompt <prompt-id>", "Evaluator prompt ID")
648
- .option("--evaluator-config <config-id>", "Evaluator config ID")
649
- .option("--json", "Output as JSON")
650
- .action(async (promptId, options) => {
651
- const resolvedAppId = resolveAppId(undefined, options);
652
- if (!options.name || !options.vars) {
653
- error("Required: --name and --vars");
654
- process.exit(1);
655
- }
656
- let inputVariables;
657
- try {
658
- inputVariables = JSON.parse(options.vars);
659
- }
660
- catch {
661
- error("Invalid JSON in --vars");
662
- process.exit(1);
663
- }
664
- let expectedOutputContains;
665
- if (options.contains) {
666
- try {
667
- expectedOutputContains = JSON.parse(options.contains);
668
- }
669
- catch {
670
- error("Invalid JSON in --contains");
671
- process.exit(1);
672
- }
673
- }
674
- let expectedJsonSubset;
675
- if (options.jsonSubset) {
676
- try {
677
- expectedJsonSubset = JSON.parse(options.jsonSubset);
678
- }
679
- catch {
680
- error("Invalid JSON in --json-subset");
681
- process.exit(1);
682
- }
683
- }
684
- const client = new ApiClient();
685
- try {
686
- const result = await client.createTestCase(resolvedAppId, "prompt", promptId, {
687
- name: options.name,
688
- inputVariables,
689
- expectedOutputPattern: options.pattern,
690
- expectedOutputContains,
691
- expectedJsonSubset,
692
- configId: options.config,
693
- evaluatorPromptId: options.evaluatorPrompt,
694
- evaluatorConfigId: options.evaluatorConfig,
695
- });
696
- if (options.json) {
697
- json(result);
698
- return;
699
- }
700
- success(`Test case created: ${result.name}`);
701
- keyValue("Test Case ID", result.testCaseId);
702
- }
703
- catch (err) {
704
- error(err.message);
705
- process.exit(1);
706
- }
707
- });
708
539
  // Get test case
709
540
  tests
710
541
  .command("get")
711
542
  .description("Get test case details")
712
- .argument("<prompt-id>", "Prompt ID")
543
+ .argument("<prompt>", "Prompt key or ID")
713
544
  .argument("<test-case-id>", "Test Case ID")
714
545
  .option("--app <app-id>", "App ID (uses current app if not specified)")
715
546
  .option("--json", "Output as JSON")
716
- .action(async (promptId, testCaseId, options) => {
547
+ .action(async (prompt, testCaseId, options) => {
717
548
  const resolvedAppId = resolveAppId(undefined, options);
718
549
  const client = new ApiClient();
719
550
  try {
551
+ const promptId = await resolvePromptId(client, resolvedAppId, prompt);
720
552
  const result = await client.getTestCase(resolvedAppId, "prompt", promptId, testCaseId);
721
553
  if (options.json) {
722
554
  json(result);
723
555
  return;
724
556
  }
725
- keyValue("Test Case ID", result.testCaseId);
726
- keyValue("Name", result.name);
727
- keyValue("Config ID", result.configId || "-");
728
- keyValue("Created", formatDate(result.createdAt));
557
+ printResult("Test Case ID", result.testCaseId);
558
+ printResult("Name", result.name);
559
+ printResult("Config ID", result.configId || "-");
560
+ printResult("Created", formatDate(result.createdAt));
729
561
  divider();
730
562
  info("Input Variables:");
731
563
  try {
@@ -737,7 +569,7 @@ Examples:
737
569
  }
738
570
  if (result.expectedOutputPattern) {
739
571
  divider();
740
- keyValue("Expected Pattern", result.expectedOutputPattern);
572
+ printResult("Expected Pattern", result.expectedOutputPattern);
741
573
  }
742
574
  if (result.expectedOutputContains) {
743
575
  divider();
@@ -761,8 +593,8 @@ Examples:
761
593
  }
762
594
  if (result.evaluatorPromptId) {
763
595
  divider();
764
- keyValue("Evaluator Prompt", result.evaluatorPromptId);
765
- keyValue("Evaluator Config", result.evaluatorConfigId || "-");
596
+ printResult("Evaluator Prompt", result.evaluatorPromptId);
597
+ printResult("Evaluator Config", result.evaluatorConfigId || "-");
766
598
  }
767
599
  }
768
600
  catch (err) {
@@ -770,144 +602,26 @@ Examples:
770
602
  process.exit(1);
771
603
  }
772
604
  });
773
- // Update test case
774
- tests
775
- .command("update")
776
- .description("Update a test case")
777
- .argument("<prompt-id>", "Prompt ID")
778
- .argument("<test-case-id>", "Test Case ID")
779
- .option("--app <app-id>", "App ID (uses current app if not specified)")
780
- .option("--name <name>", "Test case name")
781
- .option("--vars <json>", "Input variables as JSON")
782
- .option("--pattern <regex>", "Expected output pattern (regex)")
783
- .option("--contains <json>", "Expected strings to contain (JSON array)")
784
- .option("--json-subset <json>", "Expected JSON subset to match")
785
- .option("--config <config-id>", "Config ID to use for this test")
786
- .option("--clear-pattern", "Clear expected pattern")
787
- .option("--clear-contains", "Clear expected contains")
788
- .option("--clear-json-subset", "Clear expected JSON subset")
789
- .option("--json", "Output as JSON")
790
- .action(async (promptId, testCaseId, options) => {
791
- const resolvedAppId = resolveAppId(undefined, options);
792
- const payload = {};
793
- if (options.name)
794
- payload.name = options.name;
795
- if (options.vars) {
796
- try {
797
- payload.inputVariables = JSON.parse(options.vars);
798
- }
799
- catch {
800
- error("Invalid JSON in --vars");
801
- process.exit(1);
802
- }
803
- }
804
- if (options.clearPattern) {
805
- payload.expectedOutputPattern = null;
806
- }
807
- else if (options.pattern) {
808
- payload.expectedOutputPattern = options.pattern;
809
- }
810
- if (options.clearContains) {
811
- payload.expectedOutputContains = null;
812
- }
813
- else if (options.contains) {
814
- try {
815
- payload.expectedOutputContains = JSON.parse(options.contains);
816
- }
817
- catch {
818
- error("Invalid JSON in --contains");
819
- process.exit(1);
820
- }
821
- }
822
- if (options.clearJsonSubset) {
823
- payload.expectedJsonSubset = null;
824
- }
825
- else if (options.jsonSubset) {
826
- try {
827
- payload.expectedJsonSubset = JSON.parse(options.jsonSubset);
828
- }
829
- catch {
830
- error("Invalid JSON in --json-subset");
831
- process.exit(1);
832
- }
833
- }
834
- if (options.config)
835
- payload.configId = options.config;
836
- if (Object.keys(payload).length === 0) {
837
- error("No update options specified.");
838
- process.exit(1);
839
- }
840
- const client = new ApiClient();
841
- try {
842
- const result = await client.updateTestCase(resolvedAppId, "prompt", promptId, testCaseId, payload);
843
- if (options.json) {
844
- json(result);
845
- return;
846
- }
847
- success("Test case updated.");
848
- }
849
- catch (err) {
850
- error(err.message);
851
- process.exit(1);
852
- }
853
- });
854
- // Delete test case
855
- tests
856
- .command("delete")
857
- .description("Delete a test case")
858
- .argument("<prompt-id>", "Prompt ID")
859
- .argument("<test-case-id>", "Test Case ID")
860
- .option("--app <app-id>", "App ID (uses current app if not specified)")
861
- .option("-y, --yes", "Skip confirmation prompt")
862
- .action(async (promptId, testCaseId, options) => {
863
- const resolvedAppId = resolveAppId(undefined, options);
864
- if (!options.yes) {
865
- const inquirer = await import("inquirer");
866
- const { confirm } = await inquirer.default.prompt([
867
- {
868
- type: "confirm",
869
- name: "confirm",
870
- message: `Are you sure you want to delete test case ${testCaseId}?`,
871
- default: false,
872
- },
873
- ]);
874
- if (!confirm) {
875
- info("Cancelled.");
876
- return;
877
- }
878
- }
879
- const client = new ApiClient();
880
- try {
881
- await client.deleteTestCase(resolvedAppId, "prompt", promptId, testCaseId);
882
- success("Test case deleted.");
883
- }
884
- catch (err) {
885
- error(err.message);
886
- process.exit(1);
887
- }
888
- });
889
605
  // Run a single test case
890
606
  tests
891
607
  .command("run")
892
608
  .description("Run a single test case")
893
- .argument("<prompt-id>", "Prompt ID")
609
+ .argument("<prompt>", "Prompt key or ID")
894
610
  .argument("<test-case-id>", "Test Case ID")
895
611
  .option("--app <app-id>", "App ID (uses current app if not specified)")
896
612
  .option("--config <config-id>", "Override config ID")
897
613
  .option("--json", "Output as JSON")
898
- .action(async (promptId, testCaseId, options) => {
614
+ .action(async (prompt, testCaseId, options) => {
899
615
  const resolvedAppId = resolveAppId(undefined, options);
900
616
  const client = new ApiClient();
901
617
  try {
618
+ const promptId = await resolvePromptId(client, resolvedAppId, prompt);
902
619
  // Get test case to retrieve its variables
903
620
  const testCase = await client.getTestCase(resolvedAppId, "prompt", promptId, testCaseId);
904
- let variables = {};
905
- try {
906
- variables = JSON.parse(testCase.inputVariables || "{}");
907
- }
908
- catch {
909
- // Use empty object if parsing fails
910
- }
621
+ // #3158 — the case's stored input IS the run's input, and the server
622
+ // hands it back parsed; `JSON.parse` on that object threw and the run
623
+ // went ahead with `{}`, disagreeing with `run-all` over the same case.
624
+ const variables = requireTestCaseInputVariables(testCase.inputVariables, testCaseId);
911
625
  const result = await client.executeBlockTest(resolvedAppId, "prompt", promptId, {
912
626
  variables,
913
627
  testCaseId,
@@ -962,15 +676,18 @@ Examples:
962
676
  tests
963
677
  .command("run-all")
964
678
  .description("Run all test cases for a prompt, or a specific subset")
965
- .argument("<prompt-id>", "Prompt ID")
679
+ .argument("<prompt>", "Prompt key or ID")
966
680
  .option("--app <app-id>", "App ID (uses current app if not specified)")
967
681
  .option("--config <config-id>", "Override config ID for all tests")
968
682
  .option("--test-cases <ids>", "Comma-separated list of test case IDs to run (runs only these)")
969
683
  .option("--json", "Output as JSON")
970
- .action(async (promptId, options) => {
684
+ .action(async (prompt, options) => {
971
685
  const resolvedAppId = resolveAppId(undefined, options);
972
686
  const client = new ApiClient();
973
687
  try {
688
+ // #3249 — resolve first, so `Total Tests: 0` can only ever mean the
689
+ // prompt exists and has no cases, never that the identifier was wrong.
690
+ const promptId = await resolvePromptId(client, resolvedAppId, prompt);
974
691
  // Parse test case IDs if provided
975
692
  let testCaseIds;
976
693
  if (options.testCases) {
@@ -1028,15 +745,16 @@ Examples:
1028
745
  tests
1029
746
  .command("runs")
1030
747
  .description("List recent test runs for a prompt")
1031
- .argument("<prompt-id>", "Prompt ID")
748
+ .argument("<prompt>", "Prompt key or ID")
1032
749
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1033
750
  .option("--limit <n>", "Maximum number of runs to show", "20")
1034
751
  .option("--group <group>", "Filter by comparison group")
1035
752
  .option("--json", "Output as JSON")
1036
- .action(async (promptId, options) => {
753
+ .action(async (prompt, options) => {
1037
754
  const resolvedAppId = resolveAppId(undefined, options);
1038
755
  const client = new ApiClient();
1039
756
  try {
757
+ const promptId = await resolvePromptId(client, resolvedAppId, prompt);
1040
758
  const { items } = await client.listTestRuns(resolvedAppId, "prompt", promptId, {
1041
759
  limit: parseInt(options.limit),
1042
760
  comparisonGroup: options.group,
@@ -1078,24 +796,25 @@ Examples:
1078
796
  .description("Run tests in parallel using workflow-based execution")
1079
797
  .addHelpText("after", `
1080
798
  Examples:
1081
- $ primitive prompts tests batch start <prompt-id>
1082
- $ primitive prompts tests batch start <prompt-id> --test-cases 01ABC,01DEF
1083
- $ primitive prompts tests batch status <prompt-id> <batch-id>
1084
- $ primitive prompts tests batch cancel <prompt-id> <batch-id>
799
+ $ primitive prompts tests batch start <prompt>
800
+ $ primitive prompts tests batch start <prompt> --test-cases 01ABC,01DEF
801
+ $ primitive prompts tests batch status <prompt> <batch-id>
802
+ $ primitive prompts tests batch cancel <prompt> <batch-id>
1085
803
  `);
1086
804
  // Start batch tests
1087
805
  batch
1088
806
  .command("start")
1089
807
  .description("Start parallel batch test execution")
1090
- .argument("<prompt-id>", "Prompt ID")
808
+ .argument("<prompt>", "Prompt key or ID")
1091
809
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1092
810
  .option("--config <config-id>", "Override config ID for all tests")
1093
811
  .option("--test-cases <ids>", "Comma-separated list of test case IDs to run")
1094
812
  .option("--json", "Output as JSON")
1095
- .action(async (promptId, options) => {
813
+ .action(async (prompt, options) => {
1096
814
  const resolvedAppId = resolveAppId(undefined, options);
1097
815
  const client = new ApiClient();
1098
816
  try {
817
+ const promptId = await resolvePromptId(client, resolvedAppId, prompt);
1099
818
  let testCaseIds;
1100
819
  if (options.testCases) {
1101
820
  testCaseIds = options.testCases.split(",").map((id) => id.trim()).filter(Boolean);
@@ -1132,27 +851,37 @@ Examples:
1132
851
  batch
1133
852
  .command("status")
1134
853
  .description("Get status of a batch test execution")
1135
- .argument("<prompt-id>", "Prompt ID")
854
+ .argument("<prompt>", "Prompt key or ID")
1136
855
  .argument("<batch-id>", "Batch ID")
1137
856
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1138
857
  .option("--wait", "Wait for completion (polls every 2 seconds)")
1139
858
  .option("--json", "Output as JSON")
1140
- .action(async (promptId, batchId, options) => {
859
+ .action(async (prompt, batchId, options) => {
1141
860
  const resolvedAppId = resolveAppId(undefined, options);
1142
861
  const client = new ApiClient();
1143
862
  try {
863
+ const promptId = await resolvePromptId(client, resolvedAppId, prompt);
1144
864
  const fetchStatus = async () => {
1145
865
  return client.getBatchTestStatus(resolvedAppId, "prompt", promptId, batchId);
1146
866
  };
1147
867
  let result = await fetchStatus();
1148
868
  if (options.wait && result.status === "running") {
1149
869
  info("Waiting for batch completion...");
1150
- while (result.status === "running") {
1151
- await new Promise((r) => setTimeout(r, 2000));
1152
- result = await fetchStatus();
1153
- process.stdout.write(`\r Completed: ${result.completed}/${result.results?.length || 0} `);
1154
- }
1155
- console.log();
870
+ // #1969 — shared poll primitive (delay-then-probe every 2s, honoring
871
+ // an AbortSignal). `onError: "throw"` preserves the prior loop's
872
+ // fail-fast behavior: a throwing fetchStatus (expired token, deleted
873
+ // batch → 401/404) propagates to the command-level catch below and
874
+ // exits non-zero, rather than spinning forever with a frozen progress
875
+ // line. (The follow/watch tail loops legitimately keep "continue".)
876
+ const controller = new AbortController();
877
+ result = await pollUntil(fetchStatus, (r) => r.status !== "running", {
878
+ intervalMs: 2000,
879
+ signal: controller.signal,
880
+ onError: "throw",
881
+ // Progress goes to stderr so it can't corrupt JSON on stdout under --json.
882
+ onValue: (r) => progress(` Completed: ${r.completed}/${r.results?.length || 0} `),
883
+ });
884
+ progressEnd();
1156
885
  }
1157
886
  if (options.json) {
1158
887
  json(result);
@@ -1188,23 +917,22 @@ Examples:
1188
917
  batch
1189
918
  .command("cancel")
1190
919
  .description("Cancel a running batch test execution")
1191
- .argument("<prompt-id>", "Prompt ID")
920
+ .argument("<prompt>", "Prompt key or ID")
1192
921
  .argument("<batch-id>", "Batch ID")
1193
922
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1194
923
  .option("-y, --yes", "Skip confirmation prompt")
1195
924
  .option("--json", "Output as JSON")
1196
- .action(async (promptId, batchId, options) => {
925
+ .action(async (prompt, batchId, options) => {
1197
926
  const resolvedAppId = resolveAppId(undefined, options);
1198
927
  if (!options.yes) {
1199
- const inquirer = await import("inquirer");
1200
- const { confirm } = await inquirer.default.prompt([
1201
- {
1202
- type: "confirm",
1203
- name: "confirm",
1204
- message: `Are you sure you want to cancel batch ${batchId}?`,
1205
- default: false,
1206
- },
1207
- ]);
928
+ let confirm;
929
+ try {
930
+ confirm = await confirmPrompt(`Are you sure you want to cancel batch ${batchId}?`);
931
+ }
932
+ catch (err) {
933
+ error(err.message);
934
+ process.exit(1);
935
+ }
1208
936
  if (!confirm) {
1209
937
  info("Cancelled.");
1210
938
  return;
@@ -1212,6 +940,7 @@ Examples:
1212
940
  }
1213
941
  const client = new ApiClient();
1214
942
  try {
943
+ const promptId = await resolvePromptId(client, resolvedAppId, prompt);
1215
944
  // First get current status to get instance IDs
1216
945
  const status = await client.getBatchTestStatus(resolvedAppId, "prompt", promptId, batchId);
1217
946
  // Get instance IDs that are still running
@@ -1244,26 +973,29 @@ Examples:
1244
973
  // ============================================
1245
974
  const attachments = tests
1246
975
  .command("attachments")
1247
- .description("Manage test case file attachments")
976
+ .description("Inspect test case file attachments")
1248
977
  .addHelpText("after", `
978
+ Attachments are authored as files in prompts/<key>.tests/<name>/ and applied
979
+ with \`primitive config push\` (#2769); removing one and running
980
+ \`config push --prune\` deletes it server-side. These verbs read server state.
981
+
1249
982
  Examples:
1250
- $ primitive prompts tests attachments list <prompt-id> <test-case-id>
1251
- $ primitive prompts tests attachments upload <prompt-id> <test-case-id> ./document.pdf
1252
- $ primitive prompts tests attachments download <prompt-id> <test-case-id> document.pdf
1253
- $ primitive prompts tests attachments delete <prompt-id> <test-case-id> document.pdf
983
+ $ primitive prompts tests attachments list <prompt> <test-case-id>
984
+ $ primitive prompts tests attachments download <prompt> <test-case-id> document.pdf
1254
985
  `);
1255
986
  // List attachments
1256
987
  attachments
1257
988
  .command("list")
1258
989
  .description("List attachments for a test case")
1259
- .argument("<prompt-id>", "Prompt ID")
990
+ .argument("<prompt>", "Prompt key or ID")
1260
991
  .argument("<test-case-id>", "Test Case ID")
1261
992
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1262
993
  .option("--json", "Output as JSON")
1263
- .action(async (promptId, testCaseId, options) => {
994
+ .action(async (prompt, testCaseId, options) => {
1264
995
  const resolvedAppId = resolveAppId(undefined, options);
1265
996
  const client = new ApiClient();
1266
997
  try {
998
+ const promptId = await resolvePromptId(client, resolvedAppId, prompt);
1267
999
  const result = await client.listTestCaseAttachments(resolvedAppId, "prompt", promptId, testCaseId);
1268
1000
  if (options.json) {
1269
1001
  json(result.attachments);
@@ -1288,62 +1020,20 @@ Examples:
1288
1020
  process.exit(1);
1289
1021
  }
1290
1022
  });
1291
- // Upload attachment
1292
- attachments
1293
- .command("upload")
1294
- .description("Upload a file attachment to a test case")
1295
- .argument("<prompt-id>", "Prompt ID")
1296
- .argument("<test-case-id>", "Test Case ID")
1297
- .argument("<file-path>", "Path to the file to upload")
1298
- .option("--app <app-id>", "App ID (uses current app if not specified)")
1299
- .option("--name <name>", "Override the filename")
1300
- .option("--json", "Output as JSON")
1301
- .action(async (promptId, testCaseId, filePath, options) => {
1302
- const resolvedAppId = resolveAppId(undefined, options);
1303
- const client = new ApiClient();
1304
- try {
1305
- // Check file exists and get stats
1306
- const stats = statSync(filePath);
1307
- if (!stats.isFile()) {
1308
- error(`Not a file: ${filePath}`);
1309
- process.exit(1);
1310
- }
1311
- // Check file size (10 MB limit)
1312
- const TEN_MB = 10 * 1024 * 1024;
1313
- if (stats.size > TEN_MB) {
1314
- error(`File too large (max 10MB): ${formatFileSize(stats.size)}`);
1315
- process.exit(1);
1316
- }
1317
- const filename = options.name || basename(filePath);
1318
- const data = readFileSync(filePath);
1319
- const contentType = mimeLookup(filePath) || "application/octet-stream";
1320
- const result = await client.uploadTestCaseAttachment(resolvedAppId, "prompt", promptId, testCaseId, filename, data, contentType);
1321
- if (options.json) {
1322
- json(result);
1323
- return;
1324
- }
1325
- success(`Uploaded: ${filename}`);
1326
- keyValue("Size", formatFileSize(result.attachment.size));
1327
- keyValue("Type", result.attachment.contentType);
1328
- }
1329
- catch (err) {
1330
- error(err.message);
1331
- process.exit(1);
1332
- }
1333
- });
1334
1023
  // Download attachment
1335
1024
  attachments
1336
1025
  .command("download")
1337
1026
  .description("Download a file attachment from a test case")
1338
- .argument("<prompt-id>", "Prompt ID")
1027
+ .argument("<prompt>", "Prompt key or ID")
1339
1028
  .argument("<test-case-id>", "Test Case ID")
1340
1029
  .argument("<filename>", "Name of the file to download")
1341
1030
  .argument("[output-path]", "Output path (defaults to current directory)")
1342
1031
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1343
- .action(async (promptId, testCaseId, filename, outputPath, options) => {
1032
+ .action(async (prompt, testCaseId, filename, outputPath, options) => {
1344
1033
  const resolvedAppId = resolveAppId(undefined, options);
1345
1034
  const client = new ApiClient();
1346
1035
  try {
1036
+ const promptId = await resolvePromptId(client, resolvedAppId, prompt);
1347
1037
  const result = await client.downloadTestCaseAttachment(resolvedAppId, "prompt", promptId, testCaseId, filename);
1348
1038
  const finalPath = outputPath || filename;
1349
1039
  writeFileSync(finalPath, result.data);
@@ -1356,42 +1046,6 @@ Examples:
1356
1046
  process.exit(1);
1357
1047
  }
1358
1048
  });
1359
- // Delete attachment
1360
- attachments
1361
- .command("delete")
1362
- .description("Delete a file attachment from a test case")
1363
- .argument("<prompt-id>", "Prompt ID")
1364
- .argument("<test-case-id>", "Test Case ID")
1365
- .argument("<filename>", "Name of the file to delete")
1366
- .option("--app <app-id>", "App ID (uses current app if not specified)")
1367
- .option("-y, --yes", "Skip confirmation prompt")
1368
- .action(async (promptId, testCaseId, filename, options) => {
1369
- const resolvedAppId = resolveAppId(undefined, options);
1370
- if (!options.yes) {
1371
- const inquirer = await import("inquirer");
1372
- const { confirm } = await inquirer.default.prompt([
1373
- {
1374
- type: "confirm",
1375
- name: "confirm",
1376
- message: `Are you sure you want to delete attachment "${filename}"?`,
1377
- default: false,
1378
- },
1379
- ]);
1380
- if (!confirm) {
1381
- info("Cancelled.");
1382
- return;
1383
- }
1384
- }
1385
- const client = new ApiClient();
1386
- try {
1387
- await client.deleteTestCaseAttachment(resolvedAppId, "prompt", promptId, testCaseId, filename);
1388
- success(`Deleted: ${filename}`);
1389
- }
1390
- catch (err) {
1391
- error(err.message);
1392
- process.exit(1);
1393
- }
1394
- });
1395
1049
  }
1396
1050
  function formatFileSize(bytes) {
1397
1051
  if (bytes < 1024)