primitive-admin 1.0.61 → 1.0.63

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 (320) hide show
  1. package/README.md +125 -69
  2. package/assets/skill/skills/primitive-platform/SKILL.md +336 -37
  3. package/dist/bin/primitive.js +25 -14
  4. package/dist/bin/primitive.js.map +1 -1
  5. package/dist/src/commands/analytics.js +36 -16
  6. package/dist/src/commands/analytics.js.map +1 -1
  7. package/dist/src/commands/apps.js +52 -188
  8. package/dist/src/commands/apps.js.map +1 -1
  9. package/dist/src/commands/blob-buckets.js +13 -153
  10. package/dist/src/commands/blob-buckets.js.map +1 -1
  11. package/dist/src/commands/collection-type-configs.js +14 -100
  12. package/dist/src/commands/collection-type-configs.js.map +1 -1
  13. package/dist/src/commands/collections.js +47 -8
  14. package/dist/src/commands/collections.js.map +1 -1
  15. package/dist/src/commands/config.d.ts +46 -0
  16. package/dist/src/commands/config.js +479 -0
  17. package/dist/src/commands/config.js.map +1 -0
  18. package/dist/src/commands/connections.js +12 -7
  19. package/dist/src/commands/connections.js.map +1 -1
  20. package/dist/src/commands/cron-triggers.js +84 -176
  21. package/dist/src/commands/cron-triggers.js.map +1 -1
  22. package/dist/src/commands/database-type-configs.d.ts +2 -0
  23. package/dist/src/commands/database-type-configs.js +171 -0
  24. package/dist/src/commands/database-type-configs.js.map +1 -0
  25. package/dist/src/commands/database-types.js +4 -4
  26. package/dist/src/commands/database-types.js.map +1 -1
  27. package/dist/src/commands/databases.d.ts +63 -0
  28. package/dist/src/commands/databases.js +649 -87
  29. package/dist/src/commands/databases.js.map +1 -1
  30. package/dist/src/commands/documents.js +869 -12
  31. package/dist/src/commands/documents.js.map +1 -1
  32. package/dist/src/commands/email-templates.js +21 -128
  33. package/dist/src/commands/email-templates.js.map +1 -1
  34. package/dist/src/commands/env.d.ts +12 -1
  35. package/dist/src/commands/env.js +115 -26
  36. package/dist/src/commands/env.js.map +1 -1
  37. package/dist/src/commands/feature-flags.d.ts +14 -0
  38. package/dist/src/commands/feature-flags.js +116 -0
  39. package/dist/src/commands/feature-flags.js.map +1 -0
  40. package/dist/src/commands/group-type-configs.js +14 -109
  41. package/dist/src/commands/group-type-configs.js.map +1 -1
  42. package/dist/src/commands/init.d.ts +24 -1
  43. package/dist/src/commands/init.js +1194 -279
  44. package/dist/src/commands/init.js.map +1 -1
  45. package/dist/src/commands/integrations.js +166 -435
  46. package/dist/src/commands/integrations.js.map +1 -1
  47. package/dist/src/commands/metadata-category-configs.d.ts +12 -0
  48. package/dist/src/commands/metadata-category-configs.js +112 -0
  49. package/dist/src/commands/metadata-category-configs.js.map +1 -0
  50. package/dist/src/commands/metadata.js +33 -141
  51. package/dist/src/commands/metadata.js.map +1 -1
  52. package/dist/src/commands/prompts.js +206 -563
  53. package/dist/src/commands/prompts.js.map +1 -1
  54. package/dist/src/commands/rule-sets.d.ts +1 -1
  55. package/dist/src/commands/rule-sets.js +16 -132
  56. package/dist/src/commands/rule-sets.js.map +1 -1
  57. package/dist/src/commands/scripts.d.ts +10 -0
  58. package/dist/src/commands/scripts.js +203 -241
  59. package/dist/src/commands/scripts.js.map +1 -1
  60. package/dist/src/commands/sessions.js +17 -5
  61. package/dist/src/commands/sessions.js.map +1 -1
  62. package/dist/src/commands/sync-app-settings.d.ts +113 -70
  63. package/dist/src/commands/sync-app-settings.js +193 -242
  64. package/dist/src/commands/sync-app-settings.js.map +1 -1
  65. package/dist/src/commands/sync.d.ts +1904 -51
  66. package/dist/src/commands/sync.js +9468 -1790
  67. package/dist/src/commands/sync.js.map +1 -1
  68. package/dist/src/commands/tokens.js +8 -2
  69. package/dist/src/commands/tokens.js.map +1 -1
  70. package/dist/src/commands/users.js +92 -1
  71. package/dist/src/commands/users.js.map +1 -1
  72. package/dist/src/commands/vars.js +44 -58
  73. package/dist/src/commands/vars.js.map +1 -1
  74. package/dist/src/commands/webhooks.js +318 -156
  75. package/dist/src/commands/webhooks.js.map +1 -1
  76. package/dist/src/commands/workflows.d.ts +49 -12
  77. package/dist/src/commands/workflows.js +652 -1086
  78. package/dist/src/commands/workflows.js.map +1 -1
  79. package/dist/src/lib/access-rule-display.d.ts +21 -0
  80. package/dist/src/lib/access-rule-display.js +34 -0
  81. package/dist/src/lib/access-rule-display.js.map +1 -0
  82. package/dist/src/lib/api-client.d.ts +541 -61
  83. package/dist/src/lib/api-client.js +559 -102
  84. package/dist/src/lib/api-client.js.map +1 -1
  85. package/dist/src/lib/app-settings-descriptor.d.ts +180 -25
  86. package/dist/src/lib/app-settings-descriptor.js +403 -82
  87. package/dist/src/lib/app-settings-descriptor.js.map +1 -1
  88. package/dist/src/lib/canonical-json.d.ts +12 -0
  89. package/dist/src/lib/canonical-json.js +35 -0
  90. package/dist/src/lib/canonical-json.js.map +1 -0
  91. package/dist/src/lib/cli-manifest.d.ts +8 -0
  92. package/dist/src/lib/cli-manifest.js +1 -0
  93. package/dist/src/lib/cli-manifest.js.map +1 -1
  94. package/dist/src/lib/codegen-shared/generatedFiles.d.ts +6 -2
  95. package/dist/src/lib/codegen-shared/generatedFiles.js +9 -2
  96. package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -1
  97. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +6 -6
  98. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +8 -8
  99. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -1
  100. package/dist/src/lib/config-object-descriptor.d.ts +127 -0
  101. package/dist/src/lib/config-object-descriptor.js +658 -0
  102. package/dist/src/lib/config-object-descriptor.js.map +1 -0
  103. package/dist/src/lib/config-payload.d.ts +85 -0
  104. package/dist/src/lib/config-payload.js +116 -0
  105. package/dist/src/lib/config-payload.js.map +1 -0
  106. package/dist/src/lib/config-surface.d.ts +130 -0
  107. package/dist/src/lib/config-surface.js +300 -0
  108. package/dist/src/lib/config-surface.js.map +1 -0
  109. package/dist/src/lib/credentials-store.d.ts +10 -0
  110. package/dist/src/lib/credentials-store.js +23 -0
  111. package/dist/src/lib/credentials-store.js.map +1 -1
  112. package/dist/src/lib/data-input.d.ts +23 -0
  113. package/dist/src/lib/data-input.js +50 -0
  114. package/dist/src/lib/data-input.js.map +1 -0
  115. package/dist/src/lib/db-codegen/dbTemplates.js +1 -1
  116. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
  117. package/dist/src/lib/db-codegen/dbTypeIR.d.ts +1 -1
  118. package/dist/src/lib/env-resolver-core.d.ts +187 -0
  119. package/dist/src/lib/env-resolver-core.js +325 -0
  120. package/dist/src/lib/env-resolver-core.js.map +1 -0
  121. package/dist/src/lib/env-resolver.d.ts +28 -6
  122. package/dist/src/lib/env-resolver.js +44 -32
  123. package/dist/src/lib/env-resolver.js.map +1 -1
  124. package/dist/src/lib/generated-allowlist.js +12 -0
  125. package/dist/src/lib/generated-allowlist.js.map +1 -1
  126. package/dist/src/lib/generated-config-surfaces.d.ts +682 -0
  127. package/dist/src/lib/generated-config-surfaces.js +4058 -0
  128. package/dist/src/lib/generated-config-surfaces.js.map +1 -0
  129. package/dist/src/lib/generated-template-lint.d.ts +212 -0
  130. package/dist/src/lib/generated-template-lint.js +624 -0
  131. package/dist/src/lib/generated-template-lint.js.map +1 -0
  132. package/dist/src/lib/init-adopt.d.ts +16 -0
  133. package/dist/src/lib/init-adopt.js +34 -0
  134. package/dist/src/lib/init-adopt.js.map +1 -0
  135. package/dist/src/lib/init-assets.d.ts +39 -0
  136. package/dist/src/lib/init-assets.js +97 -0
  137. package/dist/src/lib/init-assets.js.map +1 -0
  138. package/dist/src/lib/init-client-platforms.d.ts +14 -0
  139. package/dist/src/lib/init-client-platforms.js +71 -0
  140. package/dist/src/lib/init-client-platforms.js.map +1 -0
  141. package/dist/src/lib/init-config.d.ts +44 -5
  142. package/dist/src/lib/init-config.js +80 -7
  143. package/dist/src/lib/init-config.js.map +1 -1
  144. package/dist/src/lib/init-email-redirect-uris.d.ts +37 -0
  145. package/dist/src/lib/init-email-redirect-uris.js +46 -0
  146. package/dist/src/lib/init-email-redirect-uris.js.map +1 -0
  147. package/dist/src/lib/init-ios-links.d.ts +90 -0
  148. package/dist/src/lib/init-ios-links.js +218 -0
  149. package/dist/src/lib/init-ios-links.js.map +1 -0
  150. package/dist/src/lib/init-plan.d.ts +80 -0
  151. package/dist/src/lib/init-plan.js +95 -0
  152. package/dist/src/lib/init-plan.js.map +1 -0
  153. package/dist/src/lib/init-production-env.d.ts +48 -0
  154. package/dist/src/lib/init-production-env.js +59 -0
  155. package/dist/src/lib/init-production-env.js.map +1 -0
  156. package/dist/src/lib/init-schema.d.ts +74 -0
  157. package/dist/src/lib/init-schema.js +358 -0
  158. package/dist/src/lib/init-schema.js.map +1 -0
  159. package/dist/src/lib/init-xcode.d.ts +33 -0
  160. package/dist/src/lib/init-xcode.js +114 -0
  161. package/dist/src/lib/init-xcode.js.map +1 -0
  162. package/dist/src/lib/integration-request-config.d.ts +30 -0
  163. package/dist/src/lib/integration-request-config.js +145 -0
  164. package/dist/src/lib/integration-request-config.js.map +1 -0
  165. package/dist/src/lib/integration-selector.d.ts +42 -0
  166. package/dist/src/lib/integration-selector.js +46 -0
  167. package/dist/src/lib/integration-selector.js.map +1 -0
  168. package/dist/src/lib/local-state.d.ts +55 -0
  169. package/dist/src/lib/local-state.js +167 -0
  170. package/dist/src/lib/local-state.js.map +1 -0
  171. package/dist/src/lib/local-test-cases.d.ts +63 -0
  172. package/dist/src/lib/local-test-cases.js +135 -0
  173. package/dist/src/lib/local-test-cases.js.map +1 -0
  174. package/dist/src/lib/log-inspection.d.ts +568 -0
  175. package/dist/src/lib/log-inspection.js +639 -0
  176. package/dist/src/lib/log-inspection.js.map +1 -0
  177. package/dist/src/lib/migration-nag.d.ts +2 -2
  178. package/dist/src/lib/migration-nag.js +3 -3
  179. package/dist/src/lib/object-status-filter.d.ts +22 -0
  180. package/dist/src/lib/object-status-filter.js +45 -0
  181. package/dist/src/lib/object-status-filter.js.map +1 -0
  182. package/dist/src/lib/output.d.ts +24 -1
  183. package/dist/src/lib/output.js +42 -1
  184. package/dist/src/lib/output.js.map +1 -1
  185. package/dist/src/lib/package-manager.d.ts +140 -0
  186. package/dist/src/lib/package-manager.js +305 -0
  187. package/dist/src/lib/package-manager.js.map +1 -0
  188. package/dist/src/lib/paginate.d.ts +50 -0
  189. package/dist/src/lib/paginate.js +53 -0
  190. package/dist/src/lib/paginate.js.map +1 -1
  191. package/dist/src/lib/platform-owned.d.ts +29 -3
  192. package/dist/src/lib/platform-owned.js +42 -3
  193. package/dist/src/lib/platform-owned.js.map +1 -1
  194. package/dist/src/lib/project-config.d.ts +8 -0
  195. package/dist/src/lib/project-config.js +30 -5
  196. package/dist/src/lib/project-config.js.map +1 -1
  197. package/dist/src/lib/query-operators.d.ts +1 -1
  198. package/dist/src/lib/query-operators.js +1 -1
  199. package/dist/src/lib/record-filter.d.ts +18 -0
  200. package/dist/src/lib/record-filter.js +55 -0
  201. package/dist/src/lib/record-filter.js.map +1 -0
  202. package/dist/src/lib/resolve-init-dev-port.d.ts +56 -0
  203. package/dist/src/lib/resolve-init-dev-port.js +55 -0
  204. package/dist/src/lib/resolve-init-dev-port.js.map +1 -0
  205. package/dist/src/lib/resolve-init-server.d.ts +64 -0
  206. package/dist/src/lib/resolve-init-server.js +77 -0
  207. package/dist/src/lib/resolve-init-server.js.map +1 -0
  208. package/dist/src/lib/resolve-platform.d.ts +43 -14
  209. package/dist/src/lib/resolve-platform.js +74 -12
  210. package/dist/src/lib/resolve-platform.js.map +1 -1
  211. package/dist/src/lib/run-status.d.ts +19 -0
  212. package/dist/src/lib/run-status.generated.d.ts +39 -0
  213. package/dist/src/lib/run-status.generated.js +66 -0
  214. package/dist/src/lib/run-status.generated.js.map +1 -0
  215. package/dist/src/lib/run-status.js +19 -0
  216. package/dist/src/lib/run-status.js.map +1 -0
  217. package/dist/src/lib/server-text-normalization.d.ts +51 -0
  218. package/dist/src/lib/server-text-normalization.js +90 -0
  219. package/dist/src/lib/server-text-normalization.js.map +1 -0
  220. package/dist/src/lib/server-url.d.ts +22 -0
  221. package/dist/src/lib/server-url.js +33 -0
  222. package/dist/src/lib/server-url.js.map +1 -0
  223. package/dist/src/lib/signing-secret-status.d.ts +81 -0
  224. package/dist/src/lib/signing-secret-status.js +116 -0
  225. package/dist/src/lib/signing-secret-status.js.map +1 -0
  226. package/dist/src/lib/skill-installer.js +6 -3
  227. package/dist/src/lib/skill-installer.js.map +1 -1
  228. package/dist/src/lib/snapshots.d.ts +2 -2
  229. package/dist/src/lib/snapshots.js +2 -2
  230. package/dist/src/lib/swift-codegen/dbGenerator.d.ts +15 -2
  231. package/dist/src/lib/swift-codegen/dbGenerator.js +250 -18
  232. package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -1
  233. package/dist/src/lib/swift-codegen/generator.d.ts +11 -2
  234. package/dist/src/lib/swift-codegen/generator.js +181 -7
  235. package/dist/src/lib/swift-codegen/generator.js.map +1 -1
  236. package/dist/src/lib/swift-codegen/siblingSymbols.d.ts +7 -3
  237. package/dist/src/lib/swift-codegen/siblingSymbols.js +4 -1
  238. package/dist/src/lib/swift-codegen/siblingSymbols.js.map +1 -1
  239. package/dist/src/lib/sync-dir-selector.d.ts +21 -0
  240. package/dist/src/lib/sync-dir-selector.js +30 -0
  241. package/dist/src/lib/sync-dir-selector.js.map +1 -0
  242. package/dist/src/lib/sync-paths.d.ts +39 -0
  243. package/dist/src/lib/sync-paths.js +70 -2
  244. package/dist/src/lib/sync-paths.js.map +1 -1
  245. package/dist/src/lib/sync-resource-types.d.ts +138 -31
  246. package/dist/src/lib/sync-resource-types.js +241 -47
  247. package/dist/src/lib/sync-resource-types.js.map +1 -1
  248. package/dist/src/lib/sync-selectors.d.ts +95 -0
  249. package/dist/src/lib/sync-selectors.js +228 -0
  250. package/dist/src/lib/sync-selectors.js.map +1 -0
  251. package/dist/src/lib/template.d.ts +62 -4
  252. package/dist/src/lib/template.js +147 -39
  253. package/dist/src/lib/template.js.map +1 -1
  254. package/dist/src/lib/test-case-file-names.d.ts +40 -0
  255. package/dist/src/lib/test-case-file-names.js +91 -0
  256. package/dist/src/lib/test-case-file-names.js.map +1 -0
  257. package/dist/src/lib/test-case-keys.d.ts +29 -0
  258. package/dist/src/lib/test-case-keys.js +55 -0
  259. package/dist/src/lib/test-case-keys.js.map +1 -0
  260. package/dist/src/lib/test-case-variables.d.ts +15 -0
  261. package/dist/src/lib/test-case-variables.js +29 -0
  262. package/dist/src/lib/test-case-variables.js.map +1 -0
  263. package/dist/src/lib/toml-database-config.d.ts +4 -4
  264. package/dist/src/lib/toml-database-config.js +27 -34
  265. package/dist/src/lib/toml-database-config.js.map +1 -1
  266. package/dist/src/lib/toml-metadata-config.d.ts +9 -5
  267. package/dist/src/lib/toml-metadata-config.js +40 -45
  268. package/dist/src/lib/toml-metadata-config.js.map +1 -1
  269. package/dist/src/lib/toml-native-form.d.ts +1 -1
  270. package/dist/src/lib/toml-native-form.js +1 -1
  271. package/dist/src/lib/toml-params-validator.d.ts +1 -1
  272. package/dist/src/lib/toml-params-validator.js +1 -1
  273. package/dist/src/lib/toml-scalar-edit.d.ts +43 -0
  274. package/dist/src/lib/toml-scalar-edit.js +283 -0
  275. package/dist/src/lib/toml-scalar-edit.js.map +1 -0
  276. package/dist/src/lib/user-selector.d.ts +24 -0
  277. package/dist/src/lib/user-selector.js +33 -0
  278. package/dist/src/lib/user-selector.js.map +1 -0
  279. package/dist/src/lib/version-check.d.ts +25 -0
  280. package/dist/src/lib/version-check.js +77 -8
  281. package/dist/src/lib/version-check.js.map +1 -1
  282. package/dist/src/lib/watch.d.ts +121 -0
  283. package/dist/src/lib/watch.js +169 -0
  284. package/dist/src/lib/watch.js.map +1 -0
  285. package/dist/src/lib/web-url.d.ts +40 -0
  286. package/dist/src/lib/web-url.js +76 -0
  287. package/dist/src/lib/web-url.js.map +1 -0
  288. package/dist/src/lib/workflow-apply.d.ts +69 -45
  289. package/dist/src/lib/workflow-apply.js +99 -72
  290. package/dist/src/lib/workflow-apply.js.map +1 -1
  291. package/dist/src/lib/workflow-codegen/generator.js +4 -4
  292. package/dist/src/lib/workflow-codegen/generator.js.map +1 -1
  293. package/dist/src/lib/workflow-codegen/invokerIR.d.ts +11 -0
  294. package/dist/src/lib/workflow-codegen/invokerIR.js +5 -0
  295. package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -1
  296. package/dist/src/lib/workflow-config-apply.d.ts +70 -0
  297. package/dist/src/lib/workflow-config-apply.js +137 -0
  298. package/dist/src/lib/workflow-config-apply.js.map +1 -0
  299. package/dist/src/lib/workflow-config-sidecar.d.ts +63 -0
  300. package/dist/src/lib/workflow-config-sidecar.js +96 -0
  301. package/dist/src/lib/workflow-config-sidecar.js.map +1 -0
  302. package/dist/src/lib/workflow-defaults.d.ts +29 -0
  303. package/dist/src/lib/workflow-defaults.js +41 -0
  304. package/dist/src/lib/workflow-defaults.js.map +1 -0
  305. package/dist/src/lib/workflow-include-preserve.d.ts +76 -0
  306. package/dist/src/lib/workflow-include-preserve.js +286 -0
  307. package/dist/src/lib/workflow-include-preserve.js.map +1 -0
  308. package/dist/src/lib/workflow-payload.d.ts +31 -20
  309. package/dist/src/lib/workflow-payload.js +100 -110
  310. package/dist/src/lib/workflow-payload.js.map +1 -1
  311. package/dist/src/lib/workflow-toml-validator.d.ts +37 -16
  312. package/dist/src/lib/workflow-toml-validator.js +141 -16
  313. package/dist/src/lib/workflow-toml-validator.js.map +1 -1
  314. package/dist/src/types/index.d.ts +78 -22
  315. package/dist/src/validators.d.ts +7 -7
  316. package/dist/src/validators.js +6 -6
  317. package/package.json +11 -6
  318. package/dist/src/commands/settings.d.ts +0 -15
  319. package/dist/src/commands/settings.js +0 -102
  320. package/dist/src/commands/settings.js.map +0 -1
@@ -1,23 +1,36 @@
1
- import { readFileSync } from "fs";
2
- import { parseConfigToml } from "../lib/config-toml.js";
3
1
  import chalk from "chalk";
4
2
  import { ApiClient } from "../lib/api-client.js";
5
3
  import { resolveAppId } from "../lib/config.js";
6
- import { success, error, info, warn, keyValue, result as printResult, formatTable, formatId, formatDate, formatStatus, formatDuration, json, divider, } from "../lib/output.js";
4
+ import { success, error, info, keyValue, result as printResult, formatTable, formatId, formatDate, formatStatus, formatDuration, json, divider, } from "../lib/output.js";
5
+ import { buildIntegrationEnvelope } from "../lib/log-inspection.js";
7
6
  import { confirmPrompt } from "../lib/confirm-prompt.js";
7
+ import { parseStatusFilter } from "../lib/object-status-filter.js";
8
+ import { testCaseInputVariables } from "../lib/test-case-variables.js";
9
+ import { resolveIntegrationSelector } from "../lib/integration-selector.js";
8
10
  export function registerIntegrationsCommands(program) {
9
11
  const integrations = program
10
12
  .command("integrations")
11
- .description("Configure external HTTP API connections and manage secrets")
13
+ .description("Configure external HTTP API connections")
12
14
  .addHelpText("after", `
13
15
  Examples:
14
16
  $ primitive integrations list
15
- $ primitive integrations create --from-file weather-api.toml
17
+ $ primitive config create integration weather-api # scaffold integrations/weather-api.toml
18
+ $ primitive config push --only integration/weather-api # apply it
16
19
  $ primitive integrations test 01HXY...
20
+ $ primitive integrations archive 01HXY... # retire it (soft delete)
17
21
 
18
- Storing secrets for an integration (integration secrets are deprecated):
22
+ Storing credentials for an integration:
19
23
  $ primitive secrets set MY_API_KEY --value <value>
20
24
  # then reference it in integration config via {{secrets.MY_API_KEY}}
25
+ `);
26
+ // Server-side configuration is authored in TOML and applied with `config push`
27
+ // (issue #2645) — this group no longer carries config-setting flags.
28
+ integrations.addHelpText("after", `
29
+ Configuration lives in integrations/<key>.toml:
30
+ $ primitive config fields integration # the file's keys, types and defaults
31
+ $ primitive config create integration <key> # scaffold one locally
32
+ $ primitive config set integration/<key> <path>=<value>
33
+ $ primitive config push --only integration/<key>
21
34
  `);
22
35
  // List integrations
23
36
  integrations
@@ -25,14 +38,20 @@ Storing secrets for an integration (integration secrets are deprecated):
25
38
  .description("List integrations")
26
39
  .argument("[app-id]", "App ID (uses current app if not specified)")
27
40
  .option("--app <app-id>", "App ID")
28
- .option("--status <status>", "Filter by status: draft, active, archived")
41
+ .option("--status <status>", "Filter by status: active, inactive, archived")
29
42
  .option("--json", "Output as JSON")
30
43
  .action(async (appId, options) => {
31
44
  const resolvedAppId = resolveAppId(appId, options);
32
45
  const client = new ApiClient();
33
46
  try {
47
+ // #2803 — `draft` is gone; refuse it by name rather than returning the
48
+ // empty list a canonical filter would produce.
34
49
  const { items } = await client.listIntegrations(resolvedAppId, {
35
- status: options.status,
50
+ status: parseStatusFilter("integrations", options.status, [
51
+ "active",
52
+ "inactive",
53
+ "archived",
54
+ ]),
36
55
  });
37
56
  if (options.json) {
38
57
  json(items);
@@ -55,75 +74,112 @@ Storing secrets for an integration (integration secrets are deprecated):
55
74
  process.exit(1);
56
75
  }
57
76
  });
58
- // Create integration
77
+ // #2645 criterion 9 / #2803 — the operational verbs. They are the only
78
+ // writers of `status`, which is server-owned and no longer a TOML key.
59
79
  integrations
60
- .command("create")
61
- .description("Create a new integration")
62
- .argument("[app-id]", "App ID (uses current app if not specified)")
63
- .option("--app <app-id>", "App ID")
64
- .option("--key <key>", "Integration key (unique identifier)")
65
- .option("--name <name>", "Display name")
66
- .option("--description <desc>", "Description")
67
- .option("--base-url <url>", "Base URL for API requests")
68
- .option("--allowed-methods <methods>", "Comma-separated list of allowed HTTP methods")
69
- .option("--allowed-paths <paths>", "Comma-separated list of allowed paths")
70
- .option("--timeout-ms <ms>", "Request timeout in milliseconds")
71
- .option("--from-file <path>", "Load integration from TOML file")
80
+ .command("disable")
81
+ .description("Take integration out of service now. Operational, not configuration: `status` is server-owned and 'config push' can neither set nor clear it.")
82
+ .argument("<integration-id>", "Integration ID")
83
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
72
84
  .option("--json", "Output as JSON")
73
- .action(async (appId, options) => {
74
- const resolvedAppId = resolveAppId(appId, options);
85
+ .action(async (id, options) => {
86
+ const resolvedAppId = resolveAppId(undefined, options);
75
87
  const client = new ApiClient();
76
- let payload;
77
- if (options.fromFile) {
88
+ try {
89
+ const result = await client.setIntegrationAvailability(resolvedAppId, id, "disable");
90
+ if (options.json) {
91
+ json(result);
92
+ return;
93
+ }
94
+ success(`Integration disabled: ${result.integrationKey || id}`);
95
+ }
96
+ catch (err) {
97
+ error(err.message);
98
+ process.exit(1);
99
+ }
100
+ });
101
+ integrations
102
+ .command("enable")
103
+ .description("Put integration back in service.")
104
+ .argument("<integration-id>", "Integration ID")
105
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
106
+ .option("--json", "Output as JSON")
107
+ .action(async (id, options) => {
108
+ const resolvedAppId = resolveAppId(undefined, options);
109
+ const client = new ApiClient();
110
+ try {
111
+ const result = await client.setIntegrationAvailability(resolvedAppId, id, "enable");
112
+ if (options.json) {
113
+ json(result);
114
+ return;
115
+ }
116
+ success(`Integration enabled: ${result.integrationKey || id}`);
117
+ }
118
+ catch (err) {
119
+ error(err.message);
120
+ process.exit(1);
121
+ }
122
+ });
123
+ // #2907 — the delete flow's CLI spelling. Not a second writer of `status`:
124
+ // this is the plain `DELETE` the server has always answered by writing the
125
+ // tombstone, which until now only the admin API and the console could reach.
126
+ // Named `archive`, not `delete`, because a configuration noun gets no
127
+ // `delete` verb (docs/cli-design.md) and prune-by-push stays the only CLI
128
+ // hard-delete path.
129
+ integrations
130
+ .command("archive")
131
+ .description("Retire an integration (soft delete). Delete-lifecycle, not `disable`: the row is kept, goes on holding its key, and there is no un-archive.")
132
+ .argument("<integration-id>", "Integration ID")
133
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
134
+ .option("-y, --yes", "Skip confirmation prompt")
135
+ .option("--json", "Output as JSON")
136
+ .addHelpText("after", `
137
+ What archiving an integration costs:
138
+ The proxy refuses it, and so does every workflow step that calls it.
139
+ The archived row still holds its integrationKey, so a push cannot re-create
140
+ one under the same key.
141
+ \`primitive integrations enable\` refuses an archived integration; there is no
142
+ un-archive.
143
+
144
+ Reclaiming the key means hard-deleting the row:
145
+ $ rm integrations/<key>.toml
146
+ $ primitive config push --prune # confirmed; hard-deletes the row
147
+ # or hard-delete it in the console, then re-add the file and push.
148
+ `)
149
+ .action(async (id, options) => {
150
+ const resolvedAppId = resolveAppId(undefined, options);
151
+ if (!options.yes) {
152
+ let confirmed;
78
153
  try {
79
- const content = readFileSync(options.fromFile, "utf-8");
80
- const tomlData = parseConfigToml(content);
81
- payload = {
82
- integrationKey: tomlData.integration?.key || tomlData.key,
83
- displayName: tomlData.integration?.displayName || tomlData.displayName,
84
- description: tomlData.integration?.description || tomlData.description,
85
- requestConfig: tomlData.requestConfig || {},
86
- timeoutMs: tomlData.integration?.timeoutMs || tomlData.timeoutMs,
87
- };
154
+ confirmed = await confirmPrompt(`Archive integration ${id}? It is retired, goes on holding its key, ` +
155
+ `and there is no un-archive.`);
88
156
  }
89
157
  catch (err) {
90
- error(`Failed to read TOML file: ${err.message}`);
158
+ error(err.message);
91
159
  process.exit(1);
92
160
  }
93
- }
94
- else {
95
- if (!options.key || !options.name || !options.baseUrl) {
96
- error("Required: --key, --name, --base-url (or use --from-file)");
97
- process.exit(1);
161
+ if (!confirmed) {
162
+ info("Cancelled.");
163
+ return;
98
164
  }
99
- payload = {
100
- integrationKey: options.key,
101
- displayName: options.name,
102
- description: options.description,
103
- requestConfig: {
104
- baseUrl: options.baseUrl,
105
- allowedMethods: options.allowedMethods?.split(",").map((s) => s.trim()),
106
- allowedPaths: options.allowedPaths?.split(",").map((s) => s.trim()),
107
- },
108
- timeoutMs: options.timeoutMs ? parseInt(options.timeoutMs) : undefined,
109
- };
110
165
  }
166
+ const client = new ApiClient();
111
167
  try {
112
- const result = await client.createIntegration(resolvedAppId, payload);
168
+ // The soft delete: no `hard`, so the server writes the tombstone and
169
+ // keeps the row (and its canonical block mirror) in step.
170
+ const result = await client.deleteIntegration(resolvedAppId, id);
113
171
  if (options.json) {
114
172
  json(result);
115
173
  return;
116
174
  }
117
- success(`Integration created: ${result.displayName}`);
118
- keyValue("Integration ID", result.integrationId);
119
- keyValue("Key", result.integrationKey);
175
+ success(`Integration archived: ${id}`);
176
+ info("It is retired and still holds its key — reclaim the key by removing its file and running a confirmed `primitive config push --prune`.");
120
177
  }
121
178
  catch (err) {
122
179
  error(err.message);
123
180
  process.exit(1);
124
181
  }
125
182
  });
126
- // Get integration
127
183
  integrations
128
184
  .command("get")
129
185
  .description("Get integration details")
@@ -145,6 +201,12 @@ Storing secrets for an integration (integration secrets are deprecated):
145
201
  printResult("Description", result.description);
146
202
  printResult("Status", formatStatus(result.status));
147
203
  printResult("Timeout", result.timeoutMs ? `${result.timeoutMs}ms` : "-");
204
+ // #2631: no rule means every non-admin call is denied. Print that in
205
+ // words — a blank or "-" would read as "unrestricted" and hide an
206
+ // integration that was never given a rule.
207
+ printResult("Access Rule", typeof result.accessRule === "string" && result.accessRule !== ""
208
+ ? result.accessRule
209
+ : "(none — all non-admin calls are denied; set one to allow callers)");
148
210
  if (result.requestConfig) {
149
211
  divider();
150
212
  info("Request Config:");
@@ -167,82 +229,6 @@ Storing secrets for an integration (integration secrets are deprecated):
167
229
  process.exit(1);
168
230
  }
169
231
  });
170
- // Update integration
171
- integrations
172
- .command("update")
173
- .description("Update an integration")
174
- .argument("<integration-id>", "Integration ID")
175
- .option("--app <app-id>", "App ID (uses current app if not specified)")
176
- .option("--name <name>", "Display name")
177
- .option("--description <desc>", "Description")
178
- .option("--status <status>", "Status: draft, active, archived")
179
- .option("--timeout-ms <ms>", "Request timeout in milliseconds")
180
- .option("--json", "Output as JSON")
181
- .action(async (integrationId, options) => {
182
- const resolvedAppId = resolveAppId(undefined, options);
183
- const payload = {};
184
- if (options.name)
185
- payload.displayName = options.name;
186
- if (options.description)
187
- payload.description = options.description;
188
- if (options.status)
189
- payload.status = options.status;
190
- if (options.timeoutMs)
191
- payload.timeoutMs = parseInt(options.timeoutMs);
192
- if (Object.keys(payload).length === 0) {
193
- error("No update options specified.");
194
- process.exit(1);
195
- }
196
- const client = new ApiClient();
197
- try {
198
- const result = await client.updateIntegration(resolvedAppId, integrationId, payload);
199
- if (options.json) {
200
- json(result);
201
- return;
202
- }
203
- success("Integration updated.");
204
- }
205
- catch (err) {
206
- error(err.message);
207
- process.exit(1);
208
- }
209
- });
210
- // Delete integration
211
- integrations
212
- .command("delete")
213
- .description("Delete or archive an integration")
214
- .argument("<integration-id>", "Integration ID")
215
- .option("--app <app-id>", "App ID (uses current app if not specified)")
216
- .option("--hard", "Permanently delete instead of archive")
217
- .option("-y, --yes", "Skip confirmation prompt")
218
- .action(async (integrationId, options) => {
219
- const resolvedAppId = resolveAppId(undefined, options);
220
- if (!options.yes) {
221
- const action = options.hard ? "permanently delete" : "archive";
222
- let confirm;
223
- try {
224
- confirm = await confirmPrompt(`Are you sure you want to ${action} integration ${integrationId}?`);
225
- }
226
- catch (err) {
227
- error(err.message);
228
- process.exit(1);
229
- }
230
- if (!confirm) {
231
- info("Cancelled.");
232
- return;
233
- }
234
- }
235
- const client = new ApiClient();
236
- try {
237
- await client.deleteIntegration(resolvedAppId, integrationId, options.hard);
238
- success(`Integration ${options.hard ? "deleted" : "archived"}.`);
239
- }
240
- catch (err) {
241
- error(err.message);
242
- process.exit(1);
243
- }
244
- });
245
- // Test integration
246
232
  integrations
247
233
  .command("test")
248
234
  .description("Test an integration by making a request")
@@ -313,6 +299,9 @@ Storing secrets for an integration (integration secrets are deprecated):
313
299
  .option("--app <app-id>", "App ID (uses current app if not specified)")
314
300
  .option("--limit <n>", "Number of logs to show", "20")
315
301
  .option("--source <source>", "Filter by call source: user, admin, test, or workflow")
302
+ .option("--status <status>", "Filter by upstream HTTP status (e.g. 200, 500)")
303
+ .option("--from <iso>", "Only logs at or after this ISO timestamp")
304
+ .option("--to <iso>", "Only logs at or before this ISO timestamp")
316
305
  .option("--json", "Output as JSON")
317
306
  .action(async (integrationId, options) => {
318
307
  const resolvedAppId = resolveAppId(undefined, options);
@@ -326,9 +315,12 @@ Storing secrets for an integration (integration secrets are deprecated):
326
315
  const logs = await client.listIntegrationLogs(resolvedAppId, integrationId, {
327
316
  limit: parseInt(options.limit),
328
317
  ...(options.source && { source: options.source }),
318
+ ...(options.status && { status: options.status }),
319
+ ...(options.from && { from: options.from }),
320
+ ...(options.to && { to: options.to }),
329
321
  });
330
322
  if (options.json) {
331
- json(logs);
323
+ json(buildIntegrationEnvelope(logs));
332
324
  return;
333
325
  }
334
326
  if (!logs || logs.length === 0) {
@@ -352,115 +344,43 @@ Storing secrets for an integration (integration secrets are deprecated):
352
344
  process.exit(1);
353
345
  }
354
346
  });
355
- // Secrets subcommand
356
- //
357
- // Integration secrets are deprecated. New secrets can no longer be created
358
- // (the `add` subcommand below is hidden from help and the command manifest,
359
- // but kept registered so scripted callers still get a clear deprecation
360
- // error — see issue #1026). The modern path is App Secrets:
361
- // primitive secrets set <key> --value <value>
362
- // then reference them in integration config via {{secrets.KEY}}.
363
- const secrets = integrations
364
- .command("secrets")
365
- .description("Manage integration secrets (deprecated — use `primitive secrets set` and reference {{secrets.KEY}} in integration config instead)");
366
- // List secrets
367
- secrets
368
- .command("list")
369
- .description("List secrets for an integration")
370
- .argument("<integration-id>", "Integration ID")
371
- .option("--app <app-id>", "App ID (uses current app if not specified)")
372
- .option("--json", "Output as JSON")
373
- .action(async (integrationId, options) => {
374
- const resolvedAppId = resolveAppId(undefined, options);
375
- const client = new ApiClient();
376
- try {
377
- warn("Integration secrets are deprecated. Use App Secrets with {{secrets.KEY}} templates instead.");
378
- const secretsList = await client.listIntegrationSecrets(resolvedAppId, integrationId);
379
- if (options.json) {
380
- json(secretsList);
381
- return;
382
- }
383
- if (!secretsList || secretsList.length === 0) {
384
- info("No secrets found.");
385
- return;
386
- }
387
- console.log(formatTable(secretsList, [
388
- { header: "ID", key: "secretId", format: formatId },
389
- { header: "VERSION", key: "version" },
390
- { header: "SUMMARY", key: "secretSummary" },
391
- { header: "STATUS", key: "status", format: formatStatus },
392
- { header: "CREATED", key: "createdAt", format: formatDate },
393
- ]));
394
- }
395
- catch (err) {
396
- error(err.message);
397
- process.exit(1);
398
- }
399
- });
400
- // Add secret
401
- //
402
- // Deprecated and disabled: hidden from `--help` and the command manifest
403
- // (issue #1026) so the CLI no longer advertises a command that can never
404
- // succeed, but still registered so an explicit/scripted invocation gets the
405
- // clear deprecation error below rather than an "unknown command" error.
406
- secrets
407
- .command("add", { hidden: true })
408
- .description("Add a secret to an integration (deprecated — use `primitive secrets set`)")
409
- .argument("<integration-id>", "Integration ID")
410
- .option("--app <app-id>", "App ID (uses current app if not specified)")
411
- .option("--data <json>", "Secret data as JSON (e.g., '{\"apiKey\":\"...\"}' )")
412
- .option("--summary <text>", "Description of the secret")
413
- .option("--json", "Output as JSON")
414
- .action(async () => {
415
- error("Integration secrets are deprecated and can no longer be created.");
416
- info("Use App Secrets with {{secrets.KEY}} templates in defaultHeaders or staticQuery instead.");
417
- info("See: primitive secrets set <key> --value <value>");
418
- process.exit(1);
419
- });
420
- // Archive secret
421
- secrets
422
- .command("archive")
423
- .description("Archive a secret")
424
- .argument("<integration-id>", "Integration ID")
425
- .argument("<secret-id>", "Secret ID")
426
- .option("--app <app-id>", "App ID (uses current app if not specified)")
427
- .action(async (integrationId, secretId, options) => {
428
- const resolvedAppId = resolveAppId(undefined, options);
429
- const client = new ApiClient();
430
- try {
431
- await client.archiveIntegrationSecret(resolvedAppId, integrationId, secretId);
432
- success("Secret archived.");
433
- }
434
- catch (err) {
435
- error(err.message);
436
- process.exit(1);
437
- }
438
- });
439
347
  // ============================================
440
348
  // TESTS SUBCOMMAND
441
349
  // ============================================
442
350
  const tests = integrations
443
351
  .command("tests")
444
- .description("Manage and run integration test cases")
352
+ .description("Run and inspect integration test cases")
445
353
  .addHelpText("after", `
354
+ Test cases are authored in TOML at integrations/<key>.tests/<name>.toml and
355
+ applied with \`primitive config push\` (issue #2769); \`config fields integration\`
356
+ lists the fields. Deleting a case is removing its file and running
357
+ \`config push --prune\`.
358
+
359
+ Every verb here takes the integration's KEY or its ID (#3000). An identifier
360
+ that names no integration in the app fails; \`Total Tests: 0\` only ever means
361
+ the integration exists and has no cases.
362
+
446
363
  Examples:
447
- $ primitive integrations tests list <integration-id>
448
- $ primitive integrations tests create <integration-id> --name "Basic test" --input '{"method":"GET","path":"/get"}'
449
- $ primitive integrations tests run <integration-id> <test-case-id>
450
- $ primitive integrations tests run-all <integration-id>
451
- $ primitive integrations tests run-all <integration-id> --test-cases 01ABC,01DEF,01GHI
364
+ $ primitive integrations tests list <integration>
365
+ $ primitive integrations tests run <integration> <test-case-id>
366
+ $ primitive integrations tests run-all <integration>
367
+ $ primitive integrations tests run-all <integration> --test-cases 01ABC,01DEF,01GHI
452
368
  `);
453
369
  // List test cases
454
370
  tests
455
371
  .command("list")
456
372
  .description("List test cases for an integration")
457
- .argument("<integration-id>", "Integration ID")
373
+ .argument("<integration>", "Integration key or ID")
458
374
  .option("--app <app-id>", "App ID (uses current app if not specified)")
459
375
  .option("--json", "Output as JSON")
460
- .action(async (integrationId, options) => {
376
+ .action(async (integration, options) => {
461
377
  const resolvedAppId = resolveAppId(undefined, options);
462
378
  const client = new ApiClient();
463
379
  try {
380
+ // #3000 — resolve BEFORE listing: the test-case endpoint answers an
381
+ // unknown block with an empty page, so an unresolved identifier would
382
+ // print "No test cases found." and exit 0.
383
+ const { integrationId } = await resolveIntegrationSelector(client, resolvedAppId, integration);
464
384
  const { items } = await client.listTestCases(resolvedAppId, "integration", integrationId);
465
385
  if (options.json) {
466
386
  json(items);
@@ -482,91 +402,19 @@ Examples:
482
402
  process.exit(1);
483
403
  }
484
404
  });
485
- // Create test case
486
- tests
487
- .command("create")
488
- .description("Create a test case for an integration")
489
- .argument("<integration-id>", "Integration ID")
490
- .option("--app <app-id>", "App ID (uses current app if not specified)")
491
- .option("--name <name>", "Test case name (required)")
492
- .option("--input <json>", "Input variables as JSON (required)")
493
- .option("--pattern <regex>", "Expected output pattern (regex)")
494
- .option("--contains <json>", "Expected strings to contain (JSON array)")
495
- .option("--json-subset <json>", "Expected JSON subset to match")
496
- .option("--config <config-id>", "Config ID to use for this test")
497
- .option("--evaluator-prompt <prompt-id>", "Evaluator prompt ID")
498
- .option("--evaluator-config <config-id>", "Evaluator config ID")
499
- .option("--json", "Output as JSON")
500
- .action(async (integrationId, options) => {
501
- const resolvedAppId = resolveAppId(undefined, options);
502
- if (!options.name || !options.input) {
503
- error("Required: --name and --input");
504
- process.exit(1);
505
- }
506
- let inputVariables;
507
- try {
508
- inputVariables = JSON.parse(options.input);
509
- }
510
- catch {
511
- error("Invalid JSON in --input");
512
- process.exit(1);
513
- }
514
- let expectedOutputContains;
515
- if (options.contains) {
516
- try {
517
- expectedOutputContains = JSON.parse(options.contains);
518
- }
519
- catch {
520
- error("Invalid JSON in --contains");
521
- process.exit(1);
522
- }
523
- }
524
- let expectedJsonSubset;
525
- if (options.jsonSubset) {
526
- try {
527
- expectedJsonSubset = JSON.parse(options.jsonSubset);
528
- }
529
- catch {
530
- error("Invalid JSON in --json-subset");
531
- process.exit(1);
532
- }
533
- }
534
- const client = new ApiClient();
535
- try {
536
- const result = await client.createTestCase(resolvedAppId, "integration", integrationId, {
537
- name: options.name,
538
- inputVariables,
539
- expectedOutputPattern: options.pattern,
540
- expectedOutputContains,
541
- expectedJsonSubset,
542
- configId: options.config,
543
- evaluatorPromptId: options.evaluatorPrompt,
544
- evaluatorConfigId: options.evaluatorConfig,
545
- });
546
- if (options.json) {
547
- json(result);
548
- return;
549
- }
550
- success(`Test case created: ${result.name}`);
551
- keyValue("Test Case ID", result.testCaseId);
552
- }
553
- catch (err) {
554
- error(err.message);
555
- process.exit(1);
556
- }
557
- });
558
405
  // Get test case
559
406
  tests
560
407
  .command("get")
561
408
  .description("Get test case details")
562
- .argument("<integration-id>", "Integration ID")
409
+ .argument("<integration>", "Integration key or ID")
563
410
  .argument("<test-case-id>", "Test Case ID")
564
411
  .option("--app <app-id>", "App ID (uses current app if not specified)")
565
412
  .option("--json", "Output as JSON")
566
- .action(async (integrationId, testCaseId, options) => {
413
+ .action(async (integration, testCaseId, options) => {
567
414
  const resolvedAppId = resolveAppId(undefined, options);
568
415
  const client = new ApiClient();
569
416
  try {
417
+ const { integrationId } = await resolveIntegrationSelector(client, resolvedAppId, integration);
570
418
  const result = await client.getTestCase(resolvedAppId, "integration", integrationId, testCaseId);
571
419
  if (options.json) {
572
420
  json(result);
@@ -578,13 +426,7 @@ Examples:
578
426
  printResult("Created", formatDate(result.createdAt));
579
427
  divider();
580
428
  info("Input Variables:");
581
- try {
582
- const vars = JSON.parse(result.inputVariables || "{}");
583
- console.log(JSON.stringify(vars, null, 2));
584
- }
585
- catch {
586
- console.log(result.inputVariables || "{}");
587
- }
429
+ console.log(JSON.stringify(testCaseInputVariables(result.inputVariables), null, 2));
588
430
  if (result.expectedOutputPattern) {
589
431
  divider();
590
432
  printResult("Expected Pattern", result.expectedOutputPattern);
@@ -620,143 +462,27 @@ Examples:
620
462
  process.exit(1);
621
463
  }
622
464
  });
623
- // Update test case
624
- tests
625
- .command("update")
626
- .description("Update a test case")
627
- .argument("<integration-id>", "Integration ID")
628
- .argument("<test-case-id>", "Test Case ID")
629
- .option("--app <app-id>", "App ID (uses current app if not specified)")
630
- .option("--name <name>", "Test case name")
631
- .option("--input <json>", "Input variables as JSON")
632
- .option("--pattern <regex>", "Expected output pattern (regex)")
633
- .option("--contains <json>", "Expected strings to contain (JSON array)")
634
- .option("--json-subset <json>", "Expected JSON subset to match")
635
- .option("--config <config-id>", "Config ID to use for this test")
636
- .option("--clear-pattern", "Clear expected pattern")
637
- .option("--clear-contains", "Clear expected contains")
638
- .option("--clear-json-subset", "Clear expected JSON subset")
639
- .option("--json", "Output as JSON")
640
- .action(async (integrationId, testCaseId, options) => {
641
- const resolvedAppId = resolveAppId(undefined, options);
642
- const payload = {};
643
- if (options.name)
644
- payload.name = options.name;
645
- if (options.input) {
646
- try {
647
- payload.inputVariables = JSON.parse(options.input);
648
- }
649
- catch {
650
- error("Invalid JSON in --input");
651
- process.exit(1);
652
- }
653
- }
654
- if (options.clearPattern) {
655
- payload.expectedOutputPattern = null;
656
- }
657
- else if (options.pattern) {
658
- payload.expectedOutputPattern = options.pattern;
659
- }
660
- if (options.clearContains) {
661
- payload.expectedOutputContains = null;
662
- }
663
- else if (options.contains) {
664
- try {
665
- payload.expectedOutputContains = JSON.parse(options.contains);
666
- }
667
- catch {
668
- error("Invalid JSON in --contains");
669
- process.exit(1);
670
- }
671
- }
672
- if (options.clearJsonSubset) {
673
- payload.expectedJsonSubset = null;
674
- }
675
- else if (options.jsonSubset) {
676
- try {
677
- payload.expectedJsonSubset = JSON.parse(options.jsonSubset);
678
- }
679
- catch {
680
- error("Invalid JSON in --json-subset");
681
- process.exit(1);
682
- }
683
- }
684
- if (options.config)
685
- payload.configId = options.config;
686
- if (Object.keys(payload).length === 0) {
687
- error("No update options specified.");
688
- process.exit(1);
689
- }
690
- const client = new ApiClient();
691
- try {
692
- const result = await client.updateTestCase(resolvedAppId, "integration", integrationId, testCaseId, payload);
693
- if (options.json) {
694
- json(result);
695
- return;
696
- }
697
- success("Test case updated.");
698
- }
699
- catch (err) {
700
- error(err.message);
701
- process.exit(1);
702
- }
703
- });
704
- // Delete test case
705
- tests
706
- .command("delete")
707
- .description("Delete a test case")
708
- .argument("<integration-id>", "Integration ID")
709
- .argument("<test-case-id>", "Test Case ID")
710
- .option("--app <app-id>", "App ID (uses current app if not specified)")
711
- .option("-y, --yes", "Skip confirmation prompt")
712
- .action(async (integrationId, testCaseId, options) => {
713
- const resolvedAppId = resolveAppId(undefined, options);
714
- if (!options.yes) {
715
- let confirm;
716
- try {
717
- confirm = await confirmPrompt(`Are you sure you want to delete test case ${testCaseId}?`);
718
- }
719
- catch (err) {
720
- error(err.message);
721
- process.exit(1);
722
- }
723
- if (!confirm) {
724
- info("Cancelled.");
725
- return;
726
- }
727
- }
728
- const client = new ApiClient();
729
- try {
730
- await client.deleteTestCase(resolvedAppId, "integration", integrationId, testCaseId);
731
- success("Test case deleted.");
732
- }
733
- catch (err) {
734
- error(err.message);
735
- process.exit(1);
736
- }
737
- });
738
465
  // Run a single test case
739
466
  tests
740
467
  .command("run")
741
468
  .description("Run a single test case")
742
- .argument("<integration-id>", "Integration ID")
469
+ .argument("<integration>", "Integration key or ID")
743
470
  .argument("<test-case-id>", "Test Case ID")
744
471
  .option("--app <app-id>", "App ID (uses current app if not specified)")
745
472
  .option("--config <config-id>", "Override config ID")
746
473
  .option("--json", "Output as JSON")
747
- .action(async (integrationId, testCaseId, options) => {
474
+ .action(async (integration, testCaseId, options) => {
748
475
  const resolvedAppId = resolveAppId(undefined, options);
749
476
  const client = new ApiClient();
750
477
  try {
478
+ const { integrationId } = await resolveIntegrationSelector(client, resolvedAppId, integration);
751
479
  // Get test case to retrieve its variables
752
480
  const testCase = await client.getTestCase(resolvedAppId, "integration", integrationId, testCaseId);
753
- let variables = {};
754
- try {
755
- variables = JSON.parse(testCase.inputVariables || "{}");
756
- }
757
- catch {
758
- // Use empty object if parsing fails
759
- }
481
+ // #2968 — the case's variables ARE the request the run issues, so a
482
+ // dropped `inputVariables` here is the config's bare defaults instead
483
+ // of the case's method, path and body. The server hands them back
484
+ // parsed; older servers hand back JSON text.
485
+ const variables = testCaseInputVariables(testCase.inputVariables);
760
486
  const result = await client.executeBlockTest(resolvedAppId, "integration", integrationId, {
761
487
  variables,
762
488
  testCaseId,
@@ -813,15 +539,19 @@ Examples:
813
539
  tests
814
540
  .command("run-all")
815
541
  .description("Run all test cases for an integration, or a specific subset")
816
- .argument("<integration-id>", "Integration ID")
542
+ .argument("<integration>", "Integration key or ID")
817
543
  .option("--app <app-id>", "App ID (uses current app if not specified)")
818
544
  .option("--config <config-id>", "Override config ID for all tests")
819
545
  .option("--test-cases <ids>", "Comma-separated list of test case IDs to run (runs only these)")
820
546
  .option("--json", "Output as JSON")
821
- .action(async (integrationId, options) => {
547
+ .action(async (integration, options) => {
822
548
  const resolvedAppId = resolveAppId(undefined, options);
823
549
  const client = new ApiClient();
824
550
  try {
551
+ // #3000 — resolve BEFORE announcing the run: an unresolved identifier
552
+ // came back as `Total Tests: 0 / Failed: 0` and exit 0, which reads as
553
+ // a pass in CI.
554
+ const { integrationId } = await resolveIntegrationSelector(client, resolvedAppId, integration);
825
555
  // Parse test case IDs if provided
826
556
  let testCaseIds;
827
557
  if (options.testCases) {
@@ -879,15 +609,16 @@ Examples:
879
609
  tests
880
610
  .command("runs")
881
611
  .description("List recent test runs for an integration")
882
- .argument("<integration-id>", "Integration ID")
612
+ .argument("<integration>", "Integration key or ID")
883
613
  .option("--app <app-id>", "App ID (uses current app if not specified)")
884
614
  .option("--limit <n>", "Maximum number of runs to show", "20")
885
615
  .option("--group <group>", "Filter by comparison group")
886
616
  .option("--json", "Output as JSON")
887
- .action(async (integrationId, options) => {
617
+ .action(async (integration, options) => {
888
618
  const resolvedAppId = resolveAppId(undefined, options);
889
619
  const client = new ApiClient();
890
620
  try {
621
+ const { integrationId } = await resolveIntegrationSelector(client, resolvedAppId, integration);
891
622
  const { items } = await client.listTestRuns(resolvedAppId, "integration", integrationId, {
892
623
  limit: parseInt(options.limit),
893
624
  comparisonGroup: options.group,