@kora-platform/cli 0.7.0-rc1 → 0.8.0-rc2
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 +21 -0
- package/dist/api-client.d.ts +250 -93
- package/dist/api-client.js +187 -163
- package/dist/api-types.d.ts +280 -162
- package/dist/artifact-api-client.d.ts +28 -1
- package/dist/artifact-api-client.js +33 -0
- package/dist/artifact-commands.d.ts +5 -0
- package/dist/artifact-commands.js +172 -1
- package/dist/audit-commands.d.ts +12 -0
- package/dist/audit-commands.js +74 -0
- package/dist/auth-commands.d.ts +1 -0
- package/dist/auth-commands.js +116 -29
- package/dist/command-builders.d.ts +1 -0
- package/dist/command-builders.js +1 -0
- package/dist/command-groups.js +10 -12
- package/dist/command-registry.js +548 -243
- package/dist/commands.js +652 -602
- package/dist/environment-context.d.ts +9 -0
- package/dist/environment-context.js +32 -0
- package/dist/{integration-commands.d.ts → extension-commands.d.ts} +3 -2
- package/dist/extension-commands.js +446 -0
- package/dist/files.d.ts +33 -0
- package/dist/files.js +247 -12
- package/dist/format.d.ts +5 -0
- package/dist/format.js +78 -1
- package/dist/runner.js +14 -5
- package/dist/schema-registry-data.d.ts +272 -569
- package/dist/schema-registry-data.js +307 -700
- package/dist/session.d.ts +1 -0
- package/dist/transport.d.ts +10 -0
- package/dist/transport.js +22 -0
- package/dist/types.d.ts +2 -1
- package/dist/workspace-source.d.ts +1 -0
- package/dist/workspace-source.js +13 -0
- package/package.json +2 -1
- package/dist/integration-api-client.d.ts +0 -29
- package/dist/integration-api-client.js +0 -50
- package/dist/integration-commands.js +0 -208
package/dist/commands.js
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname, resolve } from "node:path";
|
|
3
1
|
import { renderCompletionScript, resolveCompletionCandidates } from "./completion.js";
|
|
4
|
-
import { executeAuthLogin, executeAuthLogout, executeAuthWhoami } from "./auth-commands.js";
|
|
2
|
+
import { executeAuthLogin, executeAuthLogout, executeAuthSignup, executeAuthWhoami } from "./auth-commands.js";
|
|
5
3
|
import { getCliSchema, listCliSchemas } from "./schema-registry.js";
|
|
6
4
|
import { createPlatformApiClient } from "./api-client.js";
|
|
7
5
|
import { authProblem, genericProblem, notFoundProblem, usageProblem } from "./cli-errors.js";
|
|
8
6
|
import { readOptionalNumberFlag, readOptionalStringFlag, readRequiredStringFlag } from "./command-flags.js";
|
|
9
|
-
import { executeArtifactDownload, executeArtifactList, executeArtifactRead, executeArtifactUpload } from "./artifact-commands.js";
|
|
10
|
-
import {
|
|
7
|
+
import { executeArtifactDownload, executeArtifactArchive, executeArtifactInspect, executeArtifactInventory, executeArtifactList, executeArtifactPurge, executeArtifactRead, executeArtifactRestore, executeArtifactUpload } from "./artifact-commands.js";
|
|
8
|
+
import { executeAudit } from "./audit-commands.js";
|
|
9
|
+
import { isZipArchivePath, readArchiveBytes, readImportEntries, readJsonInputSpecifier, readWorkspaceTestEntries, readTextInputSpecifier, parseEnvFile, writeReleaseSourceFiles } from "./files.js";
|
|
11
10
|
import { renderDiffSummary, renderKeyValue, renderPrettyJson, renderSuccess, renderTable } from "./format.js";
|
|
12
11
|
import { buildTaskDetailPresentation } from "./task-detail.js";
|
|
13
12
|
import { confirmDestructive } from "./interaction.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { isInteractive } from "./prompts.js";
|
|
13
|
+
import { executeExtensions } from "./extension-commands.js";
|
|
14
|
+
import { readCliEnvironmentFlag, readRequiredCliEnvironmentFlag, resolveCliEnvironment } from "./environment-context.js";
|
|
17
15
|
export async function executeParsedCommand(parsed, context) {
|
|
18
16
|
const api = createPlatformApiClient({
|
|
19
17
|
sessionStore: context.sessionStore
|
|
@@ -21,6 +19,8 @@ export async function executeParsedCommand(parsed, context) {
|
|
|
21
19
|
switch (parsed.definition.id) {
|
|
22
20
|
case "auth.login":
|
|
23
21
|
return executeAuthLogin(parsed, context, api);
|
|
22
|
+
case "auth.signup":
|
|
23
|
+
return executeAuthSignup(parsed, context, api);
|
|
24
24
|
case "auth.logout":
|
|
25
25
|
return executeAuthLogout(context, api);
|
|
26
26
|
case "auth.whoami":
|
|
@@ -41,10 +41,6 @@ export async function executeParsedCommand(parsed, context) {
|
|
|
41
41
|
return executeOrgSelect(parsed, context, api);
|
|
42
42
|
case "org.settings.get":
|
|
43
43
|
return executeOrgSettingsGet(parsed, context, api);
|
|
44
|
-
case "org.import":
|
|
45
|
-
return executeOrgImport(parsed, context, api);
|
|
46
|
-
case "org.bundle.show":
|
|
47
|
-
return executeOrgBundleShow(parsed, context, api);
|
|
48
44
|
case "org.reset":
|
|
49
45
|
return executeOrgReset(parsed, context, api);
|
|
50
46
|
case "status":
|
|
@@ -65,24 +61,29 @@ export async function executeParsedCommand(parsed, context) {
|
|
|
65
61
|
return executeWorkflowContext(parsed, context, api);
|
|
66
62
|
case "workflow.start":
|
|
67
63
|
return executeWorkflowStart(parsed, context, api);
|
|
64
|
+
case "test.node":
|
|
65
|
+
return executeWorkflowNodeTest(parsed, context, api);
|
|
68
66
|
case "release.list":
|
|
69
67
|
return executeReleaseList(parsed, context, api);
|
|
70
68
|
case "release.get":
|
|
71
69
|
return executeReleaseGet(parsed, context, api);
|
|
72
|
-
case "release.
|
|
73
|
-
return
|
|
74
|
-
case "release.
|
|
75
|
-
return
|
|
76
|
-
case "release.
|
|
77
|
-
return
|
|
78
|
-
case "
|
|
79
|
-
|
|
80
|
-
case "
|
|
81
|
-
|
|
82
|
-
case "
|
|
83
|
-
|
|
84
|
-
case "
|
|
85
|
-
return
|
|
70
|
+
case "release.create":
|
|
71
|
+
return executeReleaseCreate(parsed, context, api);
|
|
72
|
+
case "release.validate":
|
|
73
|
+
return executeReleaseValidate(parsed, context, api);
|
|
74
|
+
case "release.source":
|
|
75
|
+
return executeReleaseSource(parsed, context, api);
|
|
76
|
+
case "environment.list":
|
|
77
|
+
case "environment.get":
|
|
78
|
+
case "environment.create":
|
|
79
|
+
case "environment.rename":
|
|
80
|
+
case "environment.archive":
|
|
81
|
+
case "environment.deploy":
|
|
82
|
+
case "environment.undeploy":
|
|
83
|
+
return executeEnvironment(parsed, context, api);
|
|
84
|
+
case "deployment.list":
|
|
85
|
+
case "deployment.get":
|
|
86
|
+
return executeDeployment(parsed, context, api);
|
|
86
87
|
case "run.list":
|
|
87
88
|
return executeRunList(parsed, context, api);
|
|
88
89
|
case "run.get":
|
|
@@ -93,14 +94,27 @@ export async function executeParsedCommand(parsed, context) {
|
|
|
93
94
|
return executeRunSteps(parsed, context, api);
|
|
94
95
|
case "run.abort":
|
|
95
96
|
return executeRunAbort(parsed, context, api);
|
|
97
|
+
case "audit.list":
|
|
98
|
+
case "audit.get":
|
|
99
|
+
return executeAudit(parsed, context, api, resolveOrgScope);
|
|
96
100
|
case "artifact.upload":
|
|
97
101
|
return executeArtifactUpload(parsed, context, api, resolveOrgScope);
|
|
98
102
|
case "artifact.list":
|
|
99
103
|
return executeArtifactList(parsed, context, api, resolveOrgScope);
|
|
104
|
+
case "artifact.inventory":
|
|
105
|
+
return executeArtifactInventory(parsed, context, api, resolveOrgScope);
|
|
106
|
+
case "artifact.inspect":
|
|
107
|
+
return executeArtifactInspect(parsed, context, api, resolveOrgScope);
|
|
100
108
|
case "artifact.download":
|
|
101
109
|
return executeArtifactDownload(parsed, context, api, resolveOrgScope);
|
|
102
110
|
case "artifact.read":
|
|
103
111
|
return executeArtifactRead(parsed, context, api, resolveOrgScope);
|
|
112
|
+
case "artifact.archive":
|
|
113
|
+
return executeArtifactArchive(parsed, context, api, resolveOrgScope);
|
|
114
|
+
case "artifact.restore":
|
|
115
|
+
return executeArtifactRestore(parsed, context, api, resolveOrgScope);
|
|
116
|
+
case "artifact.purge":
|
|
117
|
+
return executeArtifactPurge(parsed, context, api, resolveOrgScope);
|
|
104
118
|
case "task.list":
|
|
105
119
|
return executeTaskList(parsed, context, api);
|
|
106
120
|
case "task.get":
|
|
@@ -119,10 +133,6 @@ export async function executeParsedCommand(parsed, context) {
|
|
|
119
133
|
case "org-model.capabilities.get":
|
|
120
134
|
case "org-model.operations.list":
|
|
121
135
|
case "org-model.operations.get":
|
|
122
|
-
case "org-model.connectors.list":
|
|
123
|
-
case "org-model.connectors.get":
|
|
124
|
-
case "org-model.mcp-servers.list":
|
|
125
|
-
case "org-model.mcp-servers.get":
|
|
126
136
|
return executeOrgModel(parsed, context, api);
|
|
127
137
|
case "access.members.list":
|
|
128
138
|
case "access.members.get":
|
|
@@ -137,32 +147,21 @@ export async function executeParsedCommand(parsed, context) {
|
|
|
137
147
|
case "access.api-keys.create":
|
|
138
148
|
case "access.api-keys.revoke":
|
|
139
149
|
return executeAccess(parsed, context, api);
|
|
140
|
-
case "
|
|
141
|
-
case "
|
|
142
|
-
case "
|
|
143
|
-
case "
|
|
144
|
-
case "
|
|
145
|
-
case "
|
|
146
|
-
|
|
147
|
-
case "
|
|
148
|
-
case "
|
|
149
|
-
case "
|
|
150
|
-
case "
|
|
151
|
-
case "
|
|
152
|
-
case "
|
|
153
|
-
case "
|
|
154
|
-
|
|
155
|
-
case "mcp.oauth-status":
|
|
156
|
-
case "mcp.disconnect":
|
|
157
|
-
return executeMcp(parsed, context, api);
|
|
158
|
-
case "skills.list":
|
|
159
|
-
case "skills.get":
|
|
160
|
-
case "skills.create":
|
|
161
|
-
case "skills.upload":
|
|
162
|
-
case "skills.download":
|
|
163
|
-
case "skills.update":
|
|
164
|
-
case "skills.delete":
|
|
165
|
-
return executeSkills(parsed, context, api);
|
|
150
|
+
case "extensions.export":
|
|
151
|
+
case "extensions.validate":
|
|
152
|
+
case "extensions.publish":
|
|
153
|
+
case "extensions.install":
|
|
154
|
+
case "extensions.grant":
|
|
155
|
+
case "extensions.update":
|
|
156
|
+
case "extensions.built-ins.list":
|
|
157
|
+
case "extensions.built-ins.install":
|
|
158
|
+
case "extensions.list":
|
|
159
|
+
case "extensions.inspect":
|
|
160
|
+
case "extensions.detail":
|
|
161
|
+
case "extensions.disable":
|
|
162
|
+
case "extensions.enable":
|
|
163
|
+
case "extensions.delete":
|
|
164
|
+
return executeExtensions(parsed, context, api, resolveOrgScope);
|
|
166
165
|
case "env.list":
|
|
167
166
|
case "env.get":
|
|
168
167
|
case "env.replace":
|
|
@@ -176,12 +175,6 @@ export async function executeParsedCommand(parsed, context) {
|
|
|
176
175
|
case "chat.sessions.list":
|
|
177
176
|
case "chat.sessions.get":
|
|
178
177
|
case "chat.sessions.delete":
|
|
179
|
-
case "chat.changes":
|
|
180
|
-
case "chat.validation":
|
|
181
|
-
case "chat.draft.get":
|
|
182
|
-
case "chat.draft.apply":
|
|
183
|
-
case "chat.draft.refresh":
|
|
184
|
-
case "chat.workspace-state":
|
|
185
178
|
return executeChat(parsed, context, api);
|
|
186
179
|
case "completion.bash":
|
|
187
180
|
case "completion.zsh":
|
|
@@ -191,6 +184,10 @@ export async function executeParsedCommand(parsed, context) {
|
|
|
191
184
|
return executeSchemaList();
|
|
192
185
|
case "schema.get":
|
|
193
186
|
return executeSchemaGet(parsed);
|
|
187
|
+
case "admin.usage.summary":
|
|
188
|
+
case "admin.usage.workflows":
|
|
189
|
+
case "admin.usage.tools":
|
|
190
|
+
case "admin.usage.events":
|
|
194
191
|
case "admin.org.deleted.list":
|
|
195
192
|
case "admin.org.deleted.get":
|
|
196
193
|
case "admin.org.deleted.restore":
|
|
@@ -360,35 +357,13 @@ async function executeOrgSettingsGet(parsed, context, api) {
|
|
|
360
357
|
meta: { command: "org settings get", orgId: org.id }
|
|
361
358
|
};
|
|
362
359
|
}
|
|
363
|
-
async function executeOrgImport(parsed, context, api) {
|
|
364
|
-
const { org, session } = await resolveOrgScope(parsed, context, api);
|
|
365
|
-
const files = await readImportEntries(readRequiredArg(parsed, "path"));
|
|
366
|
-
const data = await api.importProject(session, org.id, { files });
|
|
367
|
-
return {
|
|
368
|
-
data,
|
|
369
|
-
human: renderSuccess(`Imported ${files.length} files into ${org.slug}.`),
|
|
370
|
-
kind: "authoring_import_apply",
|
|
371
|
-
meta: { command: "org import", orgId: org.id }
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
async function executeOrgBundleShow(parsed, context, api) {
|
|
375
|
-
const { org, session } = await resolveOrgScope(parsed, context, api);
|
|
376
|
-
const releaseId = readOptionalStringFlag(parsed, "release");
|
|
377
|
-
const data = await api.getProjectWorkspace(session, org.id, releaseId);
|
|
378
|
-
return {
|
|
379
|
-
data,
|
|
380
|
-
human: renderPrettyJson(data.project),
|
|
381
|
-
kind: "authoring_bundle_show",
|
|
382
|
-
meta: { command: "org bundle show", orgId: org.id }
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
360
|
async function executeOrgReset(parsed, context, api) {
|
|
386
361
|
const { org, session } = await resolveOrgScope(parsed, context, api);
|
|
387
|
-
await confirmDestructive(parsed, context, `Reset
|
|
362
|
+
await confirmDestructive(parsed, context, `Reset release, runtime, project, and chat state for ${org.slug}?`);
|
|
388
363
|
await api.resetOrganizationProject(session, org.id);
|
|
389
364
|
return {
|
|
390
365
|
data: { reset: true },
|
|
391
|
-
human: renderSuccess(`Reset
|
|
366
|
+
human: renderSuccess(`Reset project state for ${org.slug}.`),
|
|
392
367
|
kind: "authoring_project_reset",
|
|
393
368
|
meta: { command: "org reset", orgId: org.id }
|
|
394
369
|
};
|
|
@@ -399,8 +374,6 @@ async function executeStatus(parsed, context, api) {
|
|
|
399
374
|
return {
|
|
400
375
|
data,
|
|
401
376
|
human: renderKeyValue([
|
|
402
|
-
{ label: "Draft changes", value: data.overview.draftChangeCount },
|
|
403
|
-
{ label: "Draft has changes", value: data.overview.draftHasChanges },
|
|
404
377
|
{ label: "Pending tasks", value: data.overview.pendingTaskCount },
|
|
405
378
|
{ label: "Runs in flight", value: data.overview.runsInFlight },
|
|
406
379
|
{ label: "Workflow count", value: data.overview.workflowCount }
|
|
@@ -411,12 +384,14 @@ async function executeStatus(parsed, context, api) {
|
|
|
411
384
|
}
|
|
412
385
|
async function executeActivityList(parsed, context, api) {
|
|
413
386
|
const { org, session } = await resolveOrgScope(parsed, context, api);
|
|
387
|
+
const { environment } = resolveCliEnvironment(parsed, session);
|
|
414
388
|
const focusType = readOptionalStringFlag(parsed, "focus-type");
|
|
415
389
|
const focusValue = readOptionalStringFlag(parsed, "focus-value");
|
|
416
390
|
if ((focusType && !focusValue) || (!focusType && focusValue)) {
|
|
417
391
|
throw usageProblem("Use --focus-type and --focus-value together.", "activity list");
|
|
418
392
|
}
|
|
419
393
|
const data = await api.getActivity(session, org.id, {
|
|
394
|
+
environment,
|
|
420
395
|
...(focusType ? { focusType: focusType } : {}),
|
|
421
396
|
...(focusValue ? { focusValue } : {}),
|
|
422
397
|
...optionalNumberValue("limit", readOptionalNumberFlag(parsed, "limit")),
|
|
@@ -429,11 +404,12 @@ async function executeActivityList(parsed, context, api) {
|
|
|
429
404
|
{ key: "occurredAt", label: "Occurred" },
|
|
430
405
|
{ key: "kind", label: "Kind" },
|
|
431
406
|
{ key: "status", label: "Status" },
|
|
407
|
+
{ key: "environmentKey", label: "Environment" },
|
|
432
408
|
{ key: "processName", label: "Workflow" },
|
|
433
409
|
{ key: "summary", label: "Summary" }
|
|
434
410
|
]),
|
|
435
411
|
kind: "runtime_activity_list",
|
|
436
|
-
meta: { command: "activity list", orgId: org.id }
|
|
412
|
+
meta: { command: "activity list", environment, orgId: org.id }
|
|
437
413
|
};
|
|
438
414
|
}
|
|
439
415
|
async function executeActivityOptions(parsed, context, api) {
|
|
@@ -454,7 +430,7 @@ async function executeWorkflowList(parsed, context, api) {
|
|
|
454
430
|
human: renderTable(data.processes, [
|
|
455
431
|
{ key: "name", label: "Name" },
|
|
456
432
|
{ key: "latestVersion", label: "Latest" },
|
|
457
|
-
{ key: "
|
|
433
|
+
{ key: "deployedVersion", label: "Deployed" }
|
|
458
434
|
]),
|
|
459
435
|
kind: "authoring_workflow_list",
|
|
460
436
|
meta: { command: "workflow list", orgId: org.id }
|
|
@@ -511,7 +487,10 @@ async function executeWorkflowStart(parsed, context, api) {
|
|
|
511
487
|
? await readJsonInputSpecifier(inputSpecifier, context.stdin, "workflow start")
|
|
512
488
|
: undefined;
|
|
513
489
|
const workflowName = readRequiredArg(parsed, "name");
|
|
490
|
+
const resolvedEnvironment = resolveCliEnvironment(parsed, session);
|
|
491
|
+
announceResolvedEnvironment(parsed, context, resolvedEnvironment.environment);
|
|
514
492
|
const data = await api.startWorkflow(session, org.id, workflowName, {
|
|
493
|
+
environment: resolvedEnvironment.environment,
|
|
515
494
|
...(inputData ? { inputData } : {}),
|
|
516
495
|
startEvent: {
|
|
517
496
|
...(readOptionalStringFlag(parsed, "start-event-name")
|
|
@@ -522,24 +501,69 @@ async function executeWorkflowStart(parsed, context, api) {
|
|
|
522
501
|
});
|
|
523
502
|
return {
|
|
524
503
|
data,
|
|
525
|
-
human: renderSuccess(`Started workflow ${workflowName} as ${data.started.workflowId}.`),
|
|
504
|
+
human: renderSuccess(`Started workflow ${workflowName} in ${data.started.environmentKey ?? resolvedEnvironment.environment} as ${data.started.workflowId}.`),
|
|
526
505
|
kind: "runtime_workflow_start",
|
|
527
|
-
meta: { command: "workflow start", orgId: org.id }
|
|
506
|
+
meta: { command: "workflow start", environment: data.started.environmentKey ?? resolvedEnvironment.environment, orgId: org.id }
|
|
528
507
|
};
|
|
529
508
|
}
|
|
530
|
-
async function
|
|
509
|
+
async function executeWorkflowNodeTest(parsed, context, api) {
|
|
531
510
|
const { org, session } = await resolveOrgScope(parsed, context, api);
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
511
|
+
const workflowName = readRequiredArg(parsed, "workflow-name");
|
|
512
|
+
const nodeId = readRequiredArg(parsed, "node-id");
|
|
513
|
+
const workspacePath = readOptionalStringFlag(parsed, "workspace") ?? context.cwd;
|
|
514
|
+
const environment = readOptionalStringFlag(parsed, "environment");
|
|
515
|
+
const inputSpecifier = readOptionalStringFlag(parsed, "input");
|
|
516
|
+
const inputData = inputSpecifier
|
|
517
|
+
? await readJsonInputSpecifier(inputSpecifier, context.stdin, "test node")
|
|
518
|
+
: undefined;
|
|
519
|
+
const data = await api.testWorkflowNode(session, org.id, workflowName, nodeId, {
|
|
520
|
+
...(environment ? { environment } : {}),
|
|
521
|
+
files: await readWorkspaceTestEntries(workspacePath),
|
|
522
|
+
...(inputData ? { input: inputData } : {})
|
|
535
523
|
});
|
|
524
|
+
const test = data.test;
|
|
536
525
|
return {
|
|
537
526
|
data,
|
|
538
|
-
|
|
527
|
+
exitCode: test.status === "passed" ? 0 : 1,
|
|
528
|
+
human: renderWorkflowNodeTestHuman(test),
|
|
529
|
+
kind: "workflow_node_test",
|
|
530
|
+
meta: withCliSummary({
|
|
531
|
+
command: "test node",
|
|
532
|
+
nodeId,
|
|
533
|
+
orgId: org.id,
|
|
534
|
+
workflowName
|
|
535
|
+
}, summarizeWorkflowNodeTest(test))
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
async function executeReleaseList(parsed, context, api) {
|
|
539
|
+
const { org, session } = await resolveOrgScope(parsed, context, api);
|
|
540
|
+
const [data, deploymentData] = await Promise.all([
|
|
541
|
+
api.listReleases(session, org.id, {
|
|
542
|
+
...optionalNumberValue("limit", readOptionalNumberFlag(parsed, "limit"))
|
|
543
|
+
}),
|
|
544
|
+
api.listEnvironmentDeployments(session, org.id, { limit: 100 })
|
|
545
|
+
]);
|
|
546
|
+
return {
|
|
547
|
+
data: { ...data, deployments: deploymentData.deployments },
|
|
548
|
+
human: renderTable(data.releases.map((release) => {
|
|
549
|
+
const deploymentsForRelease = deploymentData.deployments.filter((deployment) => deployment.releaseId === release.id);
|
|
550
|
+
const latestDeployment = deploymentsForRelease[0] ?? null;
|
|
551
|
+
return {
|
|
552
|
+
createdAt: release.createdAt,
|
|
553
|
+
id: release.id,
|
|
554
|
+
state: typeof release.state === "string" ? release.state : "",
|
|
555
|
+
latestDeployment: latestDeployment ? `${latestDeployment.environmentKey}:${latestDeployment.status}` : "",
|
|
556
|
+
liveEnvironments: deploymentsForRelease
|
|
557
|
+
.filter((deployment) => deployment.isLive)
|
|
558
|
+
.map((deployment) => deployment.environmentKey)
|
|
559
|
+
.join(", ")
|
|
560
|
+
};
|
|
561
|
+
}), [
|
|
539
562
|
{ key: "id", label: "Release" },
|
|
540
|
-
{ key: "
|
|
541
|
-
{ key: "
|
|
542
|
-
{ key: "
|
|
563
|
+
{ key: "state", label: "Artifact state" },
|
|
564
|
+
{ key: "liveEnvironments", label: "Live environments" },
|
|
565
|
+
{ key: "latestDeployment", label: "Latest deployment" },
|
|
566
|
+
{ key: "createdAt", label: "Created" }
|
|
543
567
|
]),
|
|
544
568
|
kind: "releases_release_list",
|
|
545
569
|
meta: { command: "release list", orgId: org.id }
|
|
@@ -555,88 +579,269 @@ async function executeReleaseGet(parsed, context, api) {
|
|
|
555
579
|
meta: { command: "release get", orgId: org.id }
|
|
556
580
|
};
|
|
557
581
|
}
|
|
558
|
-
async function
|
|
582
|
+
async function executeReleaseValidate(parsed, context, api) {
|
|
559
583
|
const { org, session } = await resolveOrgScope(parsed, context, api);
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
584
|
+
const releaseId = readRequiredArg(parsed, "release-id");
|
|
585
|
+
const environment = readOptionalStringFlag(parsed, "environment");
|
|
586
|
+
const data = await api.validateRelease(session, org.id, {
|
|
587
|
+
...(environment ? { environment } : {}),
|
|
588
|
+
releaseId
|
|
589
|
+
});
|
|
590
|
+
const validation = data.validation;
|
|
591
|
+
const human = validation.releaseReady
|
|
592
|
+
? renderSuccess(`Release ${validation.releaseId} passed release validation${environment ? ` for ${environment}` : ""}.`)
|
|
593
|
+
: [
|
|
594
|
+
`Release ${validation.releaseId} did not pass release validation${environment ? ` for ${environment}` : ""}.`,
|
|
595
|
+
renderTable(validation.diagnostics, [
|
|
596
|
+
{ key: "gate", label: "Gate" },
|
|
597
|
+
{ key: "severity", label: "Severity" },
|
|
598
|
+
{ key: "code", label: "Code" },
|
|
599
|
+
{ key: "path", label: "Path" },
|
|
600
|
+
{ key: "message", label: "Message" }
|
|
601
|
+
])
|
|
602
|
+
].join("\n");
|
|
576
603
|
return {
|
|
577
604
|
data,
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
605
|
+
...(validation.releaseReady ? {} : { exitCode: 1 }),
|
|
606
|
+
human,
|
|
607
|
+
kind: "releases_release_validate",
|
|
608
|
+
meta: withCliSummary({ command: "release validate", ...(environment ? { environment } : {}), orgId: org.id }, summarizeReleaseValidation(validation))
|
|
581
609
|
};
|
|
582
610
|
}
|
|
583
|
-
async function
|
|
611
|
+
async function executeReleaseSource(parsed, context, api) {
|
|
584
612
|
const { org, session } = await resolveOrgScope(parsed, context, api);
|
|
585
|
-
const
|
|
613
|
+
const releaseId = readRequiredArg(parsed, "release-id");
|
|
614
|
+
const outPath = readRequiredStringFlag(parsed, "out");
|
|
615
|
+
const data = await api.getReleaseSource(session, org.id, releaseId);
|
|
616
|
+
const files = [
|
|
617
|
+
...data.source.files.map((file) => ({
|
|
618
|
+
content: file.content,
|
|
619
|
+
path: file.path
|
|
620
|
+
})),
|
|
621
|
+
...data.source.assets.map((asset) => ({
|
|
622
|
+
content: asset.content,
|
|
623
|
+
path: asset.path
|
|
624
|
+
}))
|
|
625
|
+
];
|
|
626
|
+
await writeReleaseSourceFiles(outPath, {
|
|
627
|
+
files,
|
|
628
|
+
metadata: {
|
|
629
|
+
fileCount: files.length,
|
|
630
|
+
releaseId,
|
|
631
|
+
source: "release"
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
const summary = `Release ${releaseId} source written to ${outPath}.`;
|
|
586
635
|
return {
|
|
587
|
-
data
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
636
|
+
data: {
|
|
637
|
+
fileCount: files.length,
|
|
638
|
+
releaseId,
|
|
639
|
+
out: outPath
|
|
640
|
+
},
|
|
641
|
+
human: renderSuccess(summary),
|
|
642
|
+
kind: "releases_release_source",
|
|
643
|
+
meta: withCliSummary({ command: "release source", orgId: org.id, releaseId }, summary)
|
|
591
644
|
};
|
|
592
645
|
}
|
|
593
|
-
async function
|
|
646
|
+
async function executeReleaseCreate(parsed, context, api) {
|
|
594
647
|
const { org, session } = await resolveOrgScope(parsed, context, api);
|
|
595
|
-
const
|
|
648
|
+
const workspacePath = readRequiredArg(parsed, "workspace");
|
|
649
|
+
const label = readOptionalStringFlag(parsed, "label");
|
|
650
|
+
if (isZipArchivePath(workspacePath)) {
|
|
651
|
+
const data = await api.createReleaseArchive(session, org.id, await readArchiveBytes(workspacePath, "release create"), {
|
|
652
|
+
...(label ? { label } : {})
|
|
653
|
+
});
|
|
654
|
+
const summary = summarizeReleaseCreation(data.created.release.id);
|
|
655
|
+
return {
|
|
656
|
+
data,
|
|
657
|
+
human: renderSuccess(`${summary} Source: ${workspacePath}.`),
|
|
658
|
+
kind: "releases_release_create",
|
|
659
|
+
meta: withCliSummary({ command: "release create", orgId: org.id, releaseId: data.created.release.id }, summary)
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
const files = await readImportEntries(workspacePath);
|
|
663
|
+
const data = await api.createRelease(session, org.id, {
|
|
664
|
+
files,
|
|
665
|
+
...(label ? { label } : {})
|
|
666
|
+
});
|
|
667
|
+
const summary = summarizeReleaseCreation(data.created.release.id);
|
|
596
668
|
return {
|
|
597
669
|
data,
|
|
598
|
-
human: renderSuccess(
|
|
599
|
-
kind: "
|
|
600
|
-
meta: { command: "release
|
|
670
|
+
human: renderSuccess(`${summary} Source: ${workspacePath}.`),
|
|
671
|
+
kind: "releases_release_create",
|
|
672
|
+
meta: withCliSummary({ command: "release create", orgId: org.id, releaseId: data.created.release.id }, summary)
|
|
601
673
|
};
|
|
602
674
|
}
|
|
603
|
-
async function
|
|
675
|
+
async function executeEnvironment(parsed, context, api) {
|
|
604
676
|
const { org, session } = await resolveOrgScope(parsed, context, api);
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
677
|
+
switch (parsed.definition.id) {
|
|
678
|
+
case "environment.list": {
|
|
679
|
+
const data = await api.listEnvironments(session, org.id);
|
|
680
|
+
return {
|
|
681
|
+
data,
|
|
682
|
+
human: renderTable(data.environments.map((entry) => ({
|
|
683
|
+
currentDeploymentId: entry.environment.currentDeploymentId,
|
|
684
|
+
isProductionTarget: entry.environment.isProductionTarget,
|
|
685
|
+
key: entry.environment.key,
|
|
686
|
+
latestStatus: entry.latestDeployment?.status ?? "",
|
|
687
|
+
liveDeploymentId: entry.liveDeployment?.id ?? "",
|
|
688
|
+
name: entry.environment.name,
|
|
689
|
+
status: entry.environment.status
|
|
690
|
+
})), [
|
|
691
|
+
{ key: "key", label: "Environment" },
|
|
692
|
+
{ key: "name", label: "Name" },
|
|
693
|
+
{ key: "status", label: "Status" },
|
|
694
|
+
{ key: "isProductionTarget", label: "Production" },
|
|
695
|
+
{ key: "liveDeploymentId", label: "Live deployment" },
|
|
696
|
+
{ key: "latestStatus", label: "Latest" }
|
|
697
|
+
]),
|
|
698
|
+
kind: "environment_list",
|
|
699
|
+
meta: { command: "environment list", orgId: org.id }
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
case "environment.get": {
|
|
703
|
+
const data = await api.getEnvironment(session, org.id, readRequiredArg(parsed, "environment"));
|
|
704
|
+
return {
|
|
705
|
+
data,
|
|
706
|
+
human: renderPrettyJson(data.environment),
|
|
707
|
+
kind: "environment_get",
|
|
708
|
+
meta: { command: "environment get", orgId: org.id }
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
case "environment.create": {
|
|
712
|
+
const key = readRequiredArg(parsed, "environment");
|
|
713
|
+
const copyFrom = readOptionalStringFlag(parsed, "copy-from");
|
|
714
|
+
const copyFromEnvironmentId = copyFrom
|
|
715
|
+
? await resolveEnvironmentIdForCli(session, org.id, copyFrom, api)
|
|
716
|
+
: undefined;
|
|
717
|
+
const data = await api.createEnvironment(session, org.id, {
|
|
718
|
+
...(copyFromEnvironmentId ? { copyFromEnvironmentId } : {}),
|
|
719
|
+
key,
|
|
720
|
+
...(readOptionalStringFlag(parsed, "name") ? { name: readOptionalStringFlag(parsed, "name") } : {})
|
|
721
|
+
});
|
|
722
|
+
return {
|
|
723
|
+
data,
|
|
724
|
+
human: renderSuccess(`Created environment ${data.environment.key}${copyFrom ? ` from ${copyFrom}` : ""}.`),
|
|
725
|
+
kind: "environment_create",
|
|
726
|
+
meta: {
|
|
727
|
+
command: "environment create",
|
|
728
|
+
...(copyFrom ? { copyFrom } : {}),
|
|
729
|
+
environment: data.environment.key,
|
|
730
|
+
orgId: org.id
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
case "environment.rename": {
|
|
735
|
+
const data = await api.renameEnvironment(session, org.id, readRequiredArg(parsed, "environment"), {
|
|
736
|
+
name: readRequiredArg(parsed, "name")
|
|
737
|
+
});
|
|
738
|
+
return {
|
|
739
|
+
data,
|
|
740
|
+
human: renderSuccess(`Renamed environment ${data.environment.key}.`),
|
|
741
|
+
kind: "environment_rename",
|
|
742
|
+
meta: { command: "environment rename", environment: data.environment.key, orgId: org.id }
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
case "environment.archive": {
|
|
746
|
+
const environment = readRequiredArg(parsed, "environment");
|
|
747
|
+
await confirmDestructive(parsed, context, `Archive environment ${environment}?`);
|
|
748
|
+
const data = await api.archiveEnvironment(session, org.id, environment);
|
|
749
|
+
return {
|
|
750
|
+
data,
|
|
751
|
+
human: renderSuccess(`Archived environment ${data.environment.key}.`),
|
|
752
|
+
kind: "environment_archive",
|
|
753
|
+
meta: { command: "environment archive", environment: data.environment.key, orgId: org.id }
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
case "environment.deploy": {
|
|
757
|
+
const environment = readRequiredArg(parsed, "environment");
|
|
758
|
+
const releaseId = readRequiredArg(parsed, "release");
|
|
759
|
+
const policy = readOptionalStringFlag(parsed, "policy");
|
|
760
|
+
const data = await api.deployEnvironment(session, org.id, environment, {
|
|
761
|
+
...(policy ? { policy } : {}),
|
|
762
|
+
releaseId
|
|
763
|
+
});
|
|
764
|
+
return {
|
|
765
|
+
data,
|
|
766
|
+
human: renderSuccess(`Deployed release ${releaseId} to ${environment} as ${data.deployment.id}.`),
|
|
767
|
+
kind: "environment_deploy",
|
|
768
|
+
meta: { command: "environment deploy", deploymentId: data.deployment.id, environment, orgId: org.id, releaseId }
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
case "environment.undeploy": {
|
|
772
|
+
const environment = readRequiredArg(parsed, "environment");
|
|
773
|
+
await confirmDestructive(parsed, context, `Undeploy the live deployment from ${environment}?`);
|
|
774
|
+
const policy = readOptionalStringFlag(parsed, "policy");
|
|
775
|
+
const data = await api.undeployEnvironment(session, org.id, environment, {
|
|
776
|
+
...(policy ? { policy } : {})
|
|
777
|
+
});
|
|
778
|
+
return {
|
|
779
|
+
data,
|
|
780
|
+
human: renderSuccess(`Undeployed environment ${environment}.`),
|
|
781
|
+
kind: "environment_undeploy",
|
|
782
|
+
meta: { command: "environment undeploy", deploymentId: data.deployment.id, environment, orgId: org.id }
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
default:
|
|
786
|
+
throw genericProblem(`Unhandled environment command ${parsed.definition.id}.`, parsed.definition.id);
|
|
787
|
+
}
|
|
613
788
|
}
|
|
614
|
-
async function
|
|
789
|
+
async function executeDeployment(parsed, context, api) {
|
|
615
790
|
const { org, session } = await resolveOrgScope(parsed, context, api);
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
791
|
+
switch (parsed.definition.id) {
|
|
792
|
+
case "deployment.list": {
|
|
793
|
+
const { environment } = resolveCliEnvironment(parsed, session);
|
|
794
|
+
const data = await api.listEnvironmentDeployments(session, org.id, {
|
|
795
|
+
environment,
|
|
796
|
+
...optionalNumberValue("limit", readOptionalNumberFlag(parsed, "limit"))
|
|
797
|
+
});
|
|
798
|
+
return {
|
|
799
|
+
data,
|
|
800
|
+
human: renderTable(data.deployments.map((deployment) => ({
|
|
801
|
+
...deployment,
|
|
802
|
+
live: deployment.isLive ? "yes" : ""
|
|
803
|
+
})), [
|
|
804
|
+
{ key: "id", label: "Deployment" },
|
|
805
|
+
{ key: "environmentKey", label: "Environment" },
|
|
806
|
+
{ key: "live", label: "Live" },
|
|
807
|
+
{ key: "releaseId", label: "Release" },
|
|
808
|
+
{ key: "status", label: "Status" },
|
|
809
|
+
{ key: "coreDeploymentId", label: "Core deployment" },
|
|
810
|
+
{ key: "updatedAt", label: "Updated" }
|
|
811
|
+
]),
|
|
812
|
+
kind: "deployment_list",
|
|
813
|
+
meta: { command: "deployment list", environment, orgId: org.id }
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
case "deployment.get": {
|
|
817
|
+
const environment = readOptionalStringFlag(parsed, "environment");
|
|
818
|
+
const data = await api.getEnvironmentDeployment(session, org.id, readRequiredArg(parsed, "deployment"), {
|
|
819
|
+
...(environment ? { environment } : {})
|
|
820
|
+
});
|
|
821
|
+
return {
|
|
822
|
+
data,
|
|
823
|
+
human: renderPrettyJson(data.deployment),
|
|
824
|
+
kind: "deployment_get",
|
|
825
|
+
meta: { command: "deployment get", deploymentId: data.deployment.id, ...(environment ? { environment } : {}), orgId: org.id }
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
default:
|
|
829
|
+
throw genericProblem(`Unhandled deployment command ${parsed.definition.id}.`, parsed.definition.id);
|
|
830
|
+
}
|
|
624
831
|
}
|
|
625
|
-
async function
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
human: renderSuccess(`Deactivated release ${parsed.args["release-id"]}.`),
|
|
633
|
-
kind: "releases_release_deactivate",
|
|
634
|
-
meta: { command: "release deactivate", orgId: org.id }
|
|
635
|
-
};
|
|
832
|
+
async function resolveEnvironmentIdForCli(session, orgId, environment, api) {
|
|
833
|
+
const data = await api.listEnvironments(session, orgId);
|
|
834
|
+
const match = data.environments.find((entry) => entry.environment.id === environment || entry.environment.key === environment);
|
|
835
|
+
if (!match) {
|
|
836
|
+
throw notFoundProblem(`Environment '${environment}' was not found.`, "environment create");
|
|
837
|
+
}
|
|
838
|
+
return match.environment.id;
|
|
636
839
|
}
|
|
637
840
|
async function executeRunList(parsed, context, api) {
|
|
638
841
|
const { org, session } = await resolveOrgScope(parsed, context, api);
|
|
842
|
+
const resolvedEnvironment = resolveCliEnvironment(parsed, session);
|
|
639
843
|
const data = await api.listRuns(session, org.id, {
|
|
844
|
+
environment: resolvedEnvironment.environment,
|
|
640
845
|
...optionalNumberValue("limit", readOptionalNumberFlag(parsed, "limit")),
|
|
641
846
|
...(readOptionalStringFlag(parsed, "status") ? { status: readOptionalStringFlag(parsed, "status") } : {})
|
|
642
847
|
});
|
|
@@ -645,11 +850,12 @@ async function executeRunList(parsed, context, api) {
|
|
|
645
850
|
human: renderTable(data.runs, [
|
|
646
851
|
{ key: "workflowId", label: "Workflow ID" },
|
|
647
852
|
{ key: "status", label: "Status" },
|
|
853
|
+
{ key: "environmentKey", label: "Environment" },
|
|
648
854
|
{ key: "releaseId", label: "Release" },
|
|
649
855
|
{ key: "startTime", label: "Started" }
|
|
650
856
|
]),
|
|
651
857
|
kind: "runtime_run_list",
|
|
652
|
-
meta: { command: "run list", orgId: org.id }
|
|
858
|
+
meta: { command: "run list", environment: resolvedEnvironment.environment, orgId: org.id }
|
|
653
859
|
};
|
|
654
860
|
}
|
|
655
861
|
async function executeRunGet(parsed, context, api) {
|
|
@@ -764,43 +970,7 @@ async function executeOrgModel(parsed, context, api) {
|
|
|
764
970
|
? renderPrettyJson(record)
|
|
765
971
|
: renderTable(editor.operations, [
|
|
766
972
|
{ key: "name", label: "Name" },
|
|
767
|
-
{ key: "
|
|
768
|
-
{ key: "action", label: "Action" }
|
|
769
|
-
]),
|
|
770
|
-
kind: `authoring_${sanitizeKind(section)}_${verb}`,
|
|
771
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
772
|
-
};
|
|
773
|
-
}
|
|
774
|
-
if (section === "connectors") {
|
|
775
|
-
const editor = (await api.getConnectorsEditor(session, org.id)).editor;
|
|
776
|
-
const record = verb === "get"
|
|
777
|
-
? requireFound(editor.connectors.find((entry) => entry.name === parsed.args.name), `Connector '${parsed.args.name}' was not found.`, parsed.definition.id)
|
|
778
|
-
: undefined;
|
|
779
|
-
return {
|
|
780
|
-
data: verb === "get" ? { connector: record } : { connectors: editor.connectors },
|
|
781
|
-
human: verb === "get"
|
|
782
|
-
? renderPrettyJson(record)
|
|
783
|
-
: renderTable(editor.connectors, [
|
|
784
|
-
{ key: "name", label: "Name" },
|
|
785
|
-
{ key: "type", label: "Type" }
|
|
786
|
-
]),
|
|
787
|
-
kind: `authoring_${sanitizeKind(section)}_${verb}`,
|
|
788
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
if (section === "mcp-servers") {
|
|
792
|
-
const editor = (await api.getMcpServersEditor(session, org.id)).editor;
|
|
793
|
-
const record = verb === "get"
|
|
794
|
-
? requireFound(editor.servers.find((entry) => entry.name === parsed.args.name), `MCP server '${parsed.args.name}' was not found.`, parsed.definition.id)
|
|
795
|
-
: undefined;
|
|
796
|
-
return {
|
|
797
|
-
data: verb === "get" ? { server: record } : { servers: editor.servers },
|
|
798
|
-
human: verb === "get"
|
|
799
|
-
? renderPrettyJson(record)
|
|
800
|
-
: renderTable(editor.servers, [
|
|
801
|
-
{ key: "name", label: "Name" },
|
|
802
|
-
{ key: "transport", label: "Transport" },
|
|
803
|
-
{ key: "url", label: "URL" }
|
|
973
|
+
{ key: "command", label: "Command" }
|
|
804
974
|
]),
|
|
805
975
|
kind: `authoring_${sanitizeKind(section)}_${verb}`,
|
|
806
976
|
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
@@ -985,353 +1155,41 @@ async function executeAccess(parsed, context, api) {
|
|
|
985
1155
|
throw genericProblem(`Unhandled access command ${parsed.definition.id}.`, parsed.definition.id);
|
|
986
1156
|
}
|
|
987
1157
|
}
|
|
988
|
-
async function executeMcp(parsed, context, api) {
|
|
989
|
-
const { org, session } = await resolveOrgScope(parsed, context, api);
|
|
990
|
-
switch (parsed.definition.id) {
|
|
991
|
-
case "mcp.list": {
|
|
992
|
-
const data = await api.listOrgMcpServers(session, org.id);
|
|
993
|
-
return {
|
|
994
|
-
data,
|
|
995
|
-
human: renderTable(data.servers, [
|
|
996
|
-
{ key: "slug", label: "Slug" },
|
|
997
|
-
{ key: "displayName", label: "Name" },
|
|
998
|
-
{ key: "status", label: "Status" },
|
|
999
|
-
{ key: "authMode", label: "Auth" },
|
|
1000
|
-
{ key: "url", label: "URL" }
|
|
1001
|
-
]),
|
|
1002
|
-
kind: "mcp_servers_list",
|
|
1003
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1004
|
-
};
|
|
1005
|
-
}
|
|
1006
|
-
case "mcp.get": {
|
|
1007
|
-
const data = await api.getOrgMcpServer(session, org.id, readRequiredArg(parsed, "server"));
|
|
1008
|
-
return {
|
|
1009
|
-
data,
|
|
1010
|
-
human: renderPrettyJson(data),
|
|
1011
|
-
kind: "mcp_servers_get",
|
|
1012
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1013
|
-
};
|
|
1014
|
-
}
|
|
1015
|
-
case "mcp.create": {
|
|
1016
|
-
const authMode = readOptionalMcpAuthMode(parsed);
|
|
1017
|
-
const timeoutMs = readOptionalNumberFlag(parsed, "timeout-ms");
|
|
1018
|
-
const input = {
|
|
1019
|
-
displayName: readRequiredStringFlag(parsed, "name"),
|
|
1020
|
-
slug: readRequiredStringFlag(parsed, "slug"),
|
|
1021
|
-
url: readRequiredStringFlag(parsed, "url"),
|
|
1022
|
-
...(authMode !== undefined ? { authMode } : {}),
|
|
1023
|
-
...(timeoutMs !== undefined ? { timeoutMs } : {})
|
|
1024
|
-
};
|
|
1025
|
-
const data = await api.createOrgMcpServer(session, org.id, input);
|
|
1026
|
-
return {
|
|
1027
|
-
data,
|
|
1028
|
-
human: renderSuccess(`Created MCP server ${data.server.slug}.`),
|
|
1029
|
-
kind: "mcp_servers_create",
|
|
1030
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1031
|
-
};
|
|
1032
|
-
}
|
|
1033
|
-
case "mcp.update": {
|
|
1034
|
-
const authMode = readOptionalMcpAuthMode(parsed);
|
|
1035
|
-
const displayName = readOptionalStringFlag(parsed, "name");
|
|
1036
|
-
const timeoutMs = readOptionalNumberFlag(parsed, "timeout-ms");
|
|
1037
|
-
const url = readOptionalStringFlag(parsed, "url");
|
|
1038
|
-
const disable = parsed.flags.disable === true;
|
|
1039
|
-
const enable = parsed.flags.enable === true;
|
|
1040
|
-
if (disable && enable) {
|
|
1041
|
-
throw usageProblem("Use either --disable or --enable, not both.", parsed.definition.id);
|
|
1042
|
-
}
|
|
1043
|
-
const input = {
|
|
1044
|
-
...(displayName !== undefined ? { displayName } : {}),
|
|
1045
|
-
...(url !== undefined ? { url } : {}),
|
|
1046
|
-
...(authMode !== undefined ? { authMode } : {}),
|
|
1047
|
-
...(disable || enable ? { disabled: disable } : {}),
|
|
1048
|
-
...(timeoutMs !== undefined ? { timeoutMs } : {})
|
|
1049
|
-
};
|
|
1050
|
-
if (Object.keys(input).length === 0) {
|
|
1051
|
-
throw usageProblem("Provide at least one MCP server field to update.", parsed.definition.id);
|
|
1052
|
-
}
|
|
1053
|
-
const data = await api.updateOrgMcpServer(session, org.id, readRequiredArg(parsed, "server"), input);
|
|
1054
|
-
return {
|
|
1055
|
-
data,
|
|
1056
|
-
human: renderSuccess(`Updated MCP server ${data.server.slug}.`),
|
|
1057
|
-
kind: "mcp_servers_update",
|
|
1058
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1059
|
-
};
|
|
1060
|
-
}
|
|
1061
|
-
case "mcp.delete": {
|
|
1062
|
-
const serverRef = readRequiredArg(parsed, "server");
|
|
1063
|
-
await confirmDestructive(parsed, context, `Delete MCP server ${serverRef}?`);
|
|
1064
|
-
await api.deleteOrgMcpServer(session, org.id, serverRef);
|
|
1065
|
-
return {
|
|
1066
|
-
data: {},
|
|
1067
|
-
human: renderSuccess(`Deleted MCP server ${serverRef}.`),
|
|
1068
|
-
kind: "mcp_servers_delete",
|
|
1069
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1070
|
-
};
|
|
1071
|
-
}
|
|
1072
|
-
case "mcp.test": {
|
|
1073
|
-
const data = await api.testOrgMcpServer(session, org.id, readRequiredArg(parsed, "server"));
|
|
1074
|
-
if (!data.ok) {
|
|
1075
|
-
throw genericProblem(data.server.lastErrorMessage ?? `MCP server ${data.server.slug} test failed.`, parsed.definition.id);
|
|
1076
|
-
}
|
|
1077
|
-
return {
|
|
1078
|
-
data,
|
|
1079
|
-
human: renderSuccess(`MCP server ${data.server.slug} is reachable.`),
|
|
1080
|
-
kind: "mcp_servers_test",
|
|
1081
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1082
|
-
};
|
|
1083
|
-
}
|
|
1084
|
-
case "mcp.discover-tools": {
|
|
1085
|
-
const data = await api.discoverOrgMcpServerTools(session, org.id, readRequiredArg(parsed, "server"));
|
|
1086
|
-
return {
|
|
1087
|
-
data,
|
|
1088
|
-
human: data.tools.length === 0
|
|
1089
|
-
? renderSuccess(`Discovered no tools for ${data.server.slug}.`)
|
|
1090
|
-
: renderTable(data.tools, [
|
|
1091
|
-
{ key: "toolName", label: "Tool" },
|
|
1092
|
-
{ key: "description", label: "Description" },
|
|
1093
|
-
{ key: "schemaSha256", label: "Schema" }
|
|
1094
|
-
]),
|
|
1095
|
-
kind: "mcp_servers_discover_tools",
|
|
1096
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1097
|
-
};
|
|
1098
|
-
}
|
|
1099
|
-
case "mcp.connect": {
|
|
1100
|
-
const serverRef = readRequiredArg(parsed, "server");
|
|
1101
|
-
const data = await api.connectOrgMcpServerOAuth(session, org.id, serverRef);
|
|
1102
|
-
const explicitOpen = parsed.flags.open === true;
|
|
1103
|
-
const shouldOpen = shouldAutoOpenBrowser({
|
|
1104
|
-
env: context.env,
|
|
1105
|
-
explicitNoOpen: parsed.flags["no-open"] === true,
|
|
1106
|
-
explicitOpen,
|
|
1107
|
-
interactive: isInteractive(context),
|
|
1108
|
-
openBrowserConfig: context.config.openBrowser
|
|
1109
|
-
});
|
|
1110
|
-
let opened = false;
|
|
1111
|
-
if (shouldOpen) {
|
|
1112
|
-
opened = await openBrowser(data.authorizationUrl);
|
|
1113
|
-
if (explicitOpen && !opened) {
|
|
1114
|
-
throw genericProblem(`Could not open a browser automatically. Visit ${data.authorizationUrl}`, parsed.definition.id);
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
const finalSession = parsed.flags.wait === true
|
|
1118
|
-
? await pollMcpOAuthSession(api, session, org.id, serverRef, data.sessionId)
|
|
1119
|
-
: null;
|
|
1120
|
-
if (finalSession && isFailedMcpOAuthSession(finalSession.session.status)) {
|
|
1121
|
-
throw genericProblem(finalSession.session.errorMessage
|
|
1122
|
-
?? `MCP OAuth connection ${finalSession.session.status}.`, parsed.definition.id);
|
|
1123
|
-
}
|
|
1124
|
-
return {
|
|
1125
|
-
data: finalSession ? { ...data, session: finalSession.session } : data,
|
|
1126
|
-
human: finalSession
|
|
1127
|
-
? renderMcpOAuthSessionHuman(data.authorizationUrl, finalSession.session.status)
|
|
1128
|
-
: [
|
|
1129
|
-
"MCP OAuth authorization started.",
|
|
1130
|
-
`Open: ${data.authorizationUrl}`,
|
|
1131
|
-
`Session: ${data.sessionId}`
|
|
1132
|
-
].join("\n"),
|
|
1133
|
-
kind: "mcp_servers_oauth_connect",
|
|
1134
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1135
|
-
};
|
|
1136
|
-
}
|
|
1137
|
-
case "mcp.disconnect": {
|
|
1138
|
-
const serverRef = readRequiredArg(parsed, "server");
|
|
1139
|
-
await confirmDestructive(parsed, context, `Disconnect OAuth for MCP server ${serverRef}?`);
|
|
1140
|
-
const data = await api.disconnectOrgMcpServerOAuth(session, org.id, serverRef);
|
|
1141
|
-
return {
|
|
1142
|
-
data,
|
|
1143
|
-
human: renderSuccess(`Disconnected OAuth for MCP server ${data.server.slug}.`),
|
|
1144
|
-
kind: "mcp_servers_oauth_disconnect",
|
|
1145
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1146
|
-
};
|
|
1147
|
-
}
|
|
1148
|
-
case "mcp.oauth-status": {
|
|
1149
|
-
const data = await api.getOrgMcpServerOAuthStatus(session, org.id, readRequiredArg(parsed, "server"));
|
|
1150
|
-
return {
|
|
1151
|
-
data,
|
|
1152
|
-
human: renderPrettyJson(data.oauth),
|
|
1153
|
-
kind: "mcp_servers_oauth_status",
|
|
1154
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1155
|
-
};
|
|
1156
|
-
}
|
|
1157
|
-
default:
|
|
1158
|
-
throw genericProblem(`Unhandled MCP command ${parsed.definition.id}.`, parsed.definition.id);
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
async function executeSkills(parsed, context, api) {
|
|
1162
|
-
const { org, session } = await resolveOrgScope(parsed, context, api);
|
|
1163
|
-
switch (parsed.definition.id) {
|
|
1164
|
-
case "skills.list": {
|
|
1165
|
-
const data = await api.listOrgSkillPackages(session, org.id);
|
|
1166
|
-
return {
|
|
1167
|
-
data,
|
|
1168
|
-
human: renderTable(data.skills, [
|
|
1169
|
-
{ key: "slug", label: "Slug" },
|
|
1170
|
-
{ key: "displayName", label: "Name" },
|
|
1171
|
-
{ key: "status", label: "Status" },
|
|
1172
|
-
{ key: "currentRevision", label: "Revision" },
|
|
1173
|
-
{ key: "currentFileCount", label: "Files" },
|
|
1174
|
-
{ key: "currentPackageSha256", label: "Package SHA" },
|
|
1175
|
-
{ key: "updatedAt", label: "Updated" }
|
|
1176
|
-
]),
|
|
1177
|
-
kind: "skills_list",
|
|
1178
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1179
|
-
};
|
|
1180
|
-
}
|
|
1181
|
-
case "skills.get": {
|
|
1182
|
-
const data = await api.getOrgSkillPackage(session, org.id, readRequiredArg(parsed, "skill"));
|
|
1183
|
-
return {
|
|
1184
|
-
data,
|
|
1185
|
-
human: renderPrettyJson(data),
|
|
1186
|
-
kind: "skills_get",
|
|
1187
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1188
|
-
};
|
|
1189
|
-
}
|
|
1190
|
-
case "skills.create": {
|
|
1191
|
-
const description = readOptionalStringFlag(parsed, "description");
|
|
1192
|
-
const dir = readOptionalStringFlag(parsed, "dir");
|
|
1193
|
-
const files = dir ? await readImportEntries(dir) : undefined;
|
|
1194
|
-
const data = await api.createOrgSkillPackage(session, org.id, {
|
|
1195
|
-
...(description !== undefined ? { description } : {}),
|
|
1196
|
-
displayName: readRequiredStringFlag(parsed, "name"),
|
|
1197
|
-
...(files ? { files } : {}),
|
|
1198
|
-
slug: readRequiredStringFlag(parsed, "slug")
|
|
1199
|
-
});
|
|
1200
|
-
return {
|
|
1201
|
-
data,
|
|
1202
|
-
human: renderSuccess(`Created skill package ${data.skill.slug}.`),
|
|
1203
|
-
kind: "skills_create",
|
|
1204
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1205
|
-
};
|
|
1206
|
-
}
|
|
1207
|
-
case "skills.upload": {
|
|
1208
|
-
const skillRef = readRequiredArg(parsed, "skill");
|
|
1209
|
-
const files = await readImportEntries(readRequiredStringFlag(parsed, "dir"));
|
|
1210
|
-
const data = await api.uploadOrgSkillPackageRevision(session, org.id, skillRef, { files });
|
|
1211
|
-
return {
|
|
1212
|
-
data,
|
|
1213
|
-
human: renderSuccess(`Uploaded skill package ${skillRef} revision ${String(data.revision.revision)}.`),
|
|
1214
|
-
kind: "skills_upload",
|
|
1215
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1216
|
-
};
|
|
1217
|
-
}
|
|
1218
|
-
case "skills.download": {
|
|
1219
|
-
const skillRef = readRequiredArg(parsed, "skill");
|
|
1220
|
-
const revision = readOptionalNumberFlag(parsed, "revision");
|
|
1221
|
-
const targetDir = readRequiredStringFlag(parsed, "dir");
|
|
1222
|
-
const data = revision === undefined
|
|
1223
|
-
? await api.getOrgSkillPackage(session, org.id, skillRef)
|
|
1224
|
-
: await api.getOrgSkillPackageRevision(session, org.id, skillRef, revision);
|
|
1225
|
-
await writeSkillPackageFiles(targetDir, data.files);
|
|
1226
|
-
return {
|
|
1227
|
-
data,
|
|
1228
|
-
human: renderSuccess(`Downloaded ${data.files.length} skill package files to ${resolve(targetDir)}.`),
|
|
1229
|
-
kind: "skills_download",
|
|
1230
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1231
|
-
};
|
|
1232
|
-
}
|
|
1233
|
-
case "skills.update": {
|
|
1234
|
-
const displayName = readOptionalStringFlag(parsed, "name");
|
|
1235
|
-
const description = readOptionalStringFlag(parsed, "description");
|
|
1236
|
-
const disable = parsed.flags.disable === true;
|
|
1237
|
-
const enable = parsed.flags.enable === true;
|
|
1238
|
-
if (disable && enable) {
|
|
1239
|
-
throw usageProblem("Use either --disable or --enable, not both.", parsed.definition.id);
|
|
1240
|
-
}
|
|
1241
|
-
const input = {
|
|
1242
|
-
...(displayName !== undefined ? { displayName } : {}),
|
|
1243
|
-
...(description !== undefined ? { description } : {}),
|
|
1244
|
-
...(disable || enable ? { disabled: disable } : {})
|
|
1245
|
-
};
|
|
1246
|
-
if (Object.keys(input).length === 0) {
|
|
1247
|
-
throw usageProblem("Provide at least one skill package field to update.", parsed.definition.id);
|
|
1248
|
-
}
|
|
1249
|
-
const data = await api.updateOrgSkillPackage(session, org.id, readRequiredArg(parsed, "skill"), input);
|
|
1250
|
-
return {
|
|
1251
|
-
data,
|
|
1252
|
-
human: renderSuccess(`Updated skill package ${data.skill.slug}.`),
|
|
1253
|
-
kind: "skills_update",
|
|
1254
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1255
|
-
};
|
|
1256
|
-
}
|
|
1257
|
-
case "skills.delete": {
|
|
1258
|
-
const skillRef = readRequiredArg(parsed, "skill");
|
|
1259
|
-
await confirmDestructive(parsed, context, `Delete skill package ${skillRef}?`);
|
|
1260
|
-
await api.deleteOrgSkillPackage(session, org.id, skillRef);
|
|
1261
|
-
return {
|
|
1262
|
-
data: {},
|
|
1263
|
-
human: renderSuccess(`Deleted skill package ${skillRef}.`),
|
|
1264
|
-
kind: "skills_delete",
|
|
1265
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1266
|
-
};
|
|
1267
|
-
}
|
|
1268
|
-
default:
|
|
1269
|
-
throw genericProblem(`Unhandled skills command ${parsed.definition.id}.`, parsed.definition.id);
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
async function writeSkillPackageFiles(targetDir, files) {
|
|
1273
|
-
const root = resolve(targetDir);
|
|
1274
|
-
for (const file of files) {
|
|
1275
|
-
const targetPath = resolve(root, file.path);
|
|
1276
|
-
if (!targetPath.startsWith(`${root}/`) && targetPath !== root) {
|
|
1277
|
-
throw usageProblem(`Refusing to write skill package path '${file.path}' outside destination.`, "skills.download");
|
|
1278
|
-
}
|
|
1279
|
-
await mkdir(dirname(targetPath), { recursive: true });
|
|
1280
|
-
await writeFile(targetPath, file.content, "utf8");
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
async function pollMcpOAuthSession(api, session, orgId, serverRef, oauthSessionId) {
|
|
1284
|
-
for (let attempt = 0; attempt < 60; attempt += 1) {
|
|
1285
|
-
const result = await api.getOrgMcpServerOAuthSession(session, orgId, serverRef, oauthSessionId);
|
|
1286
|
-
if (result.session.status !== "pending") {
|
|
1287
|
-
return result;
|
|
1288
|
-
}
|
|
1289
|
-
await new Promise((resolve) => setTimeout(resolve, 2_000));
|
|
1290
|
-
}
|
|
1291
|
-
return await api.getOrgMcpServerOAuthSession(session, orgId, serverRef, oauthSessionId);
|
|
1292
|
-
}
|
|
1293
|
-
function renderMcpOAuthSessionHuman(authorizationUrl, status) {
|
|
1294
|
-
if (status === "connected") {
|
|
1295
|
-
return renderSuccess("MCP OAuth connection completed.");
|
|
1296
|
-
}
|
|
1297
|
-
if (isFailedMcpOAuthSession(status)) {
|
|
1298
|
-
return "MCP OAuth connection failed.";
|
|
1299
|
-
}
|
|
1300
|
-
return [
|
|
1301
|
-
"MCP OAuth authorization is still pending.",
|
|
1302
|
-
`Open: ${authorizationUrl}`
|
|
1303
|
-
].join("\n");
|
|
1304
|
-
}
|
|
1305
|
-
function isFailedMcpOAuthSession(status) {
|
|
1306
|
-
return status === "error" || status === "expired";
|
|
1307
|
-
}
|
|
1308
1158
|
async function executeEnv(parsed, context, api) {
|
|
1309
1159
|
const { org, session } = await resolveOrgScope(parsed, context, api);
|
|
1310
1160
|
switch (parsed.definition.id) {
|
|
1311
1161
|
case "env.list": {
|
|
1312
|
-
const
|
|
1162
|
+
const environment = readCliEnvironmentFlag(parsed);
|
|
1163
|
+
const data = await api.listRuntimeVariables(session, org.id, {
|
|
1164
|
+
...(environment ? { environment } : {})
|
|
1165
|
+
});
|
|
1313
1166
|
return {
|
|
1314
1167
|
data,
|
|
1315
1168
|
human: renderTable(data.variables, [
|
|
1316
1169
|
{ key: "name", label: "Name" },
|
|
1317
1170
|
{ key: "value", label: "Value" },
|
|
1171
|
+
{ key: "environmentKey", label: "Environment" },
|
|
1318
1172
|
{ key: "updatedAt", label: "Updated" }
|
|
1319
1173
|
]),
|
|
1320
1174
|
kind: "authoring_env_list",
|
|
1321
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1175
|
+
meta: { command: parsed.definition.path.join(" "), ...(environment ? { environment } : {}), orgId: org.id }
|
|
1322
1176
|
};
|
|
1323
1177
|
}
|
|
1324
1178
|
case "env.get": {
|
|
1325
|
-
const
|
|
1179
|
+
const environment = readRequiredCliEnvironmentFlag(parsed);
|
|
1180
|
+
const variables = (await api.listRuntimeVariables(session, org.id, {
|
|
1181
|
+
environment
|
|
1182
|
+
})).variables;
|
|
1326
1183
|
const variable = requireFound(variables.find((entry) => entry.name === readRequiredArg(parsed, "name")) ?? null, `Environment variable '${parsed.args.name}' was not found.`, parsed.definition.id);
|
|
1327
1184
|
return {
|
|
1328
1185
|
data: { variable },
|
|
1329
1186
|
human: renderPrettyJson(variable),
|
|
1330
1187
|
kind: "authoring_env_get",
|
|
1331
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1188
|
+
meta: { command: parsed.definition.path.join(" "), environment, orgId: org.id }
|
|
1332
1189
|
};
|
|
1333
1190
|
}
|
|
1334
1191
|
case "env.replace": {
|
|
1192
|
+
const environment = readRequiredCliEnvironmentFlag(parsed);
|
|
1335
1193
|
const body = await readJsonInputSpecifier(String(parsed.flags.file), context.stdin, parsed.definition.id);
|
|
1336
1194
|
const rawVariables = body.variables;
|
|
1337
1195
|
if (!Array.isArray(rawVariables)) {
|
|
@@ -1346,33 +1204,42 @@ async function executeEnv(parsed, context, api) {
|
|
|
1346
1204
|
value: String(entry.value)
|
|
1347
1205
|
};
|
|
1348
1206
|
});
|
|
1349
|
-
const current = (await api.listRuntimeVariables(session, org.id
|
|
1207
|
+
const current = (await api.listRuntimeVariables(session, org.id, {
|
|
1208
|
+
environment
|
|
1209
|
+
})).variables;
|
|
1350
1210
|
const diff = diffVariables(current, nextVariables);
|
|
1211
|
+
announceResolvedEnvironment(parsed, context, environment);
|
|
1351
1212
|
if (!parsed.json) {
|
|
1352
1213
|
context.stdout.write(`${renderDiffSummary(diff)}\n`);
|
|
1353
1214
|
}
|
|
1354
|
-
await confirmDestructive(parsed, context, `Replace
|
|
1355
|
-
const data = await api.replaceRuntimeVariables(session, org.id, {
|
|
1215
|
+
await confirmDestructive(parsed, context, `Replace runtime variables for ${environment} in ${org.slug}?`);
|
|
1216
|
+
const data = await api.replaceRuntimeVariables(session, org.id, {
|
|
1217
|
+
environment,
|
|
1218
|
+
variables: nextVariables
|
|
1219
|
+
});
|
|
1356
1220
|
return {
|
|
1357
1221
|
data,
|
|
1358
|
-
human: renderSuccess(`Replaced ${data.variables.length} environment variables.`),
|
|
1222
|
+
human: renderSuccess(`Replaced ${data.variables.length} environment variables in ${environment}.`),
|
|
1359
1223
|
kind: "authoring_env_replace",
|
|
1360
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1224
|
+
meta: { command: parsed.definition.path.join(" "), environment, orgId: org.id }
|
|
1361
1225
|
};
|
|
1362
1226
|
}
|
|
1363
1227
|
case "env.import": {
|
|
1228
|
+
const environment = readRequiredCliEnvironmentFlag(parsed);
|
|
1364
1229
|
const filePath = readRequiredArg(parsed, "path-to-.env");
|
|
1365
1230
|
const content = await readTextInputSpecifier(`@${filePath}`, context.stdin, parsed.definition.id);
|
|
1366
1231
|
const preview = await parseEnvFile(filePath);
|
|
1232
|
+
announceResolvedEnvironment(parsed, context, environment);
|
|
1367
1233
|
const data = await api.importRuntimeVariables(session, org.id, {
|
|
1368
1234
|
content,
|
|
1235
|
+
environment,
|
|
1369
1236
|
fileName: filePath
|
|
1370
1237
|
});
|
|
1371
1238
|
return {
|
|
1372
1239
|
data,
|
|
1373
|
-
human: renderSuccess(`Imported ${data.importedCount} variables from ${filePath}. Parsed ${preview.length}.`),
|
|
1240
|
+
human: renderSuccess(`Imported ${data.importedCount} variables from ${filePath} into ${environment}. Parsed ${preview.length}.`),
|
|
1374
1241
|
kind: "authoring_env_import",
|
|
1375
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1242
|
+
meta: { command: parsed.definition.path.join(" "), environment, orgId: org.id }
|
|
1376
1243
|
};
|
|
1377
1244
|
}
|
|
1378
1245
|
default:
|
|
@@ -1383,40 +1250,50 @@ async function executeSecrets(parsed, context, api) {
|
|
|
1383
1250
|
const { org, session } = await resolveOrgScope(parsed, context, api);
|
|
1384
1251
|
switch (parsed.definition.id) {
|
|
1385
1252
|
case "secrets.list": {
|
|
1386
|
-
const
|
|
1253
|
+
const environment = readCliEnvironmentFlag(parsed);
|
|
1254
|
+
const data = await api.listOrgSecrets(session, org.id, {
|
|
1255
|
+
...(environment ? { environment } : {})
|
|
1256
|
+
});
|
|
1387
1257
|
return {
|
|
1388
1258
|
data,
|
|
1389
1259
|
human: renderTable(data.secrets, [
|
|
1390
1260
|
{ key: "name", label: "Name" },
|
|
1391
1261
|
{ key: "hasValue", label: "Defined" },
|
|
1262
|
+
{ key: "environmentKey", label: "Environment" },
|
|
1392
1263
|
{ key: "updatedAt", label: "Updated" }
|
|
1393
1264
|
]),
|
|
1394
1265
|
kind: "authoring_secrets_list",
|
|
1395
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1266
|
+
meta: { command: parsed.definition.path.join(" "), ...(environment ? { environment } : {}), orgId: org.id }
|
|
1396
1267
|
};
|
|
1397
1268
|
}
|
|
1398
1269
|
case "secrets.set": {
|
|
1270
|
+
const environment = readRequiredCliEnvironmentFlag(parsed);
|
|
1399
1271
|
const value = await readTextInputSpecifier("-", context.stdin, parsed.definition.id);
|
|
1272
|
+
announceResolvedEnvironment(parsed, context, environment);
|
|
1400
1273
|
const data = await api.upsertOrgSecret(session, org.id, {
|
|
1274
|
+
environment,
|
|
1401
1275
|
name: readRequiredArg(parsed, "name"),
|
|
1402
1276
|
value: value.replace(/\r?\n$/u, "")
|
|
1403
1277
|
});
|
|
1404
1278
|
return {
|
|
1405
1279
|
data,
|
|
1406
|
-
human: renderSuccess(`Saved secret '${data.secret.name}'.`),
|
|
1280
|
+
human: renderSuccess(`Saved secret '${data.secret.name}' in ${environment}.`),
|
|
1407
1281
|
kind: "authoring_secrets_set",
|
|
1408
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1282
|
+
meta: { command: parsed.definition.path.join(" "), environment, orgId: org.id }
|
|
1409
1283
|
};
|
|
1410
1284
|
}
|
|
1411
1285
|
case "secrets.delete": {
|
|
1286
|
+
const environment = readRequiredCliEnvironmentFlag(parsed);
|
|
1412
1287
|
const name = readRequiredArg(parsed, "name");
|
|
1413
|
-
await confirmDestructive(parsed, context, `Delete secret ${name} from ${org.slug}?`);
|
|
1414
|
-
const data = await api.deleteOrgSecret(session, org.id, name
|
|
1288
|
+
await confirmDestructive(parsed, context, `Delete secret ${name} from ${environment} in ${org.slug}?`);
|
|
1289
|
+
const data = await api.deleteOrgSecret(session, org.id, name, {
|
|
1290
|
+
environment
|
|
1291
|
+
});
|
|
1415
1292
|
return {
|
|
1416
1293
|
data,
|
|
1417
|
-
human: renderSuccess(data.deleted ? `Deleted secret '${name}'.` : `Secret '${name}' was not defined.`),
|
|
1294
|
+
human: renderSuccess(data.deleted ? `Deleted secret '${name}' from ${environment}.` : `Secret '${name}' was not defined in ${environment}.`),
|
|
1418
1295
|
kind: "authoring_secrets_delete",
|
|
1419
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1296
|
+
meta: { command: parsed.definition.path.join(" "), environment, orgId: org.id }
|
|
1420
1297
|
};
|
|
1421
1298
|
}
|
|
1422
1299
|
default:
|
|
@@ -1467,62 +1344,6 @@ async function executeChat(parsed, context, api) {
|
|
|
1467
1344
|
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1468
1345
|
};
|
|
1469
1346
|
}
|
|
1470
|
-
case "chat.changes": {
|
|
1471
|
-
const data = await api.getChatChanges(session, org.id, readRequiredArg(parsed, "session-id"));
|
|
1472
|
-
return {
|
|
1473
|
-
data,
|
|
1474
|
-
human: renderPrettyJson(data.changes),
|
|
1475
|
-
kind: "chat_changes_get",
|
|
1476
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1477
|
-
};
|
|
1478
|
-
}
|
|
1479
|
-
case "chat.validation": {
|
|
1480
|
-
const data = await api.getChatValidation(session, org.id, readRequiredArg(parsed, "session-id"));
|
|
1481
|
-
return {
|
|
1482
|
-
data,
|
|
1483
|
-
human: renderPrettyJson(data.validation),
|
|
1484
|
-
kind: "chat_validation_get",
|
|
1485
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1486
|
-
};
|
|
1487
|
-
}
|
|
1488
|
-
case "chat.draft.get": {
|
|
1489
|
-
const data = await api.getChatDraft(session, org.id, readRequiredArg(parsed, "session-id"));
|
|
1490
|
-
return {
|
|
1491
|
-
data,
|
|
1492
|
-
human: renderPrettyJson(data.draft),
|
|
1493
|
-
kind: "chat_draft_get",
|
|
1494
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1495
|
-
};
|
|
1496
|
-
}
|
|
1497
|
-
case "chat.draft.apply": {
|
|
1498
|
-
await confirmDestructive(parsed, context, `Apply draft for session ${parsed.args["session-id"]}?`);
|
|
1499
|
-
const data = await api.applyChatDraft(session, org.id, readRequiredArg(parsed, "session-id"));
|
|
1500
|
-
return {
|
|
1501
|
-
data,
|
|
1502
|
-
human: renderSuccess(`Applied draft for session ${parsed.args["session-id"]}.`),
|
|
1503
|
-
kind: "chat_draft_apply",
|
|
1504
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1505
|
-
};
|
|
1506
|
-
}
|
|
1507
|
-
case "chat.draft.refresh": {
|
|
1508
|
-
await confirmDestructive(parsed, context, `Refresh draft for session ${parsed.args["session-id"]}?`);
|
|
1509
|
-
const data = await api.refreshChatDraft(session, org.id, readRequiredArg(parsed, "session-id"));
|
|
1510
|
-
return {
|
|
1511
|
-
data,
|
|
1512
|
-
human: renderSuccess(`Refreshed draft for session ${parsed.args["session-id"]}.`),
|
|
1513
|
-
kind: "chat_draft_refresh",
|
|
1514
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1515
|
-
};
|
|
1516
|
-
}
|
|
1517
|
-
case "chat.workspace-state": {
|
|
1518
|
-
const data = await api.getChatWorkspaceState(session, org.id, readRequiredArg(parsed, "session-id"));
|
|
1519
|
-
return {
|
|
1520
|
-
data,
|
|
1521
|
-
human: renderPrettyJson(data.workspaceState),
|
|
1522
|
-
kind: "chat_workspace_state_get",
|
|
1523
|
-
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1524
|
-
};
|
|
1525
|
-
}
|
|
1526
1347
|
default:
|
|
1527
1348
|
throw genericProblem(`Unhandled chat command ${parsed.definition.id}.`, parsed.definition.id);
|
|
1528
1349
|
}
|
|
@@ -1569,9 +1390,10 @@ async function executeCompletion(parsed) {
|
|
|
1569
1390
|
}
|
|
1570
1391
|
}
|
|
1571
1392
|
async function executeSchemaGet(parsed) {
|
|
1572
|
-
const
|
|
1393
|
+
const resourceName = readRequiredArg(parsed, "resource-name");
|
|
1394
|
+
const schema = getCliSchema(resourceName);
|
|
1573
1395
|
if (!schema) {
|
|
1574
|
-
throw notFoundProblem(`Schema '${
|
|
1396
|
+
throw notFoundProblem(`Schema '${resourceName}' was not found. Use lowercase schema names, not YAML kind names. Run \`schema list\` to see all schemas. Known names include: ${schemaNamePreview()}.`, "schema get");
|
|
1575
1397
|
}
|
|
1576
1398
|
return {
|
|
1577
1399
|
data: { schema },
|
|
@@ -1589,9 +1411,99 @@ async function executeSchemaGet(parsed) {
|
|
|
1589
1411
|
meta: { command: "schema get" }
|
|
1590
1412
|
};
|
|
1591
1413
|
}
|
|
1414
|
+
function schemaNamePreview() {
|
|
1415
|
+
const names = new Set(listCliSchemas().map((schema) => schema.name));
|
|
1416
|
+
return ["process", "operation", "capability", "decision", "manifest", "organization", "assignments"]
|
|
1417
|
+
.filter((name) => names.has(name))
|
|
1418
|
+
.join(", ");
|
|
1419
|
+
}
|
|
1592
1420
|
async function executeAdmin(parsed, context, api) {
|
|
1593
1421
|
const session = requireStoredSession(context.session, parsed.definition.id);
|
|
1594
1422
|
switch (parsed.definition.id) {
|
|
1423
|
+
case "admin.usage.summary": {
|
|
1424
|
+
const { org, session: scopedSession } = await resolveOrgScope(parsed, context, api);
|
|
1425
|
+
const data = await api.getAgentUsage(scopedSession, org.id, readAgentUsageQuery(parsed));
|
|
1426
|
+
return {
|
|
1427
|
+
data,
|
|
1428
|
+
human: renderAgentUsageSummary(org.id, data.usage),
|
|
1429
|
+
kind: "admin_usage_summary",
|
|
1430
|
+
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
case "admin.usage.workflows": {
|
|
1434
|
+
const { org, session: scopedSession } = await resolveOrgScope(parsed, context, api);
|
|
1435
|
+
const data = await api.getAgentUsage(scopedSession, org.id, readAgentUsageQuery(parsed));
|
|
1436
|
+
return {
|
|
1437
|
+
data,
|
|
1438
|
+
human: renderTable(data.usage.workflows.map((workflow) => ({
|
|
1439
|
+
cost: formatUsdMicros(workflow.usdMicros),
|
|
1440
|
+
executions: formatInteger(workflow.agentExecutionCount),
|
|
1441
|
+
model: formatProviderModel(workflow.provider, workflow.model),
|
|
1442
|
+
tokensIn: formatInteger(workflow.tokensIn),
|
|
1443
|
+
tokensOut: formatInteger(workflow.tokensOut),
|
|
1444
|
+
toolCalls: formatInteger(workflow.toolCallCount),
|
|
1445
|
+
workflow: workflow.processName
|
|
1446
|
+
})), [
|
|
1447
|
+
{ key: "workflow", label: "Workflow" },
|
|
1448
|
+
{ key: "model", label: "Model" },
|
|
1449
|
+
{ key: "executions", label: "Executions" },
|
|
1450
|
+
{ key: "tokensIn", label: "Tokens in" },
|
|
1451
|
+
{ key: "tokensOut", label: "Tokens out" },
|
|
1452
|
+
{ key: "toolCalls", label: "Tool calls" },
|
|
1453
|
+
{ key: "cost", label: "Cost" }
|
|
1454
|
+
]),
|
|
1455
|
+
kind: "admin_usage_workflows",
|
|
1456
|
+
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
case "admin.usage.tools": {
|
|
1460
|
+
const { org, session: scopedSession } = await resolveOrgScope(parsed, context, api);
|
|
1461
|
+
const data = await api.getAgentUsage(scopedSession, org.id, readAgentUsageQuery(parsed));
|
|
1462
|
+
return {
|
|
1463
|
+
data,
|
|
1464
|
+
human: renderTable(data.usage.tools.map((tool) => ({
|
|
1465
|
+
calls: formatInteger(tool.callCount),
|
|
1466
|
+
failed: formatInteger(tool.failureCount),
|
|
1467
|
+
succeeded: formatInteger(tool.successCount),
|
|
1468
|
+
tool: tool.toolName
|
|
1469
|
+
})), [
|
|
1470
|
+
{ key: "tool", label: "Tool" },
|
|
1471
|
+
{ key: "calls", label: "Calls" },
|
|
1472
|
+
{ key: "succeeded", label: "Succeeded" },
|
|
1473
|
+
{ key: "failed", label: "Failed" }
|
|
1474
|
+
]),
|
|
1475
|
+
kind: "admin_usage_tools",
|
|
1476
|
+
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
case "admin.usage.events": {
|
|
1480
|
+
const { org, session: scopedSession } = await resolveOrgScope(parsed, context, api);
|
|
1481
|
+
const data = await api.getAgentUsage(scopedSession, org.id, readAgentUsageQuery(parsed));
|
|
1482
|
+
return {
|
|
1483
|
+
data,
|
|
1484
|
+
human: renderTable(data.usage.recentEvents.map((event) => ({
|
|
1485
|
+
cost: formatUsdMicros(event.usdMicros),
|
|
1486
|
+
event: event.eventId,
|
|
1487
|
+
model: formatProviderModel(event.provider, event.model),
|
|
1488
|
+
occurredAt: event.occurredAt,
|
|
1489
|
+
status: event.sourceStatus === "event_only" ? `${event.status} (event only)` : event.status,
|
|
1490
|
+
tokens: formatInteger(event.tokensIn + event.tokensOut),
|
|
1491
|
+
toolCalls: formatInteger(event.toolCallCount),
|
|
1492
|
+
workflow: event.processName
|
|
1493
|
+
})), [
|
|
1494
|
+
{ key: "event", label: "Event" },
|
|
1495
|
+
{ key: "workflow", label: "Workflow" },
|
|
1496
|
+
{ key: "model", label: "Model" },
|
|
1497
|
+
{ key: "status", label: "Status" },
|
|
1498
|
+
{ key: "tokens", label: "Tokens" },
|
|
1499
|
+
{ key: "toolCalls", label: "Tool calls" },
|
|
1500
|
+
{ key: "cost", label: "Cost" },
|
|
1501
|
+
{ key: "occurredAt", label: "Occurred at" }
|
|
1502
|
+
]),
|
|
1503
|
+
kind: "admin_usage_events",
|
|
1504
|
+
meta: { command: parsed.definition.path.join(" "), orgId: org.id }
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1595
1507
|
case "admin.org.deleted.list": {
|
|
1596
1508
|
const data = await api.listDeletedOrganizations(session);
|
|
1597
1509
|
return {
|
|
@@ -1638,6 +1550,46 @@ async function executeAdmin(parsed, context, api) {
|
|
|
1638
1550
|
throw genericProblem(`Unhandled admin command ${parsed.definition.id}.`, parsed.definition.id);
|
|
1639
1551
|
}
|
|
1640
1552
|
}
|
|
1553
|
+
function readAgentUsageQuery(parsed) {
|
|
1554
|
+
const from = readOptionalStringFlag(parsed, "from");
|
|
1555
|
+
const to = readOptionalStringFlag(parsed, "to");
|
|
1556
|
+
return {
|
|
1557
|
+
...(from ? { from } : {}),
|
|
1558
|
+
...optionalNumberValue("limit", readOptionalNumberFlag(parsed, "limit")),
|
|
1559
|
+
...(to ? { to } : {})
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
function renderAgentUsageSummary(orgId, usage) {
|
|
1563
|
+
const summary = usage.summary;
|
|
1564
|
+
return renderKeyValue([
|
|
1565
|
+
{ label: "Organization", value: orgId },
|
|
1566
|
+
{ label: "From", value: summary.from },
|
|
1567
|
+
{ label: "To", value: summary.to },
|
|
1568
|
+
{ label: "Agent executions", value: formatInteger(summary.agentExecutionCount) },
|
|
1569
|
+
{ label: "Tokens in", value: formatInteger(summary.tokensIn) },
|
|
1570
|
+
{ label: "Tokens out", value: formatInteger(summary.tokensOut) },
|
|
1571
|
+
{ label: "Tool calls", value: formatInteger(summary.toolCallCount) },
|
|
1572
|
+
{ label: "Cost", value: formatUsdMicros(summary.usdMicros) },
|
|
1573
|
+
{ label: "Event-only records", value: formatInteger(summary.sourceEventOnlyCount) }
|
|
1574
|
+
]);
|
|
1575
|
+
}
|
|
1576
|
+
function formatProviderModel(provider, model) {
|
|
1577
|
+
if (provider && model) {
|
|
1578
|
+
return `${provider}/${model}`;
|
|
1579
|
+
}
|
|
1580
|
+
return provider ?? model ?? "";
|
|
1581
|
+
}
|
|
1582
|
+
function formatInteger(value) {
|
|
1583
|
+
return new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 }).format(value);
|
|
1584
|
+
}
|
|
1585
|
+
function formatUsdMicros(value) {
|
|
1586
|
+
return new Intl.NumberFormat(undefined, {
|
|
1587
|
+
currency: "USD",
|
|
1588
|
+
maximumFractionDigits: 4,
|
|
1589
|
+
minimumFractionDigits: value > 0 && value < 10_000 ? 4 : 2,
|
|
1590
|
+
style: "currency"
|
|
1591
|
+
}).format(value / 1_000_000);
|
|
1592
|
+
}
|
|
1641
1593
|
async function resolveOrgScope(parsed, context, api) {
|
|
1642
1594
|
const session = requireStoredSession(context.session, parsed.definition.id);
|
|
1643
1595
|
const selector = readOptionalStringFlag(parsed, "org");
|
|
@@ -1675,16 +1627,109 @@ function isSameSessionIdentity(candidate, session) {
|
|
|
1675
1627
|
&& candidate.user.id === session.user.id
|
|
1676
1628
|
&& candidate.accessTokenPayload.sub === session.accessTokenPayload.sub;
|
|
1677
1629
|
}
|
|
1678
|
-
function readOptionalMcpAuthMode(parsed) {
|
|
1679
|
-
const value = readOptionalStringFlag(parsed, "auth-mode");
|
|
1680
|
-
if (value === undefined || value === "none" || value === "oauth") {
|
|
1681
|
-
return value;
|
|
1682
|
-
}
|
|
1683
|
-
throw usageProblem("MCP auth mode must be 'none' or 'oauth'.", parsed.definition.id);
|
|
1684
|
-
}
|
|
1685
1630
|
function optionalNumberValue(key, value) {
|
|
1686
1631
|
return value !== undefined ? { [key]: value } : {};
|
|
1687
1632
|
}
|
|
1633
|
+
function withCliSummary(meta, summary) {
|
|
1634
|
+
const normalized = normalizeCliSummary(summary);
|
|
1635
|
+
return normalized ? { ...meta, summary: normalized } : meta;
|
|
1636
|
+
}
|
|
1637
|
+
function normalizeCliSummary(value) {
|
|
1638
|
+
const trimmed = value?.trim();
|
|
1639
|
+
return trimmed && trimmed.length > 0 ? trimmed : null;
|
|
1640
|
+
}
|
|
1641
|
+
function summarizeWorkflowNodeTest(test) {
|
|
1642
|
+
if (test.status === "passed") {
|
|
1643
|
+
const capturedArtifacts = Array.isArray(test.capturedArtifacts)
|
|
1644
|
+
? test.capturedArtifacts.length
|
|
1645
|
+
: 0;
|
|
1646
|
+
return `Workflow-node test passed${capturedArtifacts > 0 ? ` with ${String(capturedArtifacts)} captured artifact${capturedArtifacts === 1 ? "" : "s"}` : ""}.`;
|
|
1647
|
+
}
|
|
1648
|
+
const label = test.status === "invalid" ? "invalid" : "failed";
|
|
1649
|
+
const error = recordField(test, "error");
|
|
1650
|
+
const message = typeof error.message === "string" && error.message.trim().length > 0
|
|
1651
|
+
? truncateSummaryText(error.message)
|
|
1652
|
+
: firstDiagnosticMessage(readSummaryDiagnostics(test));
|
|
1653
|
+
return `Workflow-node test ${label}${message ? `: ${message}` : ""}.`;
|
|
1654
|
+
}
|
|
1655
|
+
function renderWorkflowNodeTestHuman(test) {
|
|
1656
|
+
const summary = summarizeWorkflowNodeTest(test);
|
|
1657
|
+
const capturedArtifacts = Array.isArray(test.capturedArtifacts)
|
|
1658
|
+
? test.capturedArtifacts.length
|
|
1659
|
+
: 0;
|
|
1660
|
+
const logs = Array.isArray(test.logs)
|
|
1661
|
+
? test.logs.length
|
|
1662
|
+
: 0;
|
|
1663
|
+
const rows = [
|
|
1664
|
+
{ label: "Status", value: test.status },
|
|
1665
|
+
{ label: "Workflow", value: test.workflowName },
|
|
1666
|
+
{ label: "Node", value: test.nodeId },
|
|
1667
|
+
...(typeof test.operationName === "string"
|
|
1668
|
+
? [{ label: "Operation", value: test.operationName }]
|
|
1669
|
+
: []),
|
|
1670
|
+
{ label: "Captured artifacts", value: capturedArtifacts },
|
|
1671
|
+
{ label: "Logs", value: logs > 0 ? `${String(logs)} stream${logs === 1 ? "" : "s"} available` : "none" }
|
|
1672
|
+
];
|
|
1673
|
+
const diagnostics = readSummaryDiagnostics(test);
|
|
1674
|
+
const error = recordField(test, "error");
|
|
1675
|
+
const actionable = typeof error.message === "string" && error.message.trim().length > 0
|
|
1676
|
+
? truncateSummaryText(error.message)
|
|
1677
|
+
: firstDiagnosticMessage(diagnostics);
|
|
1678
|
+
return [
|
|
1679
|
+
summary,
|
|
1680
|
+
renderKeyValue(rows),
|
|
1681
|
+
...(actionable && test.status !== "passed" ? [`Action: ${actionable}`] : [])
|
|
1682
|
+
].filter((entry) => entry.trim().length > 0).join("\n");
|
|
1683
|
+
}
|
|
1684
|
+
function summarizeReleaseValidation(validation) {
|
|
1685
|
+
const diagnostics = readSummaryDiagnostics(validation);
|
|
1686
|
+
return validation.releaseReady === true
|
|
1687
|
+
? `Release validation passed${formatDiagnosticSummarySuffix(diagnostics)}.`
|
|
1688
|
+
: `Release validation failed${formatDiagnosticSummarySuffix(diagnostics)}.`;
|
|
1689
|
+
}
|
|
1690
|
+
function summarizeReleaseCreation(releaseId) {
|
|
1691
|
+
return `Release ${releaseId} created. It is not live yet; review and deploy it from /app/releases/${releaseId}.`;
|
|
1692
|
+
}
|
|
1693
|
+
function readSummaryDiagnostics(record) {
|
|
1694
|
+
const diagnostics = record.diagnostics;
|
|
1695
|
+
return Array.isArray(diagnostics)
|
|
1696
|
+
? diagnostics.filter((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry))
|
|
1697
|
+
: [];
|
|
1698
|
+
}
|
|
1699
|
+
function formatDiagnosticSummarySuffix(diagnostics) {
|
|
1700
|
+
const errorCount = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
|
|
1701
|
+
const warningCount = diagnostics.length - errorCount;
|
|
1702
|
+
const parts = [
|
|
1703
|
+
...(errorCount > 0 ? [formatDiagnosticCount(errorCount, "error")] : []),
|
|
1704
|
+
...(warningCount > 0 ? [formatDiagnosticCount(warningCount, "warning")] : [])
|
|
1705
|
+
];
|
|
1706
|
+
if (parts.length === 0) {
|
|
1707
|
+
return "";
|
|
1708
|
+
}
|
|
1709
|
+
const message = firstDiagnosticMessage(diagnostics);
|
|
1710
|
+
return ` with ${parts.join(" and ")}${message ? `: ${message}` : ""}`;
|
|
1711
|
+
}
|
|
1712
|
+
function formatDiagnosticCount(count, label) {
|
|
1713
|
+
return `${String(count)} ${label}${count === 1 ? "" : "s"}`;
|
|
1714
|
+
}
|
|
1715
|
+
function firstDiagnosticMessage(diagnostics) {
|
|
1716
|
+
for (const diagnostic of diagnostics) {
|
|
1717
|
+
if (typeof diagnostic.message === "string" && diagnostic.message.trim().length > 0) {
|
|
1718
|
+
return truncateSummaryText(diagnostic.message);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
return null;
|
|
1722
|
+
}
|
|
1723
|
+
function truncateSummaryText(value) {
|
|
1724
|
+
const trimmed = value.trim();
|
|
1725
|
+
return trimmed.length <= 180 ? trimmed : `${trimmed.slice(0, 177)}...`;
|
|
1726
|
+
}
|
|
1727
|
+
function recordField(result, key) {
|
|
1728
|
+
const value = result[key];
|
|
1729
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
1730
|
+
? value
|
|
1731
|
+
: {};
|
|
1732
|
+
}
|
|
1688
1733
|
function readRequiredArg(parsed, name) {
|
|
1689
1734
|
const value = parsed.args[name];
|
|
1690
1735
|
if (!value) {
|
|
@@ -1692,6 +1737,11 @@ function readRequiredArg(parsed, name) {
|
|
|
1692
1737
|
}
|
|
1693
1738
|
return value;
|
|
1694
1739
|
}
|
|
1740
|
+
function announceResolvedEnvironment(parsed, context, environment) {
|
|
1741
|
+
if (!parsed.json) {
|
|
1742
|
+
context.stdout.write(`Environment: ${environment}\n`);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1695
1745
|
function readRequiredIntegerArg(parsed, name) {
|
|
1696
1746
|
const raw = readRequiredArg(parsed, name);
|
|
1697
1747
|
const parsedValue = Number(raw);
|