@frontera-sdk/cli 1.52.39 → 1.52.41
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 +9 -0
- package/package.json +6 -4
- package/src/adopt.ts +6 -2
- package/src/api/apps-api.ts +86 -4
- package/src/api/blueprint-authoring-api.ts +35 -11
- package/src/api/dataset-api.ts +29 -0
- package/src/api/governed-action-api.ts +90 -2
- package/src/api/platform-api.ts +87 -2
- package/src/api/workflow-api.ts +33 -2
- package/src/app-contract.ts +156 -0
- package/src/app-lock.ts +125 -0
- package/src/args.ts +14 -3
- package/src/auth-verify.ts +10 -0
- package/src/automation-template.ts +12 -1
- package/src/blueprint/action-rules.ts +516 -0
- package/src/blueprint/action-semantic-fit.ts +205 -0
- package/src/blueprint/compile.ts +51 -2
- package/src/blueprint/diff.ts +34 -2
- package/src/blueprint/metric-query.ts +215 -0
- package/src/blueprint/metric-rules.ts +122 -0
- package/src/blueprint/object-sets.ts +116 -0
- package/src/blueprint/ontology-edit-plan.ts +49 -5
- package/src/blueprint/projection.ts +24 -1
- package/src/blueprint/scaffold.ts +84 -42
- package/src/commands/action/available.ts +1 -1
- package/src/commands/action/criteria-refusal.ts +84 -0
- package/src/commands/action/decide.ts +5 -2
- package/src/commands/action/deploy.ts +13 -4
- package/src/commands/action/list.ts +134 -19
- package/src/commands/action/requests.ts +34 -6
- package/src/commands/action/submit.ts +28 -13
- package/src/commands/agent/draft-changes.ts +1 -1
- package/src/commands/agent/index-commands.ts +69 -7
- package/src/commands/agent/resolve.ts +2 -0
- package/src/commands/app/checks.ts +44 -0
- package/src/commands/app/deploy.ts +243 -60
- package/src/commands/app/deployments.ts +97 -0
- package/src/commands/app/dev.ts +103 -11
- package/src/commands/app/init.ts +36 -3
- package/src/commands/app/promote.ts +45 -19
- package/src/commands/app/pull.ts +3 -1
- package/src/commands/app/rollback.ts +46 -0
- package/src/commands/app/sdk.ts +54 -8
- package/src/commands/app/unpin.ts +27 -0
- package/src/commands/app/versions.ts +1 -1
- package/src/commands/automation/bindings.ts +4 -2
- package/src/commands/automation/build-entry.ts +4 -1
- package/src/commands/automation/index-commands.ts +3 -1
- package/src/commands/automation/pull.ts +6 -1
- package/src/commands/automation/run.ts +34 -9
- package/src/commands/blueprint/bind.ts +2 -1
- package/src/commands/blueprint/declarative.ts +332 -32
- package/src/commands/blueprint/editable.ts +3 -3
- package/src/commands/blueprint/get.ts +6 -2
- package/src/commands/blueprint/grants.ts +64 -10
- package/src/commands/blueprint/metric.ts +108 -0
- package/src/commands/blueprint/object-sets.ts +71 -0
- package/src/commands/blueprint/query.ts +155 -31
- package/src/commands/dataset/index-commands.ts +156 -7
- package/src/commands/knowledge/index-commands.ts +122 -20
- package/src/commands/local/check.ts +657 -44
- package/src/commands/local/data.ts +33 -12
- package/src/commands/local/dev.ts +876 -89
- package/src/commands/local/docs-mcp.ts +10 -18
- package/src/commands/local/index-commands.ts +300 -63
- package/src/commands/local/init.ts +18 -5
- package/src/commands/local/key.ts +0 -3
- package/src/commands/local/providers.ts +0 -7
- package/src/commands/local/remove.ts +116 -0
- package/src/commands/local/schema.ts +3 -4
- package/src/commands/local/sdk-refresh.ts +59 -0
- package/src/commands/local/snapshot.ts +100 -0
- package/src/commands/local/sync-request.ts +321 -14
- package/src/commands/local/sync.ts +103 -6
- package/src/commands/mcp/serve.ts +34 -0
- package/src/commands/registry.ts +54 -10
- package/src/commands/types.ts +29 -0
- package/src/commands/workflow/archive.ts +46 -0
- package/src/commands/workflow/get.ts +2 -1
- package/src/commands/workflow/index-commands.ts +5 -0
- package/src/commands/workflow/push.ts +49 -2
- package/src/commands/workflow/run.ts +65 -7
- package/src/commands/workflow/runs.ts +9 -6
- package/src/commands/workflow/webhook-test.ts +166 -0
- package/src/config.ts +43 -6
- package/src/dev-broker.ts +52 -0
- package/src/exit.ts +103 -4
- package/src/flag-help.ts +77 -17
- package/src/forge/claude-commands.ts +11 -6
- package/src/forge/claude-skills.ts +4 -4
- package/src/forge/touches.ts +15 -12
- package/src/function-outside-imports.ts +89 -0
- package/src/harness.ts +42 -24
- package/src/heal.ts +113 -2
- package/src/kit.ts +1 -1
- package/src/local/fix-words.ts +12 -0
- package/src/local/function-bundle-recovery.ts +775 -0
- package/src/local/local-actions.ts +103 -10
- package/src/local/local-agent-action-grants.ts +210 -0
- package/src/local/local-agent-checks.ts +232 -0
- package/src/local/local-agent-grants.ts +29 -7
- package/src/local/local-agent-lean-plugins.ts +68 -0
- package/src/local/local-agent-missing-tools.ts +68 -0
- package/src/local/local-agent-name.ts +27 -0
- package/src/local/local-app-code.ts +191 -0
- package/src/local/local-app-dev-files.ts +186 -0
- package/src/local/local-app-dev-session.ts +88 -0
- package/src/local/local-app-lock-install.ts +114 -0
- package/src/local/local-app-pulled.ts +167 -0
- package/src/local/local-app-restore.ts +111 -0
- package/src/local/local-app-runtime.ts +68 -0
- package/src/local/local-app-sdk-paths.ts +149 -0
- package/src/local/local-app-sdk.ts +509 -0
- package/src/local/local-applied.ts +212 -22
- package/src/local/local-apply-history.ts +84 -0
- package/src/local/local-apply-summary.ts +86 -0
- package/src/local/local-apply-watchdog.ts +95 -0
- package/src/local/local-apps.ts +181 -8
- package/src/local/local-blueprint-errors.ts +198 -0
- package/src/local/local-blueprint-outline.ts +31 -12
- package/src/local/local-blueprint-schemas.ts +582 -0
- package/src/local/local-build-inputs.ts +48 -0
- package/src/local/local-build-summary.ts +495 -28
- package/src/local/local-case-evidence.ts +120 -0
- package/src/local/local-case-not-run.ts +153 -0
- package/src/local/local-chat-apps.ts +93 -16
- package/src/local/local-check-inherited.ts +83 -0
- package/src/local/local-cities.ts +89 -0
- package/src/local/local-component-health.ts +119 -0
- package/src/local/local-control.ts +34 -7
- package/src/local/local-db-snapshot.ts +348 -0
- package/src/local/local-dev-references.ts +347 -36
- package/src/local/local-dev-sync.ts +141 -12
- package/src/local/local-docker.ts +35 -1
- package/src/local/local-docs-search.ts +1 -1
- package/src/local/local-docs.ts +281 -2
- package/src/local/local-empty-workflows.ts +92 -0
- package/src/local/local-env.ts +16 -1
- package/src/local/local-file-checks.ts +104 -0
- package/src/local/local-function-ids.ts +141 -0
- package/src/local/local-function-recovery.ts +141 -0
- package/src/local/local-function-state.ts +87 -0
- package/src/local/local-keys.ts +7 -5
- package/src/local/local-knowledge-documents.ts +1 -1
- package/src/local/local-knowledge-ingestion.ts +225 -0
- package/src/local/local-knowledge-uploads.ts +217 -0
- package/src/local/local-lessons.ts +1 -1
- package/src/local/local-library.ts +1 -1
- package/src/local/local-media-placeholders.ts +182 -0
- package/src/local/local-media-sets.ts +90 -10
- package/src/local/local-model-parameters.ts +89 -0
- package/src/local/local-object-sets.ts +18 -6
- package/src/local/local-operating.ts +35 -1
- package/src/local/local-org-settings.ts +206 -8
- package/src/local/local-orphans.ts +321 -0
- package/src/local/local-personas.ts +296 -0
- package/src/local/local-portability.ts +143 -18
- package/src/local/local-ports.ts +99 -7
- package/src/local/local-project-notices.ts +234 -0
- package/src/local/local-project-secrets.ts +133 -0
- package/src/local/local-project.ts +12 -0
- package/src/local/local-provider-refusal.ts +66 -0
- package/src/local/local-request-evidence.ts +112 -0
- package/src/local/local-runtime-health.ts +123 -0
- package/src/local/local-runtime-history.ts +50 -0
- package/src/local/local-runtime-target.ts +42 -0
- package/src/local/local-sample-data.ts +141 -0
- package/src/local/local-sample-remake.ts +138 -0
- package/src/local/local-scaffold.ts +336 -53
- package/src/local/local-schema-check.ts +148 -0
- package/src/local/local-schemas.ts +742 -25
- package/src/local/local-secret-carry.ts +107 -0
- package/src/local/local-secrets.ts +41 -11
- package/src/local/local-seed.ts +102 -26
- package/src/local/local-stand-ins.ts +3 -3
- package/src/local/local-state.ts +43 -2
- package/src/local/local-studio-server.ts +170 -7
- package/src/local/local-supervisor.ts +44 -6
- package/src/local/local-sync-merge.ts +1 -1
- package/src/local/local-sync-names.ts +2 -0
- package/src/local/local-sync-plan.ts +19 -8
- package/src/local/local-sync-push.ts +15 -12
- package/src/local/local-sync-refs.ts +49 -3
- package/src/local/local-sync-remote.ts +349 -38
- package/src/local/local-sync-snapshot.ts +18 -13
- package/src/local/local-synthetic-data.ts +2116 -99
- package/src/local/local-test-baseline.ts +113 -0
- package/src/local/local-test-cases.ts +76 -1
- package/src/local/local-test-runner.ts +582 -91
- package/src/local/local-test-runs.ts +13 -5
- package/src/local/local-vendored-sdk.ts +183 -0
- package/src/local/local-workflow-state.ts +16 -3
- package/src/local/local-workflow-tests.ts +482 -0
- package/src/local/local-workspace-grants.ts +81 -0
- package/src/main.ts +50 -9
- package/src/mcp/a11y-scan.ts +55 -0
- package/src/mcp/browser/ax-snapshot.ts +181 -0
- package/src/mcp/browser/credentials.ts +78 -0
- package/src/mcp/browser/frames.ts +336 -0
- package/src/mcp/browser/headless-session.ts +413 -0
- package/src/mcp/browser/headless-tab.ts +575 -0
- package/src/mcp/browser/keys.ts +70 -0
- package/src/mcp/browser/page-scripts.ts +129 -0
- package/src/mcp/browser/wait.ts +73 -0
- package/src/mcp/forge-link.ts +126 -0
- package/src/mcp/headless.ts +199 -0
- package/src/mcp/mcp-apps.ts +435 -0
- package/src/mcp/mcp-chat.ts +226 -0
- package/src/mcp/mcp-cli.ts +53 -0
- package/src/mcp/mcp-protocol.ts +122 -0
- package/src/mcp/mcp-runtime.ts +113 -0
- package/src/mcp/mcp-server.ts +108 -0
- package/src/mcp/mcp-traces.ts +409 -0
- package/src/mcp/page-observation.ts +193 -0
- package/src/mcp/tools/agent-tools.ts +700 -0
- package/src/mcp/tools/app-tools.ts +241 -0
- package/src/mcp/tools/browser-tools.ts +310 -0
- package/src/mcp/tools/docs-tools.ts +32 -0
- package/src/mcp/tools/project-tools.ts +205 -0
- package/src/mcp/tools/test-tools.ts +339 -0
- package/src/mcp/tools/workflow-tools.ts +611 -0
- package/src/output.ts +1 -1
- package/src/pack.ts +8 -3
- package/src/packaging.ts +1 -1
- package/src/project-bootstrap.ts +107 -4
- package/src/project-context.ts +18 -1
- package/src/project.ts +27 -2
- package/src/provenance.ts +25 -0
- package/src/render-evidence.ts +65 -7
- package/src/scopes.ts +1 -1
- package/src/template.ts +14 -8
- package/src/templates/next-app-files.ts +35 -6
- package/src/templates/next-skills.ts +61 -27
- package/src/vendor/kit-assets.json +6 -6
- package/src/vendor/sdk-sources.json +17 -14
|
@@ -3,6 +3,7 @@ import { randomUUID } from 'node:crypto'
|
|
|
3
3
|
import { GovernedActionApi } from '../../api/governed-action-api'
|
|
4
4
|
import { CliError, UsageError } from '../../errors'
|
|
5
5
|
import { deriveOntologyEditPlan, PlanDerivationError } from '../../blueprint/ontology-edit-plan'
|
|
6
|
+
import { validationFindings } from '../../local/local-actions'
|
|
6
7
|
import { flagBool, flagString, type Command, type CommandContext } from '../types'
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -92,10 +93,18 @@ export const actionDeploy: Command = {
|
|
|
92
93
|
const validation = await client.validateBindingRevision(binding.id, 1)
|
|
93
94
|
|
|
94
95
|
if (validation.status !== 'valid') {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
// Each failed check, by its code and the platform's message, one per line; the full report in `--json` details.
|
|
97
|
+
const failed = validationFindings(validation)
|
|
98
|
+
throw new CliError(
|
|
99
|
+
`the write path for "${apiName}" did not validate${failed.length ? `: ${failed.length} check${failed.length === 1 ? '' : 's'} failed` : ` (status "${validation.status}")`}`,
|
|
100
|
+
{
|
|
101
|
+
code: 'ACTION_DEPLOY_INVALID',
|
|
102
|
+
hint: failed.length
|
|
103
|
+
? `${failed.map((f) => `✕ ${f}`).join('\n ')}\n fix the Action (blueprint/actions/${apiName}.yaml) or its object type's editable fields, publish, and deploy again`
|
|
104
|
+
: 'the service gave no findings for this report; read it with `--json`',
|
|
105
|
+
details: { action: apiName, bindingRevisionId: binding.id, validationReportId: validation.id, status: validation.status, findings: validation.findings ?? null, failed },
|
|
106
|
+
},
|
|
107
|
+
)
|
|
99
108
|
}
|
|
100
109
|
|
|
101
110
|
return {
|
|
@@ -1,49 +1,164 @@
|
|
|
1
|
-
import { GovernedActionApi } from '../../api/governed-action-api'
|
|
1
|
+
import { GovernedActionApi, type DeployableActionEntry } from '../../api/governed-action-api'
|
|
2
|
+
import type { BuildEntry } from '../../local/local-applied'
|
|
3
|
+
import { blueprintBuild } from '../../local/local-blueprint-outline'
|
|
4
|
+
import { localRuntimeAt } from '../../local/local-runtime-target'
|
|
2
5
|
import { table } from '../../table'
|
|
3
6
|
import type { Command } from '../types'
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
|
-
*
|
|
9
|
+
* Every Action and whether anything can run it.
|
|
7
10
|
*
|
|
8
11
|
* `deploymentStatus` is the question this answers: an Action can be published,
|
|
9
|
-
* correct and completely inert, and nothing else in the CLI says so.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
+
* correct and completely inert, and nothing else in the CLI says so. The plain
|
|
13
|
+
* published list holds only Actions that can be invoked, so it never showed
|
|
14
|
+
* the ones that matter here: this reads the deployable view, which keeps
|
|
15
|
+
* every published Action with its readiness (one deployable read per Action
|
|
16
|
+
* on a service without the list), and the draft, for Actions authored but not
|
|
17
|
+
* published yet. WHY NOT is said for each one that cannot run, with the
|
|
18
|
+
* command that moves it.
|
|
12
19
|
*/
|
|
20
|
+
|
|
21
|
+
export interface ActionRow {
|
|
22
|
+
apiName: string
|
|
23
|
+
published: boolean
|
|
24
|
+
/**
|
|
25
|
+
* `active`, `disabled`, `unbound`, `suspended`, `revoked`; `—` when not published;
|
|
26
|
+
* `left-off` on a local runtime that `local dev` did not give the Action to.
|
|
27
|
+
*/
|
|
28
|
+
deployment: string
|
|
29
|
+
available: boolean
|
|
30
|
+
/** The service's readiness reason when it cannot run (`ACTION_UNBOUND`), or null. */
|
|
31
|
+
reason: string | null
|
|
32
|
+
/** In words, with the next command; empty when it runs. */
|
|
33
|
+
whyNot: string
|
|
34
|
+
/**
|
|
35
|
+
* What it runs with that its owner should fix (the service's deployment
|
|
36
|
+
* warnings): a value it offers that the semantic type it writes refuses.
|
|
37
|
+
* It still runs; a request with that value is refused when submitted.
|
|
38
|
+
*/
|
|
39
|
+
warnings?: string[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** WHY NOT, in words, from the readiness the service reports. */
|
|
43
|
+
export function whyNot(apiName: string, entry: { published: boolean; deploymentStatus?: string; reason?: string | null; available?: boolean }): string {
|
|
44
|
+
if (!entry.published) return `not published yet — \`frontera blueprint publish\``
|
|
45
|
+
if (entry.available && (!entry.reason || entry.reason === 'ACTION_AVAILABLE' || entry.reason === 'ACTION_READY')) return ''
|
|
46
|
+
const code = entry.reason && entry.reason !== 'ACTION_AVAILABLE' ? ` (${entry.reason})` : ''
|
|
47
|
+
switch (entry.deploymentStatus) {
|
|
48
|
+
case 'unbound':
|
|
49
|
+
return `no write path${code} — \`frontera action deploy ${apiName}\`, then \`action review\``
|
|
50
|
+
case 'disabled':
|
|
51
|
+
return `ships switched off${code} — \`frontera action deploy ${apiName}\`, then \`action review\``
|
|
52
|
+
case 'suspended':
|
|
53
|
+
return `its deployment is suspended${code} — review it again with \`frontera action review\``
|
|
54
|
+
case 'revoked':
|
|
55
|
+
return `its deployment was revoked${code} — \`frontera action deploy ${apiName}\` again`
|
|
56
|
+
case 'active':
|
|
57
|
+
return entry.reason === 'ACTION_FORBIDDEN' ? `deployed, but no role may run it${code} — \`frontera action grant\`` : `deployed, but not available${code}`
|
|
58
|
+
default:
|
|
59
|
+
return `not deployed${code} — \`frontera action deploy ${apiName}\``
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function draftName(entry: Record<string, unknown>): string | null {
|
|
64
|
+
const nested = (entry.definition ?? entry.action) as { apiName?: unknown } | undefined
|
|
65
|
+
const name = nested?.apiName ?? entry.apiName
|
|
66
|
+
return typeof name === 'string' && name ? name : null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The rows: every published Action with its readiness, then each Action only the draft has. */
|
|
70
|
+
export async function actionRows(client: Pick<GovernedActionApi, 'listDeployableActions' | 'listDraftActions' | 'listPublishedActions' | 'publishedAction'>): Promise<ActionRow[]> {
|
|
71
|
+
const drafts = await client.listDraftActions().then((list) => (Array.isArray(list) ? list.flatMap((e) => draftName(e) ?? []) : [])).catch(() => [] as string[])
|
|
72
|
+
let deployable = await client.listDeployableActions()
|
|
73
|
+
if (!deployable) {
|
|
74
|
+
// A service without the list view: each Action the draft or the published list names, read one at a time.
|
|
75
|
+
const published = await client.listPublishedActions().catch(() => [])
|
|
76
|
+
const names = [...new Set([...published.map((p) => p.definition.apiName), ...drafts])]
|
|
77
|
+
deployable = []
|
|
78
|
+
for (const name of names) {
|
|
79
|
+
const one = await client.publishedAction(name).catch(() => null)
|
|
80
|
+
if (one) deployable.push({ definition: one.definition as DeployableActionEntry['definition'], deploymentStatus: one.deploymentStatus, availability: one.availability, reason: one.reason })
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const rows: ActionRow[] = deployable.map((e) => {
|
|
84
|
+
const available = e.availability === 'available' && e.executable !== false
|
|
85
|
+
const row = { published: true, deploymentStatus: e.deploymentStatus, reason: e.reason ?? null, available }
|
|
86
|
+
return {
|
|
87
|
+
apiName: e.definition.apiName,
|
|
88
|
+
published: true,
|
|
89
|
+
deployment: e.deploymentStatus ?? '—',
|
|
90
|
+
available,
|
|
91
|
+
reason: e.reason && e.reason !== 'ACTION_AVAILABLE' ? e.reason : null,
|
|
92
|
+
whyNot: whyNot(e.definition.apiName, row),
|
|
93
|
+
...(e.warnings?.length ? { warnings: e.warnings.map((w) => w.message) } : {}),
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
const listed = new Set(rows.map((r) => r.apiName))
|
|
97
|
+
for (const name of drafts) {
|
|
98
|
+
if (listed.has(name)) continue
|
|
99
|
+
rows.push({ apiName: name, published: false, deployment: '—', available: false, reason: null, whyNot: whyNot(name, { published: false }) })
|
|
100
|
+
}
|
|
101
|
+
return rows.sort((a, b) => a.apiName.localeCompare(b.apiName))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* On a project's own local runtime, each Action `local dev` left off it (the
|
|
106
|
+
* runtime refused its file, or it acts on a type left off here): the runtime
|
|
107
|
+
* never holds those, so the service's list cannot name them. From the last
|
|
108
|
+
* build's record, for the Blueprint files as they are now.
|
|
109
|
+
*/
|
|
110
|
+
export function leftOffActionRows(actions: BuildEntry['actions'] | null, listed: ReadonlySet<string>): ActionRow[] {
|
|
111
|
+
return Object.entries(actions ?? {}).flatMap(([apiName, a]) => {
|
|
112
|
+
if (a.status !== 'left-off' || listed.has(apiName)) return []
|
|
113
|
+
const why = (a.reason ?? 'local dev left it off').replace(/^the local runtime refused it: /, '')
|
|
114
|
+
return [{
|
|
115
|
+
apiName,
|
|
116
|
+
published: false,
|
|
117
|
+
deployment: 'left-off',
|
|
118
|
+
available: false,
|
|
119
|
+
reason: a.refused ? 'LOCAL_RUNTIME_REFUSED' : 'LOCAL_LEFT_OFF',
|
|
120
|
+
whyNot: a.refused
|
|
121
|
+
? `the local runtime refused its file: ${why} — fix blueprint/actions/, then \`frontera local dev --once\``
|
|
122
|
+
: `left off this runtime: ${why}`,
|
|
123
|
+
}]
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
13
127
|
export const actionList: Command = {
|
|
14
128
|
meta: {
|
|
15
129
|
noun: 'action',
|
|
16
130
|
verb: 'list',
|
|
17
131
|
args: [],
|
|
18
132
|
flags: {},
|
|
19
|
-
summary: 'List
|
|
20
|
-
examples: ['frontera action list'],
|
|
133
|
+
summary: 'List every Action, its deployment state and why it cannot run',
|
|
134
|
+
examples: ['frontera action list', 'frontera action list --json'],
|
|
21
135
|
},
|
|
22
136
|
|
|
23
137
|
async run(ctx) {
|
|
24
138
|
const client = new GovernedActionApi(ctx.apiUrl, ctx.token, ctx.workspaceId)
|
|
25
|
-
const actions = await client
|
|
139
|
+
const actions = await actionRows(client)
|
|
140
|
+
// The project's own local runtime: the Actions `local dev` left off it, which the runtime cannot list.
|
|
141
|
+
const local = localRuntimeAt(ctx.cwd, ctx.apiUrl)
|
|
142
|
+
if (local) {
|
|
143
|
+
actions.push(...leftOffActionRows(blueprintBuild(local.project.root).actions, new Set(actions.map((a) => a.apiName))))
|
|
144
|
+
actions.sort((a, b) => a.apiName.localeCompare(b.apiName))
|
|
145
|
+
}
|
|
26
146
|
|
|
27
147
|
if (actions.length === 0) {
|
|
28
148
|
return {
|
|
29
149
|
data: { actions: [] },
|
|
30
|
-
text: 'No
|
|
150
|
+
text: 'No Actions.\n Author one in blueprint/actions/, then `frontera blueprint apply` and `frontera blueprint publish`.',
|
|
31
151
|
}
|
|
32
152
|
}
|
|
33
153
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
deployment: entry.deploymentStatus ?? '—',
|
|
37
|
-
// Present only when it is NOT invocable, which is when it matters.
|
|
38
|
-
reason: entry.reason && entry.reason !== 'ACTION_AVAILABLE' ? entry.reason : '',
|
|
39
|
-
}))
|
|
40
|
-
|
|
154
|
+
// Each Action that runs with something its owner should fix, said under the table.
|
|
155
|
+
const warned = actions.flatMap((r) => (r.warnings ?? []).map((w) => ` warning: ${r.apiName}: ${w} It runs; its owner fixes the Action's contract.`))
|
|
41
156
|
return {
|
|
42
157
|
data: { actions },
|
|
43
|
-
text: table(
|
|
158
|
+
text: [table(
|
|
44
159
|
['ACTION', 'DEPLOYMENT', 'WHY NOT'],
|
|
45
|
-
|
|
46
|
-
),
|
|
160
|
+
actions.map((r) => [r.apiName, r.deployment, r.whyNot]),
|
|
161
|
+
), ...warned].join('\n'),
|
|
47
162
|
}
|
|
48
163
|
},
|
|
49
164
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GovernedActionApi } from '../../api/governed-action-api'
|
|
2
2
|
import { UsageError } from '../../errors'
|
|
3
|
+
import { localRuntimeAt } from '../../local/local-runtime-target'
|
|
3
4
|
import { table } from '../../table'
|
|
4
5
|
import { flagBool, flagString, type Command } from '../types'
|
|
5
6
|
|
|
@@ -51,18 +52,19 @@ export const actionRequests: Command = {
|
|
|
51
52
|
const request = await client.getRequest(requestId)
|
|
52
53
|
return {
|
|
53
54
|
data: request,
|
|
55
|
+
// Why it failed, first: the reason, the refusal it came from and its code, when the service says.
|
|
56
|
+
text: [failureLines((request as { failure?: unknown }).failure), table(
|
|
54
57
|
// Flattened rather than curated: the surface records provenance,
|
|
55
58
|
// attempts and failure detail under names that differ by adapter, and
|
|
56
59
|
// naming a subset here would hide exactly the field that explains the
|
|
57
60
|
// failure being investigated.
|
|
58
|
-
text: table(
|
|
59
61
|
['field', 'value'],
|
|
60
62
|
Object.entries(request).map(([k, v]) => [
|
|
61
63
|
k,
|
|
62
64
|
v === null || v === undefined ? '' : typeof v === 'object' ? JSON.stringify(v) : String(v),
|
|
63
65
|
]),
|
|
64
66
|
[undefined, 80],
|
|
65
|
-
),
|
|
67
|
+
)].filter(Boolean).join('\n\n'),
|
|
66
68
|
}
|
|
67
69
|
}
|
|
68
70
|
|
|
@@ -100,6 +102,8 @@ export const actionRequests: Command = {
|
|
|
100
102
|
|
|
101
103
|
if (page.requests.length === 0) {
|
|
102
104
|
const filtered = flagString(ctx, 'lifecycle')
|
|
105
|
+
// On the project's own local runtime a person is a local persona (`--as`), never a session token to fetch.
|
|
106
|
+
const local = localRuntimeAt(ctx.cwd, ctx.apiUrl) !== null
|
|
103
107
|
if (awaitingMe) {
|
|
104
108
|
return {
|
|
105
109
|
data: page,
|
|
@@ -108,8 +112,12 @@ export const actionRequests: Command = {
|
|
|
108
112
|
: 'Nothing is waiting for your decision.\n')
|
|
109
113
|
+ ' This lists the requests you could approve or reject right now: not ones\n'
|
|
110
114
|
+ ' you raised, already decided, or that wait on a step for an App role.\n'
|
|
111
|
-
+
|
|
112
|
-
|
|
115
|
+
+ (local
|
|
116
|
+
? ' It is a person\'s list: on a key it is always empty. On this local runtime,\n'
|
|
117
|
+
+ ' `frontera action requests --lifecycle awaiting_approval` lists what waits,\n'
|
|
118
|
+
+ ' and `frontera action approve <requestId> --as <approver>` decides it.'
|
|
119
|
+
: ' It is a person\'s list: on a key it is always empty. Set FRONTERA_TOKEN\n'
|
|
120
|
+
+ ' to your session token to read your own.')
|
|
113
121
|
+ incomplete,
|
|
114
122
|
}
|
|
115
123
|
}
|
|
@@ -119,8 +127,11 @@ export const actionRequests: Command = {
|
|
|
119
127
|
? `No requests with lifecycle "${filtered}".`
|
|
120
128
|
: 'No Action requests recorded.\n'
|
|
121
129
|
+ ' Nothing has invoked a deployed Action yet. Raise one with\n'
|
|
122
|
-
+
|
|
123
|
-
|
|
130
|
+
+ (local
|
|
131
|
+
? ' `frontera action submit <apiName> --as submitter`: on this local runtime a\n'
|
|
132
|
+
+ ' local persona acts as the person (`frontera local status` lists them).'
|
|
133
|
+
: ' `frontera action submit`, which acts as a person: set FRONTERA_TOKEN\n'
|
|
134
|
+
+ ' to your session token and FRONTERA_API_URL.'),
|
|
124
135
|
}
|
|
125
136
|
}
|
|
126
137
|
|
|
@@ -151,3 +162,20 @@ export const actionRequests: Command = {
|
|
|
151
162
|
}
|
|
152
163
|
},
|
|
153
164
|
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* A failed request's `failure` in words: its reason, the refusal it came from
|
|
168
|
+
* and its code, each on a line. Empty when the request has no failure.
|
|
169
|
+
*/
|
|
170
|
+
export function failureLines(failure: unknown): string {
|
|
171
|
+
if (!failure || typeof failure !== 'object') return ''
|
|
172
|
+
const f = failure as { reason?: unknown; refusal?: unknown; reasonCode?: unknown; businessOutcomeCode?: unknown }
|
|
173
|
+
const text = (v: unknown) => (typeof v === 'string' && v ? v : typeof v === 'object' && v ? JSON.stringify(v) : '')
|
|
174
|
+
const lines = [
|
|
175
|
+
text(f.reason) ? `Failed: ${text(f.reason)}` : 'Failed',
|
|
176
|
+
text(f.refusal) ? ` refusal: ${text(f.refusal)}` : '',
|
|
177
|
+
text(f.reasonCode) ? ` code: ${text(f.reasonCode)}` : '',
|
|
178
|
+
text(f.businessOutcomeCode) ? ` outcome: ${text(f.businessOutcomeCode)}` : '',
|
|
179
|
+
].filter(Boolean)
|
|
180
|
+
return lines.length > 1 || text(f.reason) ? lines.join('\n') : ''
|
|
181
|
+
}
|
|
@@ -3,6 +3,7 @@ import { CliError, UsageError } from '../../errors'
|
|
|
3
3
|
import { toEnvelope } from '../../exit'
|
|
4
4
|
import { parseInputFlag } from '../automation/run'
|
|
5
5
|
import { flagString, type Command } from '../types'
|
|
6
|
+
import { criteriaRefusal } from './criteria-refusal'
|
|
6
7
|
import { requestSummary } from './request-summary'
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -55,11 +56,16 @@ export const actionSubmit: Command = {
|
|
|
55
56
|
'subject-version': 'string',
|
|
56
57
|
reason: 'string',
|
|
57
58
|
'idempotency-key': 'string',
|
|
59
|
+
as: 'string',
|
|
58
60
|
},
|
|
59
61
|
summary: 'Submit a request to a deployed Action',
|
|
62
|
+
details: [
|
|
63
|
+
'On the local runtime, `--as <persona>` submits as a local persona (`--as submitter`), so a later step can be decided by another one. Refused anywhere else.',
|
|
64
|
+
],
|
|
60
65
|
examples: [
|
|
61
66
|
'frontera action submit createTicket title=Broken priority=2',
|
|
62
67
|
'frontera action submit escalateTicket --subject TKT-1 --subject-version 0',
|
|
68
|
+
'frontera action submit escalateTicket id=TKT-1 expectedVersion=0',
|
|
63
69
|
'frontera action submit createTicket --input @params.json',
|
|
64
70
|
'frontera action submit createTicket --idempotency-key retry-2026-09-26-01',
|
|
65
71
|
],
|
|
@@ -72,11 +78,8 @@ export const actionSubmit: Command = {
|
|
|
72
78
|
}
|
|
73
79
|
const subject = flagString(ctx, 'subject')
|
|
74
80
|
const version = flagString(ctx, 'subject-version')
|
|
75
|
-
if (
|
|
76
|
-
throw new UsageError(
|
|
77
|
-
'--subject and --subject-version go together',
|
|
78
|
-
`pass both: frontera action submit ${apiName} --subject <objectId> --subject-version <version>`,
|
|
79
|
-
)
|
|
81
|
+
if (version !== undefined && subject === undefined) {
|
|
82
|
+
throw new UsageError('--subject-version names the version of a --subject', `pass the record too: frontera action submit ${apiName} --subject <objectId> --subject-version <version>`)
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
const client = new GovernedActionApi(ctx.apiUrl, ctx.token, ctx.workspaceId)
|
|
@@ -94,7 +97,13 @@ export const actionSubmit: Command = {
|
|
|
94
97
|
...(await parseInputFlag(ctx)),
|
|
95
98
|
...Object.fromEntries(pairs.map((raw) => pair(raw, stringInputs(action)))),
|
|
96
99
|
}
|
|
97
|
-
const
|
|
100
|
+
const existing = action.subject?.mode === 'existing' && action.subject.objectTypeId
|
|
101
|
+
// An edit names its record either way: `--subject` (and `--subject-version`), or the key
|
|
102
|
+
// and `expectedVersion` inputs. The platform fills whichever half is left out from the
|
|
103
|
+
// other, so with `--subject` the key and version inputs are not missing; the service
|
|
104
|
+
// names any input that still is.
|
|
105
|
+
const filled = (name: string) => existing && subject !== undefined && (name === 'expectedVersion' || /^(id|.+(Id|Key|Pk))$/.test(name))
|
|
106
|
+
const missing = (action.inputSchema?.properties?.input?.required ?? []).filter((name) => !(name in input) && !filled(name))
|
|
98
107
|
if (missing.length > 0) {
|
|
99
108
|
throw new UsageError(
|
|
100
109
|
`"${apiName}" needs ${missing.join(', ')}`,
|
|
@@ -103,11 +112,16 @@ export const actionSubmit: Command = {
|
|
|
103
112
|
}
|
|
104
113
|
|
|
105
114
|
const invocation: Record<string, unknown> = { input }
|
|
106
|
-
|
|
107
|
-
|
|
115
|
+
if (existing && subject !== undefined && version === undefined && !('expectedVersion' in input)) {
|
|
116
|
+
throw new UsageError(
|
|
117
|
+
`"${apiName}" edits a record, so it says the version the change was decided on`,
|
|
118
|
+
`frontera action submit ${apiName} --subject ${subject} --subject-version <version> (\`blueprint instance\` shows it as _meta.recordVersion)`,
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
if (existing && subject === undefined && !('expectedVersion' in input)) {
|
|
108
122
|
throw new UsageError(
|
|
109
123
|
`"${apiName}" acts on an existing object`,
|
|
110
|
-
`name it: frontera action submit ${apiName} --subject <objectId> --subject-version <version>`,
|
|
124
|
+
`name it and its version: frontera action submit ${apiName} --subject <objectId> --subject-version <version>, or pass its key input and expectedVersion=<version>`,
|
|
111
125
|
)
|
|
112
126
|
}
|
|
113
127
|
if (!existing && subject !== undefined) {
|
|
@@ -116,10 +130,10 @@ export const actionSubmit: Command = {
|
|
|
116
130
|
`drop --subject and --subject-version: frontera action submit ${apiName} key=value…`,
|
|
117
131
|
)
|
|
118
132
|
}
|
|
119
|
-
if (existing) {
|
|
133
|
+
if (existing && subject !== undefined) {
|
|
120
134
|
// The object type is the Action's, not the caller's to choose.
|
|
121
135
|
invocation.subjectRef = { objectTypeId: action.subject!.objectTypeId, objectId: subject }
|
|
122
|
-
invocation.expectedSubjectVersion = version
|
|
136
|
+
if (version !== undefined) invocation.expectedSubjectVersion = version
|
|
123
137
|
}
|
|
124
138
|
const reason = flagString(ctx, 'reason')
|
|
125
139
|
if (reason !== undefined) invocation.reason = reason
|
|
@@ -146,8 +160,9 @@ export const actionSubmit: Command = {
|
|
|
146
160
|
} catch (err) {
|
|
147
161
|
// Only where the outcome is unknown. A refusal the service answered
|
|
148
162
|
// (400, 403, 404, 409) is replayed identically under the same key, so
|
|
149
|
-
// naming the retry there would invite a loop; it is rethrown as is
|
|
150
|
-
|
|
163
|
+
// naming the retry there would invite a loop; it is rethrown as is —
|
|
164
|
+
// a submission-criteria refusal with each criterion's message, not only its code.
|
|
165
|
+
if (!transient(err)) throw criteriaRefusal(err, apiName, ctx.cwd) ?? err
|
|
151
166
|
const envelope = toEnvelope(err)
|
|
152
167
|
throw new CliError(envelope.message, {
|
|
153
168
|
code: err instanceof CliError ? envelope.code : 'FAILURE',
|
|
@@ -79,7 +79,7 @@ export async function agentDraftChanges(api: PlatformApi, id: string): Promise<A
|
|
|
79
79
|
(k) => stableStringify(draftSections[k]) !== stableStringify(liveSnapshot[k]),
|
|
80
80
|
)
|
|
81
81
|
: []),
|
|
82
|
-
].sort()
|
|
82
|
+
].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
83
83
|
|
|
84
84
|
return { hasDraft: true, published, revision: draft.revision, changed }
|
|
85
85
|
}
|
|
@@ -152,9 +152,9 @@ const create: Command = {
|
|
|
152
152
|
data: agent,
|
|
153
153
|
text: [
|
|
154
154
|
`created ${slug}${agent.id ? ` ${agent.id}` : ''}`,
|
|
155
|
-
// Said plainly
|
|
156
|
-
//
|
|
157
|
-
` it is in \`configuring\`
|
|
155
|
+
// Said plainly: a caller who runs `agent list` next sees it as a draft, not
|
|
156
|
+
// among the published ones, and should not conclude the create failed.
|
|
157
|
+
` it is in \`configuring\` until published: \`frontera agent list\` shows it as \`draft only\``,
|
|
158
158
|
` next: frontera agent apply ${slug} -f <file> then frontera agent publish ${slug}`,
|
|
159
159
|
].join('\n'),
|
|
160
160
|
}
|
|
@@ -171,7 +171,20 @@ const list: Command = {
|
|
|
171
171
|
examples: ['frontera agent list', 'frontera agent list --json'],
|
|
172
172
|
},
|
|
173
173
|
async run(ctx) {
|
|
174
|
-
const
|
|
174
|
+
const client = api(ctx)
|
|
175
|
+
const published = (await client.agents()) as AgentRow[]
|
|
176
|
+
// An Agent never published (one created and only applied, as every Agent on a local runtime is)
|
|
177
|
+
// is not in that list; the workspace's full listing adds it, marked as a draft. A platform
|
|
178
|
+
// without that listing lists the published ones, as before.
|
|
179
|
+
const all = ((await client.allAgents().catch(() => [])) ?? []) as AgentRow[]
|
|
180
|
+
const known = new Set(published.map((a) => a.id))
|
|
181
|
+
const drafts = all.filter((a) => a.lifecycleStatus === 'configuring' && !known.has(a.id) && !String(a.agentId ?? '').startsWith('preview_'))
|
|
182
|
+
const rows = [...published, ...drafts]
|
|
183
|
+
// An archived Agent is in the published listing: said as archived, not as live. One with no published
|
|
184
|
+
// version is a draft, not published, even when it reads active: restore now brings a draft-only Agent
|
|
185
|
+
// back `configuring`, but one restored before that fix still reads active.
|
|
186
|
+
const state = (a: AgentRow) => (a.lifecycleStatus === 'archived' ? 'archived' : a.lifecycleStatus === 'configuring' || (a.currentVersionId === null && a.lifecycleStatus === 'active') ? 'draft only' : 'published')
|
|
187
|
+
const withState = drafts.length > 0 || rows.some((a) => state(a) !== 'published')
|
|
175
188
|
return {
|
|
176
189
|
data: rows,
|
|
177
190
|
text:
|
|
@@ -179,8 +192,8 @@ const list: Command = {
|
|
|
179
192
|
? 'No agents in this workspace.'
|
|
180
193
|
: // Either ID or SLUG works wherever an agent is named.
|
|
181
194
|
table(
|
|
182
|
-
['id', 'slug', 'name'],
|
|
183
|
-
rows.map((a) => [a.id ?? '?', a.agentId ?? '', a.name ?? '']),
|
|
195
|
+
withState ? ['id', 'slug', 'name', 'state'] : ['id', 'slug', 'name'],
|
|
196
|
+
rows.map((a) => [a.id ?? '?', a.agentId ?? '', a.name ?? '', ...(withState ? [state(a)] : [])]),
|
|
184
197
|
),
|
|
185
198
|
}
|
|
186
199
|
},
|
|
@@ -230,7 +243,16 @@ const get: Command = {
|
|
|
230
243
|
const identity = (row ?? {}) as {
|
|
231
244
|
id?: string
|
|
232
245
|
agentId?: string
|
|
233
|
-
|
|
246
|
+
currentVersionId?: string | null
|
|
247
|
+
currentVersion?: { versionNumber?: number } | null
|
|
248
|
+
draft?: unknown
|
|
249
|
+
}
|
|
250
|
+
// Never published: the composition is the empty published side, which reads as an Agent with nothing.
|
|
251
|
+
if (row && !identity.currentVersionId && !identity.currentVersion) {
|
|
252
|
+
throw new CliError(`${identity.agentId ?? t.ref} has no published version yet: only its draft exists`, {
|
|
253
|
+
code: 'NOT_FOUND',
|
|
254
|
+
hint: `read the draft: \`frontera agent get ${identity.agentId ?? t.ref} --draft\`${identity.draft ? '' : ' (`frontera agent list` shows its state)'}`,
|
|
255
|
+
})
|
|
234
256
|
}
|
|
235
257
|
return {
|
|
236
258
|
data: snapshot,
|
|
@@ -388,6 +410,44 @@ const discard: Command = {
|
|
|
388
410
|
},
|
|
389
411
|
}
|
|
390
412
|
|
|
413
|
+
const archive: Command = {
|
|
414
|
+
meta: {
|
|
415
|
+
noun: 'agent',
|
|
416
|
+
verb: 'archive',
|
|
417
|
+
args: [{ name: 'agent', required: true, description: 'agent id or slug' }],
|
|
418
|
+
flags: {},
|
|
419
|
+
summary: 'Archive an agent: it leaves service with its versions and conversations kept (`agent restore` puts it back)',
|
|
420
|
+
examples: ['frontera agent archive assistant'],
|
|
421
|
+
},
|
|
422
|
+
async run(ctx) {
|
|
423
|
+
const t = await target(ctx, 'archive')
|
|
424
|
+
const row = (await t.api.agent(t.id).catch(() => null)) as AgentRow | null
|
|
425
|
+
if (row?.lifecycleStatus === 'archived') return { data: { archived: true, agent: t.ref, already: true }, text: `${t.ref} is already archived.` }
|
|
426
|
+
await t.api.archiveAgent(t.id)
|
|
427
|
+
return {
|
|
428
|
+
data: { archived: true, agent: t.ref },
|
|
429
|
+
text: [`Archived ${t.ref}: no chat, channel or workflow reaches it now; its versions and conversations are kept.`, ` its slug stays taken; \`frontera agent restore ${t.ref}\` puts it back`].join('\n'),
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const restore: Command = {
|
|
435
|
+
meta: {
|
|
436
|
+
noun: 'agent',
|
|
437
|
+
verb: 'restore',
|
|
438
|
+
args: [{ name: 'agent', required: true, description: 'agent id or slug' }],
|
|
439
|
+
flags: {},
|
|
440
|
+
summary: 'Put an archived agent back into service, as it was',
|
|
441
|
+
examples: ['frontera agent restore assistant'],
|
|
442
|
+
},
|
|
443
|
+
async run(ctx) {
|
|
444
|
+
const t = await target(ctx, 'restore')
|
|
445
|
+
const result = (await t.api.restoreAgent(t.id)) as { restored?: boolean; lifecycleStatus?: string } | null
|
|
446
|
+
if (result && result.restored === false) return { data: { restored: false, agent: t.ref }, text: `${t.ref} isn't archived${result.lifecycleStatus ? ` (it is ${result.lifecycleStatus === 'configuring' ? 'a draft' : result.lifecycleStatus})` : ''}: nothing to restore.` }
|
|
447
|
+
return { data: { restored: true, agent: t.ref }, text: `Restored ${t.ref}.` }
|
|
448
|
+
},
|
|
449
|
+
}
|
|
450
|
+
|
|
391
451
|
const versions: Command = {
|
|
392
452
|
meta: {
|
|
393
453
|
noun: 'agent',
|
|
@@ -555,6 +615,8 @@ export const agentCommands: Command[] = [
|
|
|
555
615
|
publish,
|
|
556
616
|
diff,
|
|
557
617
|
discard,
|
|
618
|
+
archive,
|
|
619
|
+
restore,
|
|
558
620
|
versions,
|
|
559
621
|
revert,
|
|
560
622
|
skillAttach,
|
|
@@ -8,6 +8,8 @@ export interface AgentRow {
|
|
|
8
8
|
name?: string
|
|
9
9
|
kind?: string
|
|
10
10
|
lifecycleStatus?: string
|
|
11
|
+
/** The published version; null for an Agent never published. */
|
|
12
|
+
currentVersionId?: string | null
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { AppsApi, type DeploymentCheck } from '../../api/apps-api'
|
|
2
|
+
import { UsageError } from '../../errors'
|
|
3
|
+
import { table } from '../../table'
|
|
4
|
+
import { projectApp, requireProjectFrom } from './shared'
|
|
5
|
+
import type { Command } from '../types'
|
|
6
|
+
|
|
7
|
+
/** The checks a deployment passes in the App's workspace, with which ones the workspace requires. */
|
|
8
|
+
export function renderChecks(deployment: string, checks: DeploymentCheck[]): string {
|
|
9
|
+
if (!checks.length) return `${deployment}: no checks recorded`
|
|
10
|
+
return table(
|
|
11
|
+
['CHECK', 'STATUS', 'REQUIRED', 'DETAIL'],
|
|
12
|
+
checks.map((c) => [c.label, c.status, c.required ? 'yes' : '-', c.detail]),
|
|
13
|
+
[undefined, undefined, undefined, 72],
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const appChecks: Command = {
|
|
18
|
+
meta: {
|
|
19
|
+
noun: 'app',
|
|
20
|
+
verb: 'checks',
|
|
21
|
+
args: [
|
|
22
|
+
{
|
|
23
|
+
name: 'deployment',
|
|
24
|
+
required: true,
|
|
25
|
+
description: 'the deployment id or release label, from `frontera app deployments`',
|
|
26
|
+
producer: 'frontera app deployments — then `frontera app checks <deployment>`',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
flags: {},
|
|
30
|
+
summary: 'Show the build, render, contract and accessibility checks for a deployment',
|
|
31
|
+
examples: ['frontera app checks dpl_01JABCDEF', 'frontera app checks 1.4.0 --json'],
|
|
32
|
+
needsProject: true,
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
async run(ctx) {
|
|
36
|
+
const deployment = ctx.positional[0]
|
|
37
|
+
if (!deployment) throw new UsageError('say which deployment', 'frontera app checks <deployment id or label>')
|
|
38
|
+
const project = requireProjectFrom(ctx)
|
|
39
|
+
const client = new AppsApi(ctx.apiUrl, ctx.token)
|
|
40
|
+
const app = await projectApp(client, project)
|
|
41
|
+
const res = await client.checks(app.id, deployment)
|
|
42
|
+
return { data: res, text: renderChecks(res.deployment, res.checks) }
|
|
43
|
+
},
|
|
44
|
+
}
|