primitive-admin 1.0.61 → 1.0.62
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.
- package/README.md +125 -69
- package/assets/skill/skills/primitive-platform/SKILL.md +336 -37
- package/dist/bin/primitive.js +25 -14
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/analytics.js +36 -16
- package/dist/src/commands/analytics.js.map +1 -1
- package/dist/src/commands/apps.js +49 -188
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/blob-buckets.js +13 -153
- package/dist/src/commands/blob-buckets.js.map +1 -1
- package/dist/src/commands/collection-type-configs.js +14 -100
- package/dist/src/commands/collection-type-configs.js.map +1 -1
- package/dist/src/commands/collections.js +47 -8
- package/dist/src/commands/collections.js.map +1 -1
- package/dist/src/commands/config.d.ts +46 -0
- package/dist/src/commands/config.js +479 -0
- package/dist/src/commands/config.js.map +1 -0
- package/dist/src/commands/connections.js +12 -7
- package/dist/src/commands/connections.js.map +1 -1
- package/dist/src/commands/cron-triggers.js +84 -176
- package/dist/src/commands/cron-triggers.js.map +1 -1
- package/dist/src/commands/database-type-configs.d.ts +2 -0
- package/dist/src/commands/database-type-configs.js +171 -0
- package/dist/src/commands/database-type-configs.js.map +1 -0
- package/dist/src/commands/database-types.js +4 -4
- package/dist/src/commands/database-types.js.map +1 -1
- package/dist/src/commands/databases.d.ts +63 -0
- package/dist/src/commands/databases.js +649 -87
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.js +869 -12
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/email-templates.js +18 -125
- package/dist/src/commands/email-templates.js.map +1 -1
- package/dist/src/commands/env.d.ts +12 -1
- package/dist/src/commands/env.js +98 -25
- package/dist/src/commands/env.js.map +1 -1
- package/dist/src/commands/feature-flags.d.ts +14 -0
- package/dist/src/commands/feature-flags.js +116 -0
- package/dist/src/commands/feature-flags.js.map +1 -0
- package/dist/src/commands/group-type-configs.js +14 -109
- package/dist/src/commands/group-type-configs.js.map +1 -1
- package/dist/src/commands/init.d.ts +9 -1
- package/dist/src/commands/init.js +1056 -277
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.js +136 -421
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/metadata-category-configs.d.ts +12 -0
- package/dist/src/commands/metadata-category-configs.js +112 -0
- package/dist/src/commands/metadata-category-configs.js.map +1 -0
- package/dist/src/commands/metadata.js +33 -141
- package/dist/src/commands/metadata.js.map +1 -1
- package/dist/src/commands/prompts.js +206 -563
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/rule-sets.d.ts +1 -1
- package/dist/src/commands/rule-sets.js +16 -132
- package/dist/src/commands/rule-sets.js.map +1 -1
- package/dist/src/commands/scripts.js +67 -234
- package/dist/src/commands/scripts.js.map +1 -1
- package/dist/src/commands/sessions.js +17 -5
- package/dist/src/commands/sessions.js.map +1 -1
- package/dist/src/commands/sync-app-settings.d.ts +113 -70
- package/dist/src/commands/sync-app-settings.js +193 -242
- package/dist/src/commands/sync-app-settings.js.map +1 -1
- package/dist/src/commands/sync.d.ts +1932 -49
- package/dist/src/commands/sync.js +9521 -1769
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/tokens.js +8 -2
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/users.js +92 -1
- package/dist/src/commands/users.js.map +1 -1
- package/dist/src/commands/vars.js +44 -58
- package/dist/src/commands/vars.js.map +1 -1
- package/dist/src/commands/webhooks.js +318 -156
- package/dist/src/commands/webhooks.js.map +1 -1
- package/dist/src/commands/workflows.d.ts +49 -12
- package/dist/src/commands/workflows.js +652 -1086
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/access-rule-display.d.ts +21 -0
- package/dist/src/lib/access-rule-display.js +34 -0
- package/dist/src/lib/access-rule-display.js.map +1 -0
- package/dist/src/lib/api-client.d.ts +541 -61
- package/dist/src/lib/api-client.js +559 -102
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/app-settings-descriptor.d.ts +180 -25
- package/dist/src/lib/app-settings-descriptor.js +403 -82
- package/dist/src/lib/app-settings-descriptor.js.map +1 -1
- package/dist/src/lib/canonical-json.d.ts +12 -0
- package/dist/src/lib/canonical-json.js +35 -0
- package/dist/src/lib/canonical-json.js.map +1 -0
- package/dist/src/lib/cli-manifest.d.ts +8 -0
- package/dist/src/lib/cli-manifest.js +1 -0
- package/dist/src/lib/cli-manifest.js.map +1 -1
- package/dist/src/lib/codegen-shared/generatedFiles.d.ts +6 -2
- package/dist/src/lib/codegen-shared/generatedFiles.js +9 -2
- package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -1
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +6 -6
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +8 -8
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -1
- package/dist/src/lib/config-object-descriptor.d.ts +127 -0
- package/dist/src/lib/config-object-descriptor.js +658 -0
- package/dist/src/lib/config-object-descriptor.js.map +1 -0
- package/dist/src/lib/config-payload.d.ts +85 -0
- package/dist/src/lib/config-payload.js +116 -0
- package/dist/src/lib/config-payload.js.map +1 -0
- package/dist/src/lib/config-surface.d.ts +130 -0
- package/dist/src/lib/config-surface.js +300 -0
- package/dist/src/lib/config-surface.js.map +1 -0
- package/dist/src/lib/credentials-store.d.ts +10 -0
- package/dist/src/lib/credentials-store.js +23 -0
- package/dist/src/lib/credentials-store.js.map +1 -1
- package/dist/src/lib/data-input.d.ts +23 -0
- package/dist/src/lib/data-input.js +50 -0
- package/dist/src/lib/data-input.js.map +1 -0
- package/dist/src/lib/db-codegen/dbTemplates.js +1 -1
- package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTypeIR.d.ts +1 -1
- package/dist/src/lib/env-resolver-core.d.ts +147 -0
- package/dist/src/lib/env-resolver-core.js +265 -0
- package/dist/src/lib/env-resolver-core.js.map +1 -0
- package/dist/src/lib/env-resolver.d.ts +28 -6
- package/dist/src/lib/env-resolver.js +44 -32
- package/dist/src/lib/env-resolver.js.map +1 -1
- package/dist/src/lib/generated-allowlist.js +12 -0
- package/dist/src/lib/generated-allowlist.js.map +1 -1
- package/dist/src/lib/generated-config-surfaces.d.ts +682 -0
- package/dist/src/lib/generated-config-surfaces.js +4058 -0
- package/dist/src/lib/generated-config-surfaces.js.map +1 -0
- package/dist/src/lib/generated-template-lint.d.ts +212 -0
- package/dist/src/lib/generated-template-lint.js +624 -0
- package/dist/src/lib/generated-template-lint.js.map +1 -0
- package/dist/src/lib/init-adopt.d.ts +16 -0
- package/dist/src/lib/init-adopt.js +34 -0
- package/dist/src/lib/init-adopt.js.map +1 -0
- package/dist/src/lib/init-assets.d.ts +39 -0
- package/dist/src/lib/init-assets.js +97 -0
- package/dist/src/lib/init-assets.js.map +1 -0
- package/dist/src/lib/init-config.d.ts +44 -5
- package/dist/src/lib/init-config.js +80 -7
- package/dist/src/lib/init-config.js.map +1 -1
- package/dist/src/lib/init-ios-links.d.ts +50 -0
- package/dist/src/lib/init-ios-links.js +153 -0
- package/dist/src/lib/init-ios-links.js.map +1 -0
- package/dist/src/lib/init-plan.d.ts +80 -0
- package/dist/src/lib/init-plan.js +95 -0
- package/dist/src/lib/init-plan.js.map +1 -0
- package/dist/src/lib/init-production-env.d.ts +48 -0
- package/dist/src/lib/init-production-env.js +59 -0
- package/dist/src/lib/init-production-env.js.map +1 -0
- package/dist/src/lib/init-schema.d.ts +74 -0
- package/dist/src/lib/init-schema.js +358 -0
- package/dist/src/lib/init-schema.js.map +1 -0
- package/dist/src/lib/init-xcode.d.ts +33 -0
- package/dist/src/lib/init-xcode.js +114 -0
- package/dist/src/lib/init-xcode.js.map +1 -0
- package/dist/src/lib/integration-request-config.d.ts +30 -0
- package/dist/src/lib/integration-request-config.js +145 -0
- package/dist/src/lib/integration-request-config.js.map +1 -0
- package/dist/src/lib/local-state.d.ts +55 -0
- package/dist/src/lib/local-state.js +167 -0
- package/dist/src/lib/local-state.js.map +1 -0
- package/dist/src/lib/log-inspection.d.ts +568 -0
- package/dist/src/lib/log-inspection.js +639 -0
- package/dist/src/lib/log-inspection.js.map +1 -0
- package/dist/src/lib/migration-nag.d.ts +2 -2
- package/dist/src/lib/migration-nag.js +3 -3
- package/dist/src/lib/object-status-filter.d.ts +22 -0
- package/dist/src/lib/object-status-filter.js +45 -0
- package/dist/src/lib/object-status-filter.js.map +1 -0
- package/dist/src/lib/output.d.ts +24 -1
- package/dist/src/lib/output.js +42 -1
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/package-manager.d.ts +140 -0
- package/dist/src/lib/package-manager.js +305 -0
- package/dist/src/lib/package-manager.js.map +1 -0
- package/dist/src/lib/paginate.d.ts +50 -0
- package/dist/src/lib/paginate.js +53 -0
- package/dist/src/lib/paginate.js.map +1 -1
- package/dist/src/lib/platform-owned.d.ts +29 -3
- package/dist/src/lib/platform-owned.js +42 -3
- package/dist/src/lib/platform-owned.js.map +1 -1
- package/dist/src/lib/project-config.js +13 -5
- package/dist/src/lib/project-config.js.map +1 -1
- package/dist/src/lib/query-operators.d.ts +1 -1
- package/dist/src/lib/query-operators.js +1 -1
- package/dist/src/lib/record-filter.d.ts +18 -0
- package/dist/src/lib/record-filter.js +55 -0
- package/dist/src/lib/record-filter.js.map +1 -0
- package/dist/src/lib/resolve-init-dev-port.d.ts +56 -0
- package/dist/src/lib/resolve-init-dev-port.js +55 -0
- package/dist/src/lib/resolve-init-dev-port.js.map +1 -0
- package/dist/src/lib/resolve-init-server.d.ts +64 -0
- package/dist/src/lib/resolve-init-server.js +77 -0
- package/dist/src/lib/resolve-init-server.js.map +1 -0
- package/dist/src/lib/resolve-platform.d.ts +43 -14
- package/dist/src/lib/resolve-platform.js +74 -12
- package/dist/src/lib/resolve-platform.js.map +1 -1
- package/dist/src/lib/run-status.d.ts +19 -0
- package/dist/src/lib/run-status.generated.d.ts +39 -0
- package/dist/src/lib/run-status.generated.js +66 -0
- package/dist/src/lib/run-status.generated.js.map +1 -0
- package/dist/src/lib/run-status.js +19 -0
- package/dist/src/lib/run-status.js.map +1 -0
- package/dist/src/lib/server-text-normalization.d.ts +51 -0
- package/dist/src/lib/server-text-normalization.js +90 -0
- package/dist/src/lib/server-text-normalization.js.map +1 -0
- package/dist/src/lib/server-url.d.ts +22 -0
- package/dist/src/lib/server-url.js +33 -0
- package/dist/src/lib/server-url.js.map +1 -0
- package/dist/src/lib/signing-secret-status.d.ts +81 -0
- package/dist/src/lib/signing-secret-status.js +116 -0
- package/dist/src/lib/signing-secret-status.js.map +1 -0
- package/dist/src/lib/skill-installer.js +6 -3
- package/dist/src/lib/skill-installer.js.map +1 -1
- package/dist/src/lib/snapshots.d.ts +2 -2
- package/dist/src/lib/snapshots.js +2 -2
- package/dist/src/lib/swift-codegen/dbGenerator.d.ts +15 -2
- package/dist/src/lib/swift-codegen/dbGenerator.js +250 -18
- package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -1
- package/dist/src/lib/swift-codegen/generator.d.ts +11 -2
- package/dist/src/lib/swift-codegen/generator.js +181 -7
- package/dist/src/lib/swift-codegen/generator.js.map +1 -1
- package/dist/src/lib/swift-codegen/siblingSymbols.d.ts +7 -3
- package/dist/src/lib/swift-codegen/siblingSymbols.js +4 -1
- package/dist/src/lib/swift-codegen/siblingSymbols.js.map +1 -1
- package/dist/src/lib/sync-dir-selector.d.ts +21 -0
- package/dist/src/lib/sync-dir-selector.js +30 -0
- package/dist/src/lib/sync-dir-selector.js.map +1 -0
- package/dist/src/lib/sync-paths.d.ts +39 -0
- package/dist/src/lib/sync-paths.js +70 -2
- package/dist/src/lib/sync-paths.js.map +1 -1
- package/dist/src/lib/sync-resource-types.d.ts +138 -31
- package/dist/src/lib/sync-resource-types.js +241 -47
- package/dist/src/lib/sync-resource-types.js.map +1 -1
- package/dist/src/lib/sync-selectors.d.ts +95 -0
- package/dist/src/lib/sync-selectors.js +228 -0
- package/dist/src/lib/sync-selectors.js.map +1 -0
- package/dist/src/lib/template.d.ts +62 -4
- package/dist/src/lib/template.js +147 -39
- package/dist/src/lib/template.js.map +1 -1
- package/dist/src/lib/test-case-keys.d.ts +29 -0
- package/dist/src/lib/test-case-keys.js +55 -0
- package/dist/src/lib/test-case-keys.js.map +1 -0
- package/dist/src/lib/test-case-variables.d.ts +15 -0
- package/dist/src/lib/test-case-variables.js +29 -0
- package/dist/src/lib/test-case-variables.js.map +1 -0
- package/dist/src/lib/toml-database-config.d.ts +4 -4
- package/dist/src/lib/toml-database-config.js +27 -34
- package/dist/src/lib/toml-database-config.js.map +1 -1
- package/dist/src/lib/toml-metadata-config.d.ts +9 -5
- package/dist/src/lib/toml-metadata-config.js +40 -45
- package/dist/src/lib/toml-metadata-config.js.map +1 -1
- package/dist/src/lib/toml-native-form.d.ts +1 -1
- package/dist/src/lib/toml-native-form.js +1 -1
- package/dist/src/lib/toml-params-validator.d.ts +1 -1
- package/dist/src/lib/toml-params-validator.js +1 -1
- package/dist/src/lib/toml-scalar-edit.d.ts +43 -0
- package/dist/src/lib/toml-scalar-edit.js +283 -0
- package/dist/src/lib/toml-scalar-edit.js.map +1 -0
- package/dist/src/lib/user-selector.d.ts +24 -0
- package/dist/src/lib/user-selector.js +33 -0
- package/dist/src/lib/user-selector.js.map +1 -0
- package/dist/src/lib/version-check.d.ts +25 -0
- package/dist/src/lib/version-check.js +77 -8
- package/dist/src/lib/version-check.js.map +1 -1
- package/dist/src/lib/watch.d.ts +121 -0
- package/dist/src/lib/watch.js +169 -0
- package/dist/src/lib/watch.js.map +1 -0
- package/dist/src/lib/workflow-apply.d.ts +69 -45
- package/dist/src/lib/workflow-apply.js +99 -72
- package/dist/src/lib/workflow-apply.js.map +1 -1
- package/dist/src/lib/workflow-codegen/generator.js +4 -4
- package/dist/src/lib/workflow-codegen/generator.js.map +1 -1
- package/dist/src/lib/workflow-codegen/invokerIR.d.ts +11 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js +5 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -1
- package/dist/src/lib/workflow-config-apply.d.ts +70 -0
- package/dist/src/lib/workflow-config-apply.js +137 -0
- package/dist/src/lib/workflow-config-apply.js.map +1 -0
- package/dist/src/lib/workflow-config-sidecar.d.ts +63 -0
- package/dist/src/lib/workflow-config-sidecar.js +96 -0
- package/dist/src/lib/workflow-config-sidecar.js.map +1 -0
- package/dist/src/lib/workflow-defaults.d.ts +29 -0
- package/dist/src/lib/workflow-defaults.js +41 -0
- package/dist/src/lib/workflow-defaults.js.map +1 -0
- package/dist/src/lib/workflow-include-preserve.d.ts +76 -0
- package/dist/src/lib/workflow-include-preserve.js +286 -0
- package/dist/src/lib/workflow-include-preserve.js.map +1 -0
- package/dist/src/lib/workflow-payload.d.ts +31 -20
- package/dist/src/lib/workflow-payload.js +100 -110
- package/dist/src/lib/workflow-payload.js.map +1 -1
- package/dist/src/lib/workflow-toml-validator.d.ts +37 -16
- package/dist/src/lib/workflow-toml-validator.js +141 -16
- package/dist/src/lib/workflow-toml-validator.js.map +1 -1
- package/dist/src/types/index.d.ts +78 -22
- package/dist/src/validators.d.ts +7 -7
- package/dist/src/validators.js +6 -6
- package/package.json +11 -6
- package/dist/src/commands/settings.d.ts +0 -15
- package/dist/src/commands/settings.js +0 -102
- package/dist/src/commands/settings.js.map +0 -1
|
@@ -1,12 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { basename } from "path";
|
|
3
|
-
import { parseConfigToml } from "../lib/config-toml.js";
|
|
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
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";
|
|
9
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
|
+
/**
|
|
11
|
+
* Resolve a prompt reference — its server id, or the author-chosen key its
|
|
12
|
+
* TOML file is named for.
|
|
13
|
+
*
|
|
14
|
+
* Under TOML-only authoring the key is what an operator has: `prompts/<key>.toml`
|
|
15
|
+
* is the object, and the id is a server detail they never wrote down. Mirrors
|
|
16
|
+
* `resolveScriptId` in `scripts.ts`.
|
|
17
|
+
*/
|
|
18
|
+
async function resolvePromptId(client, appId, ref) {
|
|
19
|
+
const { items } = await client.listPrompts(appId, { limit: 100 });
|
|
20
|
+
const byId = (items ?? []).find((p) => p.promptId === ref);
|
|
21
|
+
if (byId)
|
|
22
|
+
return byId.promptId;
|
|
23
|
+
const byKey = (items ?? []).find((p) => p.promptKey === ref);
|
|
24
|
+
if (byKey)
|
|
25
|
+
return byKey.promptId;
|
|
26
|
+
throw new Error(`Prompt not found: ${ref}`);
|
|
27
|
+
}
|
|
10
28
|
export function registerPromptsCommands(program) {
|
|
11
29
|
const prompts = program
|
|
12
30
|
.command("prompts")
|
|
@@ -14,9 +32,19 @@ export function registerPromptsCommands(program) {
|
|
|
14
32
|
.addHelpText("after", `
|
|
15
33
|
Examples:
|
|
16
34
|
$ primitive prompts list
|
|
17
|
-
$ primitive
|
|
35
|
+
$ primitive config create prompt summarizer # scaffold prompts/summarizer.toml
|
|
36
|
+
$ primitive config push --only prompt/summarizer # apply it
|
|
18
37
|
$ primitive prompts execute 01HXY... --vars '{"text":"Hello world"}'
|
|
19
|
-
$ primitive prompts configs
|
|
38
|
+
$ primitive prompts configs list summarizer # what the server runs
|
|
39
|
+
`);
|
|
40
|
+
// Server-side configuration is authored in TOML and applied with `config push`
|
|
41
|
+
// (issue #2645) — this group no longer carries config-setting flags.
|
|
42
|
+
prompts.addHelpText("after", `
|
|
43
|
+
Configuration lives in prompts/<key>.toml:
|
|
44
|
+
$ primitive config fields prompt # the file's keys, types and defaults
|
|
45
|
+
$ primitive config create prompt <key> # scaffold one locally
|
|
46
|
+
$ primitive config set prompt/<key> <path>=<value>
|
|
47
|
+
$ primitive config push --only prompt/<key>
|
|
20
48
|
`);
|
|
21
49
|
// List prompts
|
|
22
50
|
prompts
|
|
@@ -24,14 +52,22 @@ Examples:
|
|
|
24
52
|
.description("List prompts")
|
|
25
53
|
.argument("[app-id]", "App ID (uses current app if not specified)")
|
|
26
54
|
.option("--app <app-id>", "App ID")
|
|
27
|
-
.option("--status <status>", "Filter by status:
|
|
55
|
+
.option("--status <status>", "Filter by status: active, inactive, archived")
|
|
28
56
|
.option("--json", "Output as JSON")
|
|
29
57
|
.action(async (appId, options) => {
|
|
30
58
|
const resolvedAppId = resolveAppId(appId, options);
|
|
31
59
|
const client = new ApiClient();
|
|
32
60
|
try {
|
|
61
|
+
// #2803 — `draft` is gone; refuse it by name rather than returning the
|
|
62
|
+
// empty list a canonical filter would produce. #2887 added `archived`:
|
|
63
|
+
// the delete flow writes one now, so asking for the retired rows is a
|
|
64
|
+
// question with an answer rather than a 400.
|
|
33
65
|
const { items } = await client.listPrompts(resolvedAppId, {
|
|
34
|
-
status: options.status,
|
|
66
|
+
status: parseStatusFilter("prompts", options.status, [
|
|
67
|
+
"active",
|
|
68
|
+
"inactive",
|
|
69
|
+
"archived",
|
|
70
|
+
]),
|
|
35
71
|
});
|
|
36
72
|
if (options.json) {
|
|
37
73
|
json(items);
|
|
@@ -54,208 +90,154 @@ Examples:
|
|
|
54
90
|
process.exit(1);
|
|
55
91
|
}
|
|
56
92
|
});
|
|
57
|
-
//
|
|
93
|
+
// #2645 criterion 9 / #2803 — the operational verbs. They are the only
|
|
94
|
+
// writers of `status`, which is server-owned and no longer a TOML key.
|
|
58
95
|
prompts
|
|
59
|
-
.command("
|
|
60
|
-
.description("
|
|
61
|
-
.argument("
|
|
62
|
-
.option("--app <app-id>", "App ID")
|
|
63
|
-
.option("--key <key>", "Prompt key (unique identifier)")
|
|
64
|
-
.option("--name <name>", "Display name")
|
|
65
|
-
.option("--description <desc>", "Description")
|
|
66
|
-
.option("--provider <provider>", "LLM provider: openrouter, gemini", "openrouter")
|
|
67
|
-
.option("--model <model>", "Model name")
|
|
68
|
-
.option("--system-prompt <prompt>", "System prompt")
|
|
69
|
-
.option("--user-template <template>", "User prompt template")
|
|
70
|
-
.option("--temperature <temp>", "Temperature (0-1)")
|
|
71
|
-
.option("--max-tokens <n>", "Max output tokens")
|
|
72
|
-
.option("--output-format <format>", "Output format: text, json")
|
|
73
|
-
.option("--input-schema <json>", "Input schema as JSON")
|
|
74
|
-
.option("--output-schema <json>", "Output schema as JSON")
|
|
75
|
-
.option("--from-file <path>", "Load prompt from TOML file")
|
|
96
|
+
.command("disable")
|
|
97
|
+
.description("Take prompt out of service now. Operational, not configuration: `status` is server-owned and 'config push' can neither set nor clear it.")
|
|
98
|
+
.argument("<prompt-id>", "Prompt ID")
|
|
99
|
+
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
76
100
|
.option("--json", "Output as JSON")
|
|
77
|
-
.action(async (
|
|
78
|
-
const resolvedAppId = resolveAppId(
|
|
101
|
+
.action(async (id, options) => {
|
|
102
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
79
103
|
const client = new ApiClient();
|
|
80
|
-
let payload;
|
|
81
|
-
if (options.fromFile) {
|
|
82
|
-
try {
|
|
83
|
-
const content = readFileSync(options.fromFile, "utf-8");
|
|
84
|
-
const tomlData = parseConfigToml(content);
|
|
85
|
-
const prompt = tomlData.prompt || tomlData;
|
|
86
|
-
const config = tomlData.configs?.[0] || {};
|
|
87
|
-
payload = {
|
|
88
|
-
promptKey: prompt.key || prompt.promptKey,
|
|
89
|
-
displayName: prompt.displayName || prompt.name,
|
|
90
|
-
description: prompt.description,
|
|
91
|
-
provider: config.provider || prompt.provider || "openrouter",
|
|
92
|
-
model: config.model || prompt.model,
|
|
93
|
-
systemPrompt: config.systemPrompt || prompt.systemPrompt,
|
|
94
|
-
userPromptTemplate: config.userPromptTemplate || prompt.userPromptTemplate,
|
|
95
|
-
temperature: config.temperature || prompt.temperature,
|
|
96
|
-
maxTokens: config.maxTokens || prompt.maxTokens,
|
|
97
|
-
outputFormat: config.outputFormat || prompt.outputFormat,
|
|
98
|
-
inputSchema: prompt.inputSchema,
|
|
99
|
-
outputSchema: prompt.outputSchema,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
catch (err) {
|
|
103
|
-
error(`Failed to read TOML file: ${err.message}`);
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
if (!options.key || !options.name || !options.model || !options.userTemplate) {
|
|
109
|
-
error("Required: --key, --name, --model, --user-template (or use --from-file)");
|
|
110
|
-
process.exit(1);
|
|
111
|
-
}
|
|
112
|
-
payload = {
|
|
113
|
-
promptKey: options.key,
|
|
114
|
-
displayName: options.name,
|
|
115
|
-
description: options.description,
|
|
116
|
-
provider: options.provider,
|
|
117
|
-
model: options.model,
|
|
118
|
-
systemPrompt: options.systemPrompt,
|
|
119
|
-
userPromptTemplate: options.userTemplate,
|
|
120
|
-
temperature: options.temperature,
|
|
121
|
-
maxTokens: options.maxTokens ? parseInt(options.maxTokens) : undefined,
|
|
122
|
-
outputFormat: options.outputFormat,
|
|
123
|
-
inputSchema: options.inputSchema,
|
|
124
|
-
outputSchema: options.outputSchema,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
104
|
try {
|
|
128
|
-
const result = await client.
|
|
105
|
+
const result = await client.setPromptAvailability(resolvedAppId, id, "disable");
|
|
129
106
|
if (options.json) {
|
|
130
107
|
json(result);
|
|
131
108
|
return;
|
|
132
109
|
}
|
|
133
|
-
success(`Prompt
|
|
134
|
-
keyValue("Prompt ID", result.promptId);
|
|
135
|
-
keyValue("Key", result.promptKey);
|
|
110
|
+
success(`Prompt disabled: ${result.promptKey || id}`);
|
|
136
111
|
}
|
|
137
112
|
catch (err) {
|
|
138
113
|
error(err.message);
|
|
139
114
|
process.exit(1);
|
|
140
115
|
}
|
|
141
116
|
});
|
|
142
|
-
// Get prompt
|
|
143
117
|
prompts
|
|
144
|
-
.command("
|
|
145
|
-
.description("
|
|
118
|
+
.command("enable")
|
|
119
|
+
.description("Put prompt back in service.")
|
|
146
120
|
.argument("<prompt-id>", "Prompt ID")
|
|
147
121
|
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
148
122
|
.option("--json", "Output as JSON")
|
|
149
|
-
.action(async (
|
|
123
|
+
.action(async (id, options) => {
|
|
150
124
|
const resolvedAppId = resolveAppId(undefined, options);
|
|
151
125
|
const client = new ApiClient();
|
|
152
126
|
try {
|
|
153
|
-
const result = await client.
|
|
127
|
+
const result = await client.setPromptAvailability(resolvedAppId, id, "enable");
|
|
154
128
|
if (options.json) {
|
|
155
129
|
json(result);
|
|
156
130
|
return;
|
|
157
131
|
}
|
|
158
|
-
|
|
159
|
-
printResult("Key", result.promptKey);
|
|
160
|
-
printResult("Name", result.displayName);
|
|
161
|
-
printResult("Description", result.description);
|
|
162
|
-
printResult("Status", formatStatus(result.status));
|
|
163
|
-
printResult("Active Config", result.activeConfigId || "-");
|
|
164
|
-
if (result.configs && result.configs.length > 0) {
|
|
165
|
-
divider();
|
|
166
|
-
info(`Configs (${result.configs.length}):`);
|
|
167
|
-
console.log(formatTable(result.configs, [
|
|
168
|
-
{ header: "ID", key: "configId", format: formatId },
|
|
169
|
-
{ header: "NAME", key: "configName" },
|
|
170
|
-
{ header: "MODEL", key: "model" },
|
|
171
|
-
{ header: "STATUS", key: "status", format: formatStatus },
|
|
172
|
-
]));
|
|
173
|
-
}
|
|
132
|
+
success(`Prompt enabled: ${result.promptKey || id}`);
|
|
174
133
|
}
|
|
175
134
|
catch (err) {
|
|
176
135
|
error(err.message);
|
|
177
136
|
process.exit(1);
|
|
178
137
|
}
|
|
179
138
|
});
|
|
180
|
-
//
|
|
139
|
+
// #2907 — the delete flow's CLI spelling. Not a second writer of `status`:
|
|
140
|
+
// this is the plain `DELETE` #2887 made retire-with-history, which until now
|
|
141
|
+
// only the admin API and the console could reach. Named `archive`, not
|
|
142
|
+
// `delete`, because a configuration noun gets no `delete` verb
|
|
143
|
+
// (docs/cli-design.md) and prune-by-push stays the only CLI hard-delete path.
|
|
181
144
|
prompts
|
|
182
|
-
.command("
|
|
183
|
-
.description("
|
|
145
|
+
.command("archive")
|
|
146
|
+
.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.")
|
|
184
147
|
.argument("<prompt-id>", "Prompt ID")
|
|
185
148
|
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
186
|
-
.option("--
|
|
187
|
-
.option("--description <desc>", "Description")
|
|
188
|
-
.option("--status <status>", "Status: draft, active, archived")
|
|
189
|
-
.option("--input-schema <json>", "Input schema as JSON")
|
|
190
|
-
.option("--output-schema <json>", "Output schema as JSON")
|
|
149
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
191
150
|
.option("--json", "Output as JSON")
|
|
192
|
-
.
|
|
151
|
+
.addHelpText("after", `
|
|
152
|
+
What archiving a prompt costs (retire-with-history):
|
|
153
|
+
The member endpoint refuses it, and so does admin execution — the diagnostic
|
|
154
|
+
runs an INACTIVE prompt on purpose, but not an archived one.
|
|
155
|
+
Its configs, executions and analytics keep resolving what they point at.
|
|
156
|
+
The archived row still holds its promptKey.
|
|
157
|
+
\`primitive prompts enable\` refuses an archived prompt; there is no
|
|
158
|
+
un-archive.
|
|
159
|
+
The per-version \`[[configs]] status\` is a different key and is untouched.
|
|
160
|
+
|
|
161
|
+
Reclaiming the key means hard-deleting the row:
|
|
162
|
+
$ rm prompts/<key>.toml
|
|
163
|
+
$ primitive config push --prune # confirmed; hard-deletes the row
|
|
164
|
+
# or hard-delete it in the console, then re-add the file and push.
|
|
165
|
+
`)
|
|
166
|
+
.action(async (id, options) => {
|
|
193
167
|
const resolvedAppId = resolveAppId(undefined, options);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
168
|
+
if (!options.yes) {
|
|
169
|
+
let confirmed;
|
|
170
|
+
try {
|
|
171
|
+
confirmed = await confirmPrompt(`Archive prompt ${id}? It is retired, its configs and executions are ` +
|
|
172
|
+
`kept, it goes on holding its key, and there is no un-archive.`);
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
error(err.message);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
if (!confirmed) {
|
|
179
|
+
info("Cancelled.");
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
208
182
|
}
|
|
209
183
|
const client = new ApiClient();
|
|
210
184
|
try {
|
|
211
|
-
|
|
185
|
+
// The soft delete: no `hard`, so the configs and stored prompt bodies
|
|
186
|
+
// survive and every execution row keeps resolving.
|
|
187
|
+
const result = await client.deletePrompt(resolvedAppId, id);
|
|
212
188
|
if (options.json) {
|
|
213
189
|
json(result);
|
|
214
190
|
return;
|
|
215
191
|
}
|
|
216
|
-
success(
|
|
192
|
+
success(`Prompt archived: ${id}`);
|
|
193
|
+
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`.");
|
|
217
194
|
}
|
|
218
195
|
catch (err) {
|
|
219
196
|
error(err.message);
|
|
220
197
|
process.exit(1);
|
|
221
198
|
}
|
|
222
199
|
});
|
|
223
|
-
// Delete prompt
|
|
224
200
|
prompts
|
|
225
|
-
.command("
|
|
226
|
-
.description("
|
|
201
|
+
.command("get")
|
|
202
|
+
.description("Get prompt details")
|
|
227
203
|
.argument("<prompt-id>", "Prompt ID")
|
|
228
204
|
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
229
|
-
.option("--
|
|
230
|
-
.option("-y, --yes", "Skip confirmation prompt")
|
|
205
|
+
.option("--json", "Output as JSON")
|
|
231
206
|
.action(async (promptId, options) => {
|
|
232
207
|
const resolvedAppId = resolveAppId(undefined, options);
|
|
233
|
-
if (!options.yes) {
|
|
234
|
-
const action = options.hard ? "permanently delete" : "archive";
|
|
235
|
-
let confirm;
|
|
236
|
-
try {
|
|
237
|
-
confirm = await confirmPrompt(`Are you sure you want to ${action} prompt ${promptId}?`);
|
|
238
|
-
}
|
|
239
|
-
catch (err) {
|
|
240
|
-
error(err.message);
|
|
241
|
-
process.exit(1);
|
|
242
|
-
}
|
|
243
|
-
if (!confirm) {
|
|
244
|
-
info("Cancelled.");
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
208
|
const client = new ApiClient();
|
|
249
209
|
try {
|
|
250
|
-
await client.
|
|
251
|
-
|
|
210
|
+
const result = await client.getPrompt(resolvedAppId, promptId);
|
|
211
|
+
if (options.json) {
|
|
212
|
+
json(result);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
printResult("Prompt ID", result.promptId);
|
|
216
|
+
printResult("Key", result.promptKey);
|
|
217
|
+
printResult("Name", result.displayName);
|
|
218
|
+
printResult("Description", result.description);
|
|
219
|
+
printResult("Status", formatStatus(result.status));
|
|
220
|
+
// #2652: no rule means every non-admin execution is denied. Print that
|
|
221
|
+
// in words — a blank or "-" would read as "unrestricted" and hide a
|
|
222
|
+
// prompt that was never given a rule.
|
|
223
|
+
printResult("Access Rule", formatAccessRuleForDisplay(result.accessRule, "prompt"));
|
|
224
|
+
printResult("Active Config", result.activeConfigId || "-");
|
|
225
|
+
if (result.configs && result.configs.length > 0) {
|
|
226
|
+
divider();
|
|
227
|
+
info(`Configs (${result.configs.length}):`);
|
|
228
|
+
console.log(formatTable(result.configs, [
|
|
229
|
+
{ header: "ID", key: "configId", format: formatId },
|
|
230
|
+
{ header: "NAME", key: "configName" },
|
|
231
|
+
{ header: "MODEL", key: "model" },
|
|
232
|
+
{ header: "STATUS", key: "status", format: formatStatus },
|
|
233
|
+
]));
|
|
234
|
+
}
|
|
252
235
|
}
|
|
253
236
|
catch (err) {
|
|
254
237
|
error(err.message);
|
|
255
238
|
process.exit(1);
|
|
256
239
|
}
|
|
257
240
|
});
|
|
258
|
-
// Execute prompt
|
|
259
241
|
prompts
|
|
260
242
|
.command("execute")
|
|
261
243
|
.description("Execute a prompt with variables")
|
|
@@ -397,8 +379,25 @@ Examples:
|
|
|
397
379
|
process.exit(1);
|
|
398
380
|
}
|
|
399
381
|
});
|
|
400
|
-
// Configs subcommand
|
|
401
|
-
|
|
382
|
+
// Configs subcommand — reads only. A prompt's configs are `[[configs]]`
|
|
383
|
+
// entries in its own TOML file (#2645), so the verbs that used to write them
|
|
384
|
+
// were a second writer for state the file already owns.
|
|
385
|
+
const configs = prompts
|
|
386
|
+
.command("configs")
|
|
387
|
+
.description("Inspect a prompt's configs (authored in prompts/<key>.toml)")
|
|
388
|
+
.addHelpText("after", `
|
|
389
|
+
A prompt's configs are the \`[[configs]]\` entries of prompts/<key>.toml. Add,
|
|
390
|
+
edit, retire (\`status = "archived"\`) or mark one live (\`active = true\`) in that
|
|
391
|
+
file, then apply it:
|
|
392
|
+
|
|
393
|
+
$ primitive config push --only prompt/<key>
|
|
394
|
+
|
|
395
|
+
That per-config \`status\` says which VERSION is retired and is yours to author.
|
|
396
|
+
The PROMPT's own availability is server-owned and has no TOML key: use
|
|
397
|
+
'primitive prompts disable' / 'enable'.
|
|
398
|
+
|
|
399
|
+
Duplicating a config is copying its \`[[configs]]\` block and renaming it.
|
|
400
|
+
`);
|
|
402
401
|
// List configs
|
|
403
402
|
configs
|
|
404
403
|
.command("list")
|
|
@@ -432,154 +431,51 @@ Examples:
|
|
|
432
431
|
process.exit(1);
|
|
433
432
|
}
|
|
434
433
|
});
|
|
435
|
-
//
|
|
434
|
+
// Get one config. `list` says which configs exist; this says what one of
|
|
435
|
+
// them actually runs — the body, provider and model the server would use.
|
|
436
|
+
// With `prompts configs create/update` gone, this is where that content is
|
|
437
|
+
// read back from.
|
|
436
438
|
configs
|
|
437
|
-
.command("
|
|
438
|
-
.description("
|
|
439
|
-
.argument("<prompt
|
|
439
|
+
.command("get")
|
|
440
|
+
.description("Show one config of a prompt as the server has it")
|
|
441
|
+
.argument("<prompt>", "Prompt ID or key")
|
|
442
|
+
.argument("<config>", "Config ID or name")
|
|
440
443
|
.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
444
|
.option("--json", "Output as JSON")
|
|
451
|
-
.action(async (
|
|
445
|
+
.action(async (prompt, config, options) => {
|
|
452
446
|
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
447
|
const client = new ApiClient();
|
|
469
448
|
try {
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
449
|
+
const promptId = await resolvePromptId(client, resolvedAppId, prompt);
|
|
450
|
+
const { items } = await client.listPromptConfigs(resolvedAppId, promptId);
|
|
451
|
+
const summary = (items ?? []).find((c) => c.configId === config || c.configName === config);
|
|
452
|
+
if (!summary) {
|
|
453
|
+
error(`Prompt '${prompt}' has no config '${config}'. Run 'primitive prompts configs list ${prompt}' to see them.`);
|
|
454
|
+
process.exit(1);
|
|
474
455
|
}
|
|
475
|
-
|
|
476
|
-
|
|
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
|
-
});
|
|
456
|
+
const full = (await client.getPromptConfig(resolvedAppId, promptId, summary.configId)) ??
|
|
457
|
+
summary;
|
|
518
458
|
if (options.json) {
|
|
519
|
-
json(
|
|
459
|
+
json(full);
|
|
520
460
|
return;
|
|
521
461
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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;
|
|
462
|
+
printResult("Config ID", full.configId);
|
|
463
|
+
printResult("Name", full.configName);
|
|
464
|
+
printResult("Status", formatStatus(full.status));
|
|
465
|
+
printResult("Provider", full.provider || "-");
|
|
466
|
+
printResult("Model", full.model || "-");
|
|
467
|
+
if (full.description)
|
|
468
|
+
printResult("Description", full.description);
|
|
469
|
+
if (full.systemPrompt) {
|
|
470
|
+
divider();
|
|
471
|
+
info("System prompt:");
|
|
472
|
+
console.log(full.systemPrompt);
|
|
473
|
+
}
|
|
474
|
+
if (full.userPrompt) {
|
|
475
|
+
divider();
|
|
476
|
+
info("User prompt:");
|
|
477
|
+
console.log(full.userPrompt);
|
|
581
478
|
}
|
|
582
|
-
success("Config updated.");
|
|
583
479
|
}
|
|
584
480
|
catch (err) {
|
|
585
481
|
error(err.message);
|
|
@@ -591,11 +487,14 @@ Examples:
|
|
|
591
487
|
// ============================================
|
|
592
488
|
const tests = prompts
|
|
593
489
|
.command("tests")
|
|
594
|
-
.description("
|
|
490
|
+
.description("Run and inspect prompt test cases")
|
|
595
491
|
.addHelpText("after", `
|
|
492
|
+
Test cases are authored in TOML at prompts/<key>.tests/<name>.toml and applied
|
|
493
|
+
with \`primitive config push\` (issue #2769); \`config fields prompt\` lists the
|
|
494
|
+
fields. Deleting a case is removing its file and running \`config push --prune\`.
|
|
495
|
+
|
|
596
496
|
Examples:
|
|
597
497
|
$ primitive prompts tests list <prompt-id>
|
|
598
|
-
$ primitive prompts tests create <prompt-id> --name "Basic test" --vars '{"input":"hello"}'
|
|
599
498
|
$ primitive prompts tests run <prompt-id> <test-case-id>
|
|
600
499
|
$ primitive prompts tests run-all <prompt-id>
|
|
601
500
|
$ primitive prompts tests run-all <prompt-id> --test-cases 01ABC,01DEF,01GHI
|
|
@@ -632,79 +531,6 @@ Examples:
|
|
|
632
531
|
process.exit(1);
|
|
633
532
|
}
|
|
634
533
|
});
|
|
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
534
|
// Get test case
|
|
709
535
|
tests
|
|
710
536
|
.command("get")
|
|
@@ -770,121 +596,6 @@ Examples:
|
|
|
770
596
|
process.exit(1);
|
|
771
597
|
}
|
|
772
598
|
});
|
|
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
|
-
let confirm;
|
|
866
|
-
try {
|
|
867
|
-
confirm = await confirmPrompt(`Are you sure you want to delete test case ${testCaseId}?`);
|
|
868
|
-
}
|
|
869
|
-
catch (err) {
|
|
870
|
-
error(err.message);
|
|
871
|
-
process.exit(1);
|
|
872
|
-
}
|
|
873
|
-
if (!confirm) {
|
|
874
|
-
info("Cancelled.");
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
const client = new ApiClient();
|
|
879
|
-
try {
|
|
880
|
-
await client.deleteTestCase(resolvedAppId, "prompt", promptId, testCaseId);
|
|
881
|
-
success("Test case deleted.");
|
|
882
|
-
}
|
|
883
|
-
catch (err) {
|
|
884
|
-
error(err.message);
|
|
885
|
-
process.exit(1);
|
|
886
|
-
}
|
|
887
|
-
});
|
|
888
599
|
// Run a single test case
|
|
889
600
|
tests
|
|
890
601
|
.command("run")
|
|
@@ -1146,12 +857,20 @@ Examples:
|
|
|
1146
857
|
let result = await fetchStatus();
|
|
1147
858
|
if (options.wait && result.status === "running") {
|
|
1148
859
|
info("Waiting for batch completion...");
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
860
|
+
// #1969 — shared poll primitive (delay-then-probe every 2s, honoring
|
|
861
|
+
// an AbortSignal). `onError: "throw"` preserves the prior loop's
|
|
862
|
+
// fail-fast behavior: a throwing fetchStatus (expired token, deleted
|
|
863
|
+
// batch → 401/404) propagates to the command-level catch below and
|
|
864
|
+
// exits non-zero, rather than spinning forever with a frozen progress
|
|
865
|
+
// line. (The follow/watch tail loops legitimately keep "continue".)
|
|
866
|
+
const controller = new AbortController();
|
|
867
|
+
result = await pollUntil(fetchStatus, (r) => r.status !== "running", {
|
|
868
|
+
intervalMs: 2000,
|
|
869
|
+
signal: controller.signal,
|
|
870
|
+
onError: "throw",
|
|
1152
871
|
// Progress goes to stderr so it can't corrupt JSON on stdout under --json.
|
|
1153
|
-
progress(` Completed: ${
|
|
1154
|
-
}
|
|
872
|
+
onValue: (r) => progress(` Completed: ${r.completed}/${r.results?.length || 0} `),
|
|
873
|
+
});
|
|
1155
874
|
progressEnd();
|
|
1156
875
|
}
|
|
1157
876
|
if (options.json) {
|
|
@@ -1243,13 +962,15 @@ Examples:
|
|
|
1243
962
|
// ============================================
|
|
1244
963
|
const attachments = tests
|
|
1245
964
|
.command("attachments")
|
|
1246
|
-
.description("
|
|
965
|
+
.description("Inspect test case file attachments")
|
|
1247
966
|
.addHelpText("after", `
|
|
967
|
+
Attachments are authored as files in prompts/<key>.tests/<name>/ and applied
|
|
968
|
+
with \`primitive config push\` (#2769); removing one and running
|
|
969
|
+
\`config push --prune\` deletes it server-side. These verbs read server state.
|
|
970
|
+
|
|
1248
971
|
Examples:
|
|
1249
972
|
$ primitive prompts tests attachments list <prompt-id> <test-case-id>
|
|
1250
|
-
$ primitive prompts tests attachments upload <prompt-id> <test-case-id> ./document.pdf
|
|
1251
973
|
$ primitive prompts tests attachments download <prompt-id> <test-case-id> document.pdf
|
|
1252
|
-
$ primitive prompts tests attachments delete <prompt-id> <test-case-id> document.pdf
|
|
1253
974
|
`);
|
|
1254
975
|
// List attachments
|
|
1255
976
|
attachments
|
|
@@ -1287,49 +1008,6 @@ Examples:
|
|
|
1287
1008
|
process.exit(1);
|
|
1288
1009
|
}
|
|
1289
1010
|
});
|
|
1290
|
-
// Upload attachment
|
|
1291
|
-
attachments
|
|
1292
|
-
.command("upload")
|
|
1293
|
-
.description("Upload a file attachment to a test case")
|
|
1294
|
-
.argument("<prompt-id>", "Prompt ID")
|
|
1295
|
-
.argument("<test-case-id>", "Test Case ID")
|
|
1296
|
-
.argument("<file-path>", "Path to the file to upload")
|
|
1297
|
-
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
1298
|
-
.option("--name <name>", "Override the filename")
|
|
1299
|
-
.option("--json", "Output as JSON")
|
|
1300
|
-
.action(async (promptId, testCaseId, filePath, options) => {
|
|
1301
|
-
const resolvedAppId = resolveAppId(undefined, options);
|
|
1302
|
-
const client = new ApiClient();
|
|
1303
|
-
try {
|
|
1304
|
-
// Check file exists and get stats
|
|
1305
|
-
const stats = statSync(filePath);
|
|
1306
|
-
if (!stats.isFile()) {
|
|
1307
|
-
error(`Not a file: ${filePath}`);
|
|
1308
|
-
process.exit(1);
|
|
1309
|
-
}
|
|
1310
|
-
// Check file size (10 MB limit)
|
|
1311
|
-
const TEN_MB = 10 * 1024 * 1024;
|
|
1312
|
-
if (stats.size > TEN_MB) {
|
|
1313
|
-
error(`File too large (max 10MB): ${formatFileSize(stats.size)}`);
|
|
1314
|
-
process.exit(1);
|
|
1315
|
-
}
|
|
1316
|
-
const filename = options.name || basename(filePath);
|
|
1317
|
-
const data = readFileSync(filePath);
|
|
1318
|
-
const contentType = mimeLookup(filePath) || "application/octet-stream";
|
|
1319
|
-
const result = await client.uploadTestCaseAttachment(resolvedAppId, "prompt", promptId, testCaseId, filename, data, contentType);
|
|
1320
|
-
if (options.json) {
|
|
1321
|
-
json(result);
|
|
1322
|
-
return;
|
|
1323
|
-
}
|
|
1324
|
-
success(`Uploaded: ${filename}`);
|
|
1325
|
-
keyValue("Size", formatFileSize(result.attachment.size));
|
|
1326
|
-
keyValue("Type", result.attachment.contentType);
|
|
1327
|
-
}
|
|
1328
|
-
catch (err) {
|
|
1329
|
-
error(err.message);
|
|
1330
|
-
process.exit(1);
|
|
1331
|
-
}
|
|
1332
|
-
});
|
|
1333
1011
|
// Download attachment
|
|
1334
1012
|
attachments
|
|
1335
1013
|
.command("download")
|
|
@@ -1355,41 +1033,6 @@ Examples:
|
|
|
1355
1033
|
process.exit(1);
|
|
1356
1034
|
}
|
|
1357
1035
|
});
|
|
1358
|
-
// Delete attachment
|
|
1359
|
-
attachments
|
|
1360
|
-
.command("delete")
|
|
1361
|
-
.description("Delete a file attachment from a test case")
|
|
1362
|
-
.argument("<prompt-id>", "Prompt ID")
|
|
1363
|
-
.argument("<test-case-id>", "Test Case ID")
|
|
1364
|
-
.argument("<filename>", "Name of the file to delete")
|
|
1365
|
-
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
1366
|
-
.option("-y, --yes", "Skip confirmation prompt")
|
|
1367
|
-
.action(async (promptId, testCaseId, filename, options) => {
|
|
1368
|
-
const resolvedAppId = resolveAppId(undefined, options);
|
|
1369
|
-
if (!options.yes) {
|
|
1370
|
-
let confirm;
|
|
1371
|
-
try {
|
|
1372
|
-
confirm = await confirmPrompt(`Are you sure you want to delete attachment "${filename}"?`);
|
|
1373
|
-
}
|
|
1374
|
-
catch (err) {
|
|
1375
|
-
error(err.message);
|
|
1376
|
-
process.exit(1);
|
|
1377
|
-
}
|
|
1378
|
-
if (!confirm) {
|
|
1379
|
-
info("Cancelled.");
|
|
1380
|
-
return;
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
const client = new ApiClient();
|
|
1384
|
-
try {
|
|
1385
|
-
await client.deleteTestCaseAttachment(resolvedAppId, "prompt", promptId, testCaseId, filename);
|
|
1386
|
-
success(`Deleted: ${filename}`);
|
|
1387
|
-
}
|
|
1388
|
-
catch (err) {
|
|
1389
|
-
error(err.message);
|
|
1390
|
-
process.exit(1);
|
|
1391
|
-
}
|
|
1392
|
-
});
|
|
1393
1036
|
}
|
|
1394
1037
|
function formatFileSize(bytes) {
|
|
1395
1038
|
if (bytes < 1024)
|