primitive-admin 1.1.0-alpha.4 → 1.1.0-alpha.40

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 (216) hide show
  1. package/README.md +325 -31
  2. package/assets/skill/skills/primitive-platform/SKILL.md +281 -0
  3. package/dist/bin/primitive.d.ts +2 -0
  4. package/dist/bin/primitive.js +266 -16
  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 +123 -18
  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 +464 -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 +55 -13
  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 +177 -7
  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 +422 -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 +34 -35
  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 +178 -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 +515 -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/cron-triggers.d.ts +2 -0
  34. package/dist/src/commands/cron-triggers.js +357 -0
  35. package/dist/src/commands/cron-triggers.js.map +1 -0
  36. package/dist/src/commands/database-types.d.ts +2 -0
  37. package/dist/src/commands/database-types.js +471 -0
  38. package/dist/src/commands/database-types.js.map +1 -0
  39. package/dist/src/commands/databases.d.ts +2 -0
  40. package/dist/src/commands/databases.js +1462 -83
  41. package/dist/src/commands/databases.js.map +1 -1
  42. package/dist/src/commands/documents.d.ts +2 -0
  43. package/dist/src/commands/documents.js +518 -11
  44. package/dist/src/commands/documents.js.map +1 -1
  45. package/dist/src/commands/email-templates.d.ts +2 -0
  46. package/dist/src/commands/email-templates.js +281 -0
  47. package/dist/src/commands/email-templates.js.map +1 -0
  48. package/dist/src/commands/env.d.ts +12 -0
  49. package/dist/src/commands/env.js +260 -0
  50. package/dist/src/commands/env.js.map +1 -0
  51. package/dist/src/commands/group-type-configs.d.ts +2 -0
  52. package/dist/src/commands/group-type-configs.js +181 -0
  53. package/dist/src/commands/group-type-configs.js.map +1 -0
  54. package/dist/src/commands/groups.d.ts +2 -0
  55. package/dist/src/commands/groups.js +45 -43
  56. package/dist/src/commands/groups.js.map +1 -1
  57. package/dist/src/commands/guides.d.ts +159 -0
  58. package/dist/src/commands/guides.js +449 -35
  59. package/dist/src/commands/guides.js.map +1 -1
  60. package/dist/src/commands/init.d.ts +17 -0
  61. package/dist/src/commands/init.js +811 -201
  62. package/dist/src/commands/init.js.map +1 -1
  63. package/dist/src/commands/integrations.d.ts +2 -0
  64. package/dist/src/commands/integrations.js +533 -46
  65. package/dist/src/commands/integrations.js.map +1 -1
  66. package/dist/src/commands/llm.d.ts +2 -0
  67. package/dist/src/commands/llm.js +4 -2
  68. package/dist/src/commands/llm.js.map +1 -1
  69. package/dist/src/commands/prompts.d.ts +2 -0
  70. package/dist/src/commands/prompts.js +50 -52
  71. package/dist/src/commands/prompts.js.map +1 -1
  72. package/dist/src/commands/rule-sets.d.ts +3 -0
  73. package/dist/src/commands/rule-sets.js +388 -0
  74. package/dist/src/commands/rule-sets.js.map +1 -0
  75. package/dist/src/commands/scripts.d.ts +2 -0
  76. package/dist/src/commands/scripts.js +667 -0
  77. package/dist/src/commands/scripts.js.map +1 -0
  78. package/dist/src/commands/secrets.d.ts +2 -0
  79. package/dist/src/commands/secrets.js +108 -0
  80. package/dist/src/commands/secrets.js.map +1 -0
  81. package/dist/src/commands/skill.d.ts +2 -0
  82. package/dist/src/commands/skill.js +29 -0
  83. package/dist/src/commands/skill.js.map +1 -0
  84. package/dist/src/commands/sync.d.ts +174 -0
  85. package/dist/src/commands/sync.js +4392 -235
  86. package/dist/src/commands/sync.js.map +1 -1
  87. package/dist/src/commands/tokens.d.ts +2 -0
  88. package/dist/src/commands/tokens.js +122 -19
  89. package/dist/src/commands/tokens.js.map +1 -1
  90. package/dist/src/commands/users.d.ts +2 -0
  91. package/dist/src/commands/users.js +440 -22
  92. package/dist/src/commands/users.js.map +1 -1
  93. package/dist/src/commands/waitlist.d.ts +2 -0
  94. package/dist/src/commands/waitlist.js +10 -10
  95. package/dist/src/commands/waitlist.js.map +1 -1
  96. package/dist/src/commands/webhooks.d.ts +2 -0
  97. package/dist/src/commands/webhooks.js +391 -0
  98. package/dist/src/commands/webhooks.js.map +1 -0
  99. package/dist/src/commands/workflows.d.ts +49 -0
  100. package/dist/src/commands/workflows.js +1009 -123
  101. package/dist/src/commands/workflows.js.map +1 -1
  102. package/dist/src/lib/api-client.d.ts +1272 -0
  103. package/dist/src/lib/api-client.js +1115 -83
  104. package/dist/src/lib/api-client.js.map +1 -1
  105. package/dist/src/lib/auth-flow.d.ts +8 -0
  106. package/dist/src/lib/block-layout.d.ts +160 -0
  107. package/dist/src/lib/block-layout.js +451 -0
  108. package/dist/src/lib/block-layout.js.map +1 -0
  109. package/dist/src/lib/cli-manifest.d.ts +60 -0
  110. package/dist/src/lib/cli-manifest.js +70 -0
  111. package/dist/src/lib/cli-manifest.js.map +1 -0
  112. package/dist/src/lib/config.d.ts +53 -0
  113. package/dist/src/lib/config.js +92 -53
  114. package/dist/src/lib/config.js.map +1 -1
  115. package/dist/src/lib/confirm-prompt.d.ts +83 -0
  116. package/dist/src/lib/confirm-prompt.js +110 -0
  117. package/dist/src/lib/confirm-prompt.js.map +1 -0
  118. package/dist/src/lib/constants.d.ts +2 -0
  119. package/dist/src/lib/constants.js +3 -0
  120. package/dist/src/lib/constants.js.map +1 -0
  121. package/dist/src/lib/crash-handlers.d.ts +20 -0
  122. package/dist/src/lib/crash-handlers.js +49 -0
  123. package/dist/src/lib/crash-handlers.js.map +1 -0
  124. package/dist/src/lib/credentials-store.d.ts +79 -0
  125. package/dist/src/lib/credentials-store.js +307 -0
  126. package/dist/src/lib/credentials-store.js.map +1 -0
  127. package/dist/src/lib/csv.d.ts +48 -0
  128. package/dist/src/lib/csv.js +177 -0
  129. package/dist/src/lib/csv.js.map +1 -0
  130. package/dist/src/lib/db-codegen/dbFingerprint.d.ts +10 -0
  131. package/dist/src/lib/db-codegen/dbFingerprint.js +17 -0
  132. package/dist/src/lib/db-codegen/dbFingerprint.js.map +1 -0
  133. package/dist/src/lib/db-codegen/dbGenerator.d.ts +111 -0
  134. package/dist/src/lib/db-codegen/dbGenerator.js +371 -0
  135. package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -0
  136. package/dist/src/lib/db-codegen/dbNaming.d.ts +45 -0
  137. package/dist/src/lib/db-codegen/dbNaming.js +104 -0
  138. package/dist/src/lib/db-codegen/dbNaming.js.map +1 -0
  139. package/dist/src/lib/db-codegen/dbTemplates.d.ts +97 -0
  140. package/dist/src/lib/db-codegen/dbTemplates.js +159 -0
  141. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -0
  142. package/dist/src/lib/db-codegen/dbTsTypes.d.ts +78 -0
  143. package/dist/src/lib/db-codegen/dbTsTypes.js +112 -0
  144. package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -0
  145. package/dist/src/lib/env-resolver.d.ts +62 -0
  146. package/dist/src/lib/env-resolver.js +121 -0
  147. package/dist/src/lib/env-resolver.js.map +1 -0
  148. package/dist/src/lib/fetch.d.ts +5 -0
  149. package/dist/src/lib/generated-allowlist.d.ts +28 -0
  150. package/dist/src/lib/generated-allowlist.js +213 -0
  151. package/dist/src/lib/generated-allowlist.js.map +1 -0
  152. package/dist/src/lib/init-config.d.ts +59 -0
  153. package/dist/src/lib/init-config.js +113 -0
  154. package/dist/src/lib/init-config.js.map +1 -0
  155. package/dist/src/lib/migration-nag.d.ts +49 -0
  156. package/dist/src/lib/migration-nag.js +163 -0
  157. package/dist/src/lib/migration-nag.js.map +1 -0
  158. package/dist/src/lib/output.d.ts +86 -0
  159. package/dist/src/lib/output.js +150 -8
  160. package/dist/src/lib/output.js.map +1 -1
  161. package/dist/src/lib/paginate.d.ts +33 -0
  162. package/dist/src/lib/paginate.js +42 -0
  163. package/dist/src/lib/paginate.js.map +1 -0
  164. package/dist/src/lib/project-config.d.ts +97 -0
  165. package/dist/src/lib/project-config.js +209 -0
  166. package/dist/src/lib/project-config.js.map +1 -0
  167. package/dist/src/lib/query-operators.d.ts +43 -0
  168. package/dist/src/lib/query-operators.js +80 -0
  169. package/dist/src/lib/query-operators.js.map +1 -0
  170. package/dist/src/lib/refresh-admin-credentials.d.ts +65 -0
  171. package/dist/src/lib/refresh-admin-credentials.js +103 -0
  172. package/dist/src/lib/refresh-admin-credentials.js.map +1 -0
  173. package/dist/src/lib/resolve-platform.d.ts +45 -0
  174. package/dist/src/lib/resolve-platform.js +43 -0
  175. package/dist/src/lib/resolve-platform.js.map +1 -0
  176. package/dist/src/lib/skill-installer.d.ts +23 -0
  177. package/dist/src/lib/skill-installer.js +135 -0
  178. package/dist/src/lib/skill-installer.js.map +1 -0
  179. package/dist/src/lib/snapshots.d.ts +99 -0
  180. package/dist/src/lib/snapshots.js +357 -0
  181. package/dist/src/lib/snapshots.js.map +1 -0
  182. package/dist/src/lib/sync-paths.d.ts +72 -0
  183. package/dist/src/lib/sync-paths.js +130 -0
  184. package/dist/src/lib/sync-paths.js.map +1 -0
  185. package/dist/src/lib/template.d.ts +97 -0
  186. package/dist/src/lib/template.js +336 -62
  187. package/dist/src/lib/template.js.map +1 -1
  188. package/dist/src/lib/token-inject.d.ts +56 -0
  189. package/dist/src/lib/token-inject.js +204 -0
  190. package/dist/src/lib/token-inject.js.map +1 -0
  191. package/dist/src/lib/toml-database-config.d.ts +152 -0
  192. package/dist/src/lib/toml-database-config.js +600 -0
  193. package/dist/src/lib/toml-database-config.js.map +1 -0
  194. package/dist/src/lib/toml-metadata-config.d.ts +108 -0
  195. package/dist/src/lib/toml-metadata-config.js +371 -0
  196. package/dist/src/lib/toml-metadata-config.js.map +1 -0
  197. package/dist/src/lib/toml-params-validator.d.ts +129 -0
  198. package/dist/src/lib/toml-params-validator.js +298 -0
  199. package/dist/src/lib/toml-params-validator.js.map +1 -0
  200. package/dist/src/lib/version-check.d.ts +10 -0
  201. package/dist/src/lib/version-check.js +172 -0
  202. package/dist/src/lib/version-check.js.map +1 -0
  203. package/dist/src/lib/workflow-apply.d.ts +66 -0
  204. package/dist/src/lib/workflow-apply.js +117 -0
  205. package/dist/src/lib/workflow-apply.js.map +1 -0
  206. package/dist/src/lib/workflow-fragments.d.ts +41 -0
  207. package/dist/src/lib/workflow-fragments.js +121 -0
  208. package/dist/src/lib/workflow-fragments.js.map +1 -0
  209. package/dist/src/lib/workflow-toml-validator.d.ts +95 -0
  210. package/dist/src/lib/workflow-toml-validator.js +323 -0
  211. package/dist/src/lib/workflow-toml-validator.js.map +1 -0
  212. package/dist/src/types/index.d.ts +520 -0
  213. package/dist/src/validators.d.ts +64 -0
  214. package/dist/src/validators.js +63 -0
  215. package/dist/src/validators.js.map +1 -0
  216. package/package.json +18 -4
@@ -1,8 +1,10 @@
1
1
  import { readFileSync } from "fs";
2
2
  import * as TOML from "@iarna/toml";
3
+ import chalk from "chalk";
3
4
  import { ApiClient } from "../lib/api-client.js";
4
5
  import { resolveAppId } from "../lib/config.js";
5
- import { success, error, info, keyValue, formatTable, formatId, formatDate, formatStatus, formatDuration, json, divider, } from "../lib/output.js";
6
+ import { success, error, info, warn, keyValue, result as printResult, formatTable, formatId, formatDate, formatStatus, formatDuration, json, divider, } from "../lib/output.js";
7
+ import { confirmPrompt } from "../lib/confirm-prompt.js";
6
8
  export function registerIntegrationsCommands(program) {
7
9
  const integrations = program
8
10
  .command("integrations")
@@ -12,7 +14,10 @@ Examples:
12
14
  $ primitive integrations list
13
15
  $ primitive integrations create --from-file weather-api.toml
14
16
  $ primitive integrations test 01HXY...
15
- $ primitive integrations secrets add 01HXY... --data '{"apiKey":"..."}'
17
+
18
+ Storing secrets for an integration (integration secrets are deprecated):
19
+ $ primitive secrets set MY_API_KEY --value <value>
20
+ # then reference it in integration config via {{secrets.MY_API_KEY}}
16
21
  `);
17
22
  // List integrations
18
23
  integrations
@@ -134,24 +139,24 @@ Examples:
134
139
  json(result);
135
140
  return;
136
141
  }
137
- keyValue("Integration ID", result.integrationId);
138
- keyValue("Key", result.integrationKey);
139
- keyValue("Name", result.displayName);
140
- keyValue("Description", result.description);
141
- keyValue("Status", formatStatus(result.status));
142
- keyValue("Timeout", result.timeoutMs ? `${result.timeoutMs}ms` : "-");
142
+ printResult("Integration ID", result.integrationId);
143
+ printResult("Key", result.integrationKey);
144
+ printResult("Name", result.displayName);
145
+ printResult("Description", result.description);
146
+ printResult("Status", formatStatus(result.status));
147
+ printResult("Timeout", result.timeoutMs ? `${result.timeoutMs}ms` : "-");
143
148
  if (result.requestConfig) {
144
149
  divider();
145
150
  info("Request Config:");
146
- keyValue(" Base URL", result.requestConfig.baseUrl);
147
- keyValue(" Allowed Methods", result.requestConfig.allowedMethods?.join(", ") || "all");
148
- keyValue(" Allowed Paths", result.requestConfig.allowedPaths?.join(", ") || "all");
149
- keyValue(" Response Passthrough", result.requestConfig.responsePassthrough);
151
+ printResult(" Base URL", result.requestConfig.baseUrl);
152
+ printResult(" Allowed Methods", result.requestConfig.allowedMethods?.join(", ") || "all");
153
+ printResult(" Allowed Paths", result.requestConfig.allowedPaths?.join(", ") || "all");
154
+ printResult(" Response Passthrough", result.requestConfig.responsePassthrough);
150
155
  if (result.requestConfig.bodyMode) {
151
- keyValue(" Body Mode", result.requestConfig.bodyMode);
156
+ printResult(" Body Mode", result.requestConfig.bodyMode);
152
157
  }
153
158
  if (result.requestConfig.multipartFieldMapping?.length) {
154
- keyValue(" Multipart Fields", result.requestConfig.multipartFieldMapping
159
+ printResult(" Multipart Fields", result.requestConfig.multipartFieldMapping
155
160
  .map((f) => `${f.fieldName} (${f.type}${f.attachmentIndex != null ? `, attachment[${f.attachmentIndex}]` : ""})`)
156
161
  .join(", "));
157
162
  }
@@ -214,15 +219,14 @@ Examples:
214
219
  const resolvedAppId = resolveAppId(undefined, options);
215
220
  if (!options.yes) {
216
221
  const action = options.hard ? "permanently delete" : "archive";
217
- const inquirer = await import("inquirer");
218
- const { confirm } = await inquirer.default.prompt([
219
- {
220
- type: "confirm",
221
- name: "confirm",
222
- message: `Are you sure you want to ${action} integration ${integrationId}?`,
223
- default: false,
224
- },
225
- ]);
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
+ }
226
230
  if (!confirm) {
227
231
  info("Cancelled.");
228
232
  return;
@@ -304,17 +308,24 @@ Examples:
304
308
  // List integration logs
305
309
  integrations
306
310
  .command("logs")
307
- .description("View integration request logs")
311
+ .description("View integration request logs (includes workflow-initiated calls; filter with --source workflow|user|admin|test)")
308
312
  .argument("<integration-id>", "Integration ID")
309
313
  .option("--app <app-id>", "App ID (uses current app if not specified)")
310
314
  .option("--limit <n>", "Number of logs to show", "20")
315
+ .option("--source <source>", "Filter by call source: user, admin, test, or workflow")
311
316
  .option("--json", "Output as JSON")
312
317
  .action(async (integrationId, options) => {
313
318
  const resolvedAppId = resolveAppId(undefined, options);
314
319
  const client = new ApiClient();
315
320
  try {
321
+ const validSources = new Set(["user", "admin", "test", "workflow"]);
322
+ if (options.source && !validSources.has(options.source)) {
323
+ error(`Invalid --source value '${options.source}'. Must be one of: user, admin, test, workflow.`);
324
+ process.exit(1);
325
+ }
316
326
  const logs = await client.listIntegrationLogs(resolvedAppId, integrationId, {
317
327
  limit: parseInt(options.limit),
328
+ ...(options.source && { source: options.source }),
318
329
  });
319
330
  if (options.json) {
320
331
  json(logs);
@@ -326,10 +337,13 @@ Examples:
326
337
  }
327
338
  console.log(formatTable(logs, [
328
339
  { header: "TIME", key: "timestamp", format: formatDate },
340
+ { header: "SOURCE", key: "source", format: (v) => v || "user" },
329
341
  { header: "METHOD", key: "method" },
330
342
  { header: "PATH", key: "path" },
331
343
  { header: "STATUS", key: "status" },
332
344
  { header: "DURATION", key: "durationMs", format: formatDuration },
345
+ { header: "WORKFLOW", key: "workflowKey", format: (v) => v || "" },
346
+ { header: "RUN", key: "runId", format: (v) => (v ? formatId(v) : "") },
333
347
  { header: "TRACE", key: "traceId", format: formatId },
334
348
  ]));
335
349
  }
@@ -339,7 +353,16 @@ Examples:
339
353
  }
340
354
  });
341
355
  // Secrets subcommand
342
- const secrets = integrations.command("secrets").description("Manage integration secrets");
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)");
343
366
  // List secrets
344
367
  secrets
345
368
  .command("list")
@@ -351,6 +374,7 @@ Examples:
351
374
  const resolvedAppId = resolveAppId(undefined, options);
352
375
  const client = new ApiClient();
353
376
  try {
377
+ warn("Integration secrets are deprecated. Use App Secrets with {{secrets.KEY}} templates instead.");
354
378
  const secretsList = await client.listIntegrationSecrets(resolvedAppId, integrationId);
355
379
  if (options.json) {
356
380
  json(secretsList);
@@ -374,60 +398,523 @@ Examples:
374
398
  }
375
399
  });
376
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.
377
406
  secrets
378
- .command("add")
379
- .description("Add a secret to an integration")
407
+ .command("add", { hidden: true })
408
+ .description("Add a secret to an integration (deprecated — use `primitive secrets set`)")
380
409
  .argument("<integration-id>", "Integration ID")
381
410
  .option("--app <app-id>", "App ID (uses current app if not specified)")
382
411
  .option("--data <json>", "Secret data as JSON (e.g., '{\"apiKey\":\"...\"}' )")
383
412
  .option("--summary <text>", "Description of the secret")
384
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
+ // ============================================
440
+ // TESTS SUBCOMMAND
441
+ // ============================================
442
+ const tests = integrations
443
+ .command("tests")
444
+ .description("Manage and run integration test cases")
445
+ .addHelpText("after", `
446
+ 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
452
+ `);
453
+ // List test cases
454
+ tests
455
+ .command("list")
456
+ .description("List test cases for an integration")
457
+ .argument("<integration-id>", "Integration ID")
458
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
459
+ .option("--json", "Output as JSON")
460
+ .action(async (integrationId, options) => {
461
+ const resolvedAppId = resolveAppId(undefined, options);
462
+ const client = new ApiClient();
463
+ try {
464
+ const { items } = await client.listTestCases(resolvedAppId, "integration", integrationId);
465
+ if (options.json) {
466
+ json(items);
467
+ return;
468
+ }
469
+ if (!items || items.length === 0) {
470
+ info("No test cases found.");
471
+ return;
472
+ }
473
+ console.log(formatTable(items, [
474
+ { header: "ID", key: "testCaseId", format: formatId },
475
+ { header: "NAME", key: "name" },
476
+ { header: "CONFIG", key: "configId", format: (v) => v ? formatId(v) : "-" },
477
+ { header: "CREATED", key: "createdAt", format: formatDate },
478
+ ]));
479
+ }
480
+ catch (err) {
481
+ error(err.message);
482
+ process.exit(1);
483
+ }
484
+ });
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")
385
500
  .action(async (integrationId, options) => {
386
501
  const resolvedAppId = resolveAppId(undefined, options);
387
- if (!options.data) {
388
- error("--data is required (JSON object with secret values)");
502
+ if (!options.name || !options.input) {
503
+ error("Required: --name and --input");
389
504
  process.exit(1);
390
505
  }
391
- let secretData;
506
+ let inputVariables;
392
507
  try {
393
- secretData = JSON.parse(options.data);
508
+ inputVariables = JSON.parse(options.input);
394
509
  }
395
510
  catch {
396
- error("Invalid JSON in --data");
511
+ error("Invalid JSON in --input");
397
512
  process.exit(1);
398
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
+ }
399
534
  const client = new ApiClient();
400
535
  try {
401
- const result = await client.addIntegrationSecret(resolvedAppId, integrationId, {
402
- secretData,
403
- secretSummary: options.summary,
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,
404
545
  });
405
546
  if (options.json) {
406
547
  json(result);
407
548
  return;
408
549
  }
409
- success("Secret added.");
410
- keyValue("Secret ID", result?.secretId);
411
- keyValue("Version", result?.version);
550
+ success(`Test case created: ${result.name}`);
551
+ keyValue("Test Case ID", result.testCaseId);
412
552
  }
413
553
  catch (err) {
414
554
  error(err.message);
415
555
  process.exit(1);
416
556
  }
417
557
  });
418
- // Archive secret
419
- secrets
420
- .command("archive")
421
- .description("Archive a secret")
558
+ // Get test case
559
+ tests
560
+ .command("get")
561
+ .description("Get test case details")
422
562
  .argument("<integration-id>", "Integration ID")
423
- .argument("<secret-id>", "Secret ID")
563
+ .argument("<test-case-id>", "Test Case ID")
424
564
  .option("--app <app-id>", "App ID (uses current app if not specified)")
425
- .action(async (integrationId, secretId, options) => {
565
+ .option("--json", "Output as JSON")
566
+ .action(async (integrationId, testCaseId, options) => {
426
567
  const resolvedAppId = resolveAppId(undefined, options);
427
568
  const client = new ApiClient();
428
569
  try {
429
- await client.archiveIntegrationSecret(resolvedAppId, integrationId, secretId);
430
- success("Secret archived.");
570
+ const result = await client.getTestCase(resolvedAppId, "integration", integrationId, testCaseId);
571
+ if (options.json) {
572
+ json(result);
573
+ return;
574
+ }
575
+ printResult("Test Case ID", result.testCaseId);
576
+ printResult("Name", result.name);
577
+ printResult("Config ID", result.configId || "-");
578
+ printResult("Created", formatDate(result.createdAt));
579
+ divider();
580
+ 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
+ }
588
+ if (result.expectedOutputPattern) {
589
+ divider();
590
+ printResult("Expected Pattern", result.expectedOutputPattern);
591
+ }
592
+ if (result.expectedOutputContains) {
593
+ divider();
594
+ info("Expected Contains:");
595
+ try {
596
+ console.log(JSON.stringify(JSON.parse(result.expectedOutputContains), null, 2));
597
+ }
598
+ catch {
599
+ console.log(result.expectedOutputContains);
600
+ }
601
+ }
602
+ if (result.expectedJsonSubset) {
603
+ divider();
604
+ info("Expected JSON Subset:");
605
+ try {
606
+ console.log(JSON.stringify(JSON.parse(result.expectedJsonSubset), null, 2));
607
+ }
608
+ catch {
609
+ console.log(result.expectedJsonSubset);
610
+ }
611
+ }
612
+ if (result.evaluatorPromptId) {
613
+ divider();
614
+ printResult("Evaluator Prompt", result.evaluatorPromptId);
615
+ printResult("Evaluator Config", result.evaluatorConfigId || "-");
616
+ }
617
+ }
618
+ catch (err) {
619
+ error(err.message);
620
+ process.exit(1);
621
+ }
622
+ });
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
+ // Run a single test case
739
+ tests
740
+ .command("run")
741
+ .description("Run a single test case")
742
+ .argument("<integration-id>", "Integration ID")
743
+ .argument("<test-case-id>", "Test Case ID")
744
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
745
+ .option("--config <config-id>", "Override config ID")
746
+ .option("--json", "Output as JSON")
747
+ .action(async (integrationId, testCaseId, options) => {
748
+ const resolvedAppId = resolveAppId(undefined, options);
749
+ const client = new ApiClient();
750
+ try {
751
+ // Get test case to retrieve its variables
752
+ 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
+ }
760
+ const result = await client.executeBlockTest(resolvedAppId, "integration", integrationId, {
761
+ variables,
762
+ testCaseId,
763
+ configId: options.config || testCase.configId,
764
+ });
765
+ if (options.json) {
766
+ json(result);
767
+ return;
768
+ }
769
+ const passed = result.verification?.passed;
770
+ if (passed) {
771
+ success("Test PASSED");
772
+ }
773
+ else {
774
+ error("Test FAILED");
775
+ }
776
+ keyValue("Duration", formatDuration(result.metrics?.durationMs));
777
+ if (result.verification) {
778
+ divider();
779
+ info("Verification:");
780
+ const v = result.verification;
781
+ keyValue(" Passed", v.passed ? chalk.green("Yes") : chalk.red("No"));
782
+ keyValue(" Checks", `${v.summary?.passed || 0}/${v.summary?.total || 0} passed`);
783
+ if (v.checks && v.checks.length > 0) {
784
+ console.log();
785
+ for (const check of v.checks) {
786
+ const icon = check.passed ? chalk.green("\u2713") : chalk.red("\u2717");
787
+ console.log(` ${icon} ${check.name}`);
788
+ if (!check.passed && check.message) {
789
+ console.log(` ${chalk.dim(check.message)}`);
790
+ }
791
+ }
792
+ }
793
+ }
794
+ if (result.output) {
795
+ divider();
796
+ info("Output:");
797
+ console.log(typeof result.output === "string" ? result.output : JSON.stringify(result.output, null, 2));
798
+ }
799
+ if (result.error) {
800
+ divider();
801
+ error(`Error: ${result.error}`);
802
+ }
803
+ if (!passed) {
804
+ process.exit(1);
805
+ }
806
+ }
807
+ catch (err) {
808
+ error(err.message);
809
+ process.exit(1);
810
+ }
811
+ });
812
+ // Run all test cases (or a specific group)
813
+ tests
814
+ .command("run-all")
815
+ .description("Run all test cases for an integration, or a specific subset")
816
+ .argument("<integration-id>", "Integration ID")
817
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
818
+ .option("--config <config-id>", "Override config ID for all tests")
819
+ .option("--test-cases <ids>", "Comma-separated list of test case IDs to run (runs only these)")
820
+ .option("--json", "Output as JSON")
821
+ .action(async (integrationId, options) => {
822
+ const resolvedAppId = resolveAppId(undefined, options);
823
+ const client = new ApiClient();
824
+ try {
825
+ // Parse test case IDs if provided
826
+ let testCaseIds;
827
+ if (options.testCases) {
828
+ testCaseIds = options.testCases.split(",").map((id) => id.trim()).filter(Boolean);
829
+ if (testCaseIds.length === 0) {
830
+ error("No valid test case IDs provided in --test-cases");
831
+ process.exit(1);
832
+ }
833
+ }
834
+ if (!options.json) {
835
+ if (testCaseIds) {
836
+ info(`Running ${testCaseIds.length} selected test case(s)...`);
837
+ }
838
+ else {
839
+ info("Running all test cases...");
840
+ }
841
+ }
842
+ const result = await client.runAllTestCases(resolvedAppId, "integration", integrationId, {
843
+ overrideConfigId: options.config,
844
+ testCaseIds,
845
+ });
846
+ if (options.json) {
847
+ json(result);
848
+ return;
849
+ }
850
+ const { summary, runs, comparisonGroup } = result;
851
+ divider();
852
+ keyValue("Comparison Group", comparisonGroup);
853
+ keyValue("Total Tests", summary.total);
854
+ keyValue("Passed", chalk.green(summary.passed));
855
+ keyValue("Failed", summary.failed > 0 ? chalk.red(summary.failed) : "0");
856
+ divider();
857
+ if (runs && runs.length > 0) {
858
+ for (const r of runs) {
859
+ const passed = r.verification?.passed;
860
+ const icon = passed ? chalk.green("\u2713") : chalk.red("\u2717");
861
+ const status = passed ? "PASSED" : "FAILED";
862
+ console.log(`${icon} ${r.testCaseName} - ${status}`);
863
+ if (!passed && r.verification?.details?.reason) {
864
+ console.log(` ${chalk.dim(r.verification.details.reason)}`);
865
+ }
866
+ }
867
+ }
868
+ // Exit with error code if any tests failed
869
+ if (summary.failed > 0) {
870
+ process.exit(1);
871
+ }
872
+ }
873
+ catch (err) {
874
+ error(err.message);
875
+ process.exit(1);
876
+ }
877
+ });
878
+ // List test runs
879
+ tests
880
+ .command("runs")
881
+ .description("List recent test runs for an integration")
882
+ .argument("<integration-id>", "Integration ID")
883
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
884
+ .option("--limit <n>", "Maximum number of runs to show", "20")
885
+ .option("--group <group>", "Filter by comparison group")
886
+ .option("--json", "Output as JSON")
887
+ .action(async (integrationId, options) => {
888
+ const resolvedAppId = resolveAppId(undefined, options);
889
+ const client = new ApiClient();
890
+ try {
891
+ const { items } = await client.listTestRuns(resolvedAppId, "integration", integrationId, {
892
+ limit: parseInt(options.limit),
893
+ comparisonGroup: options.group,
894
+ });
895
+ if (options.json) {
896
+ json(items);
897
+ return;
898
+ }
899
+ if (!items || items.length === 0) {
900
+ info("No test runs found.");
901
+ return;
902
+ }
903
+ console.log(formatTable(items, [
904
+ { header: "RUN ID", key: "runId", format: formatId },
905
+ { header: "TEST CASE ID", key: "testCaseId", format: (v) => v ? formatId(v) : "-" },
906
+ {
907
+ header: "PASSED",
908
+ key: "verificationPassed",
909
+ format: (v) => v === true
910
+ ? chalk.green("Yes")
911
+ : v === false
912
+ ? chalk.red("No")
913
+ : "-",
914
+ },
915
+ { header: "DURATION", key: "durationMs", format: formatDuration },
916
+ { header: "STARTED", key: "startedAt", format: formatDate },
917
+ ]));
431
918
  }
432
919
  catch (err) {
433
920
  error(err.message);