@kora-platform/cli 0.8.0-rc1 → 0.8.0-rc10

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.
@@ -1,11 +1,22 @@
1
1
  import type { AbortRuntimeRunResult, AgentUsageReport, ApiKeyRecord, AuditEventListResult, AuditEventRecord, AuditOutcome, AuthoringOrganizationManagementContext, CapabilityEditorRecord, ChatHealth, ChatSessionRecord, ChatSessionSummary, CreateReleaseResult, CompleteRuntimeTaskResult, DeletedOrganizationDetail, DeletedOrganizationSummary, EnvironmentDeploymentRecord, EnvironmentDetailRecord, EnvironmentRecord, BuiltInExtensionRecord, ExtensionInstallDetailRecord, ExtensionInstallRecord, ExtensionInstallUpdatePlan, ExtensionPackageExportEnvelope, ExtensionPackageFileInput, ExtensionPackageRecord, ExtensionPackageRevisionRecord, ExtensionPackageValidationResult, ExtensionPermissions, InviteRecord, MembershipRecord, OperationsEditorRecord, WorkflowNodeTestResult, OrganizationRecord, OrganizationWithRoleRecord, OrgSecretRecord, OrgSettingsRecord, ProcessDefinitionDetail, ProcessDefinitionRecord, ProcessDefinitionSummary, ReleaseSourceSnapshotRecord, ReleaseValidationResult, ReleaseSummaryRecord, RuntimeControlActivityFocusType, RuntimeControlActivityReadResult, RuntimeControlOverviewRecord, RuntimeControlRunDetailRecord, RuntimeControlRunRecord, RuntimeControlRunStateRecord, RuntimeControlRunStepRecord, RuntimeControlStartProcessResult, RuntimeControlTaskRecord, RuntimeVariableRecord, WorkflowDependencySummary, WorkflowInspectionContext } from "./api-types.js";
2
2
  import type { CliSessionState } from "./transport.js";
3
3
  import { createPlatformTransport } from "./transport.js";
4
+ export type ReleaseSnapshotSelectorInput = {
5
+ environment: string;
6
+ releaseId?: never;
7
+ } | {
8
+ environment?: never;
9
+ releaseId: string;
10
+ };
4
11
  export interface PlatformApiClientInput {
5
12
  now?: () => number;
6
13
  sessionStore: Parameters<typeof createPlatformTransport>[0]["sessionStore"];
7
14
  }
8
15
  export declare function createPlatformApiClient(input: PlatformApiClientInput): {
16
+ claimDeviceLogin: (claim: {
17
+ baseUrl: string;
18
+ deviceCode: string;
19
+ }) => Promise<import("./transport.js").CliDeviceLoginClaim>;
9
20
  getAuthSettings: (baseUrl: string) => Promise<import("./transport.js").CliAuthSettings>;
10
21
  login: (credentials: {
11
22
  baseUrl: string;
@@ -19,6 +30,7 @@ export declare function createPlatformApiClient(input: PlatformApiClientInput):
19
30
  name: string;
20
31
  password: string;
21
32
  }) => Promise<CliSessionState>;
33
+ startDeviceLogin: (baseUrl: string) => Promise<import("./transport.js").CliDeviceLoginStart>;
22
34
  getMe(session: CliSessionState): Promise<{
23
35
  user: {
24
36
  email: string;
@@ -84,13 +96,12 @@ export declare function createPlatformApiClient(input: PlatformApiClientInput):
84
96
  }): Promise<{
85
97
  variables: RuntimeVariableRecord[];
86
98
  }>;
87
- importRuntimeVariables(session: CliSessionState, orgId: string, inputData: {
88
- content: string;
99
+ upsertRuntimeVariable(session: CliSessionState, orgId: string, inputData: {
89
100
  environment: string;
90
- fileName?: string | null;
101
+ name: string;
102
+ value: string;
91
103
  }): Promise<{
92
- importedCount: number;
93
- variables: RuntimeVariableRecord[];
104
+ variable: RuntimeVariableRecord;
94
105
  }>;
95
106
  listOrgSecrets(session: CliSessionState, orgId: string, inputData?: {
96
107
  environment?: string;
@@ -224,19 +235,23 @@ export declare function createPlatformApiClient(input: PlatformApiClientInput):
224
235
  deleteExtensionInstall(session: CliSessionState, orgId: string, installRef: string, inputData: {
225
236
  environment: string;
226
237
  }): Promise<void>;
227
- listWorkflows(session: CliSessionState, orgId: string, releaseId?: string): Promise<{
238
+ listWorkflows(session: CliSessionState, orgId: string, inputData: {
239
+ environment?: never;
240
+ live: true;
241
+ releaseId?: never;
242
+ } | ReleaseSnapshotSelectorInput): Promise<{
228
243
  processes: ProcessDefinitionSummary[];
229
244
  }>;
230
- getWorkflow(session: CliSessionState, orgId: string, name: string, releaseId?: string): Promise<{
245
+ getWorkflow(session: CliSessionState, orgId: string, name: string, selector: ReleaseSnapshotSelectorInput): Promise<{
231
246
  process: ProcessDefinitionDetail;
232
247
  }>;
233
- getWorkflowVersion(session: CliSessionState, orgId: string, name: string, version: number, releaseId?: string): Promise<{
248
+ getWorkflowVersion(session: CliSessionState, orgId: string, name: string, version: number, selector: ReleaseSnapshotSelectorInput): Promise<{
234
249
  process: ProcessDefinitionRecord;
235
250
  }>;
236
- getWorkflowDependencies(session: CliSessionState, orgId: string, name: string, version: number, releaseId?: string): Promise<{
251
+ getWorkflowDependencies(session: CliSessionState, orgId: string, name: string, version: number, selector: ReleaseSnapshotSelectorInput): Promise<{
237
252
  dependencies: WorkflowDependencySummary;
238
253
  }>;
239
- getWorkflowContext(session: CliSessionState, orgId: string, releaseId?: string): Promise<{
254
+ getWorkflowContext(session: CliSessionState, orgId: string, selector: ReleaseSnapshotSelectorInput): Promise<{
240
255
  context: WorkflowInspectionContext;
241
256
  }>;
242
257
  startWorkflow(session: CliSessionState, orgId: string, name: string, inputData?: {
@@ -250,13 +265,13 @@ export declare function createPlatformApiClient(input: PlatformApiClientInput):
250
265
  }): Promise<{
251
266
  started: RuntimeControlStartProcessResult;
252
267
  }>;
253
- getOrganizationManagementContext(session: CliSessionState, orgId: string): Promise<{
268
+ getOrganizationManagementContext(session: CliSessionState, orgId: string, selector: ReleaseSnapshotSelectorInput): Promise<{
254
269
  context: AuthoringOrganizationManagementContext;
255
270
  }>;
256
- getCapabilityEditor(session: CliSessionState, orgId: string, name: string): Promise<{
271
+ getCapabilityEditor(session: CliSessionState, orgId: string, name: string, selector: ReleaseSnapshotSelectorInput): Promise<{
257
272
  editor: CapabilityEditorRecord;
258
273
  }>;
259
- getOperationsEditor(session: CliSessionState, orgId: string): Promise<{
274
+ getOperationsEditor(session: CliSessionState, orgId: string, selector: ReleaseSnapshotSelectorInput): Promise<{
260
275
  editor: OperationsEditorRecord;
261
276
  }>;
262
277
  getOverview(session: CliSessionState, orgId: string): Promise<{
@@ -349,9 +364,7 @@ export declare function createPlatformApiClient(input: PlatformApiClientInput):
349
364
  listEnvironments(session: CliSessionState, orgId: string): Promise<{
350
365
  environments: EnvironmentDetailRecord[];
351
366
  }>;
352
- getEnvironment(session: CliSessionState, orgId: string, environment: string): Promise<{
353
- environment: EnvironmentDetailRecord;
354
- }>;
367
+ getEnvironment(session: CliSessionState, orgId: string, environment: string): Promise<EnvironmentDetailRecord>;
355
368
  createEnvironment(session: CliSessionState, orgId: string, inputData: {
356
369
  copyFromEnvironmentId?: string;
357
370
  key: string;
@@ -394,12 +407,10 @@ export declare function createPlatformApiClient(input: PlatformApiClientInput):
394
407
  content: string;
395
408
  path: string;
396
409
  }>;
397
- label?: string;
398
410
  }): Promise<{
399
411
  created: CreateReleaseResult;
400
412
  }>;
401
413
  createReleaseArchive(session: CliSessionState, orgId: string, archive: Uint8Array, inputData?: {
402
- label?: string;
403
414
  subdir?: string;
404
415
  }): Promise<{
405
416
  created: CreateReleaseResult;
@@ -5,10 +5,12 @@ export function createPlatformApiClient(input) {
5
5
  const artifactApi = createArtifactApiClient(transport);
6
6
  return {
7
7
  ...artifactApi,
8
+ claimDeviceLogin: transport.claimDeviceLogin,
8
9
  getAuthSettings: transport.getAuthSettings,
9
10
  login: transport.login,
10
11
  refreshSession: transport.refreshSession,
11
12
  signup: transport.signup,
13
+ startDeviceLogin: transport.startDeviceLogin,
12
14
  async getMe(session) {
13
15
  return transport.requestJson({
14
16
  path: "/api/v1/auth/me",
@@ -96,12 +98,12 @@ export function createPlatformApiClient(input) {
96
98
  session
97
99
  });
98
100
  },
99
- async importRuntimeVariables(session, orgId, inputData) {
100
- const { environment, ...body } = inputData;
101
+ async upsertRuntimeVariable(session, orgId, inputData) {
102
+ const { environment, name, value } = inputData;
101
103
  return transport.requestJson({
102
- body,
103
- method: "POST",
104
- path: withQuery(`/api/v1/orgs/${encodeURIComponent(orgId)}/runtime-variables/import`, { environment }),
104
+ body: { value },
105
+ method: "PUT",
106
+ path: withQuery(`/api/v1/orgs/${encodeURIComponent(orgId)}/runtime-variables/${encodeURIComponent(name)}`, { environment }),
105
107
  session
106
108
  });
107
109
  },
@@ -264,37 +266,36 @@ export function createPlatformApiClient(input) {
264
266
  session
265
267
  });
266
268
  },
267
- async listWorkflows(session, orgId, releaseId) {
269
+ async listWorkflows(session, orgId, inputData) {
268
270
  return transport.requestJson({
269
271
  path: withQuery(`/api/v1/orgs/${encodeURIComponent(orgId)}/processes`, {
270
- releaseId
272
+ environment: inputData.environment,
273
+ releaseId: inputData.releaseId
271
274
  }),
272
275
  session
273
276
  });
274
277
  },
275
- async getWorkflow(session, orgId, name, releaseId) {
278
+ async getWorkflow(session, orgId, name, selector) {
276
279
  return transport.requestJson({
277
- path: withQuery(`/api/v1/orgs/${encodeURIComponent(orgId)}/processes/${encodeURIComponent(name)}`, { releaseId }),
280
+ path: withQuery(`/api/v1/orgs/${encodeURIComponent(orgId)}/processes/${encodeURIComponent(name)}`, selector),
278
281
  session
279
282
  });
280
283
  },
281
- async getWorkflowVersion(session, orgId, name, version, releaseId) {
284
+ async getWorkflowVersion(session, orgId, name, version, selector) {
282
285
  return transport.requestJson({
283
- path: withQuery(`/api/v1/orgs/${encodeURIComponent(orgId)}/processes/${encodeURIComponent(name)}/versions/${String(version)}`, { releaseId }),
286
+ path: withQuery(`/api/v1/orgs/${encodeURIComponent(orgId)}/processes/${encodeURIComponent(name)}/versions/${String(version)}`, selector),
284
287
  session
285
288
  });
286
289
  },
287
- async getWorkflowDependencies(session, orgId, name, version, releaseId) {
290
+ async getWorkflowDependencies(session, orgId, name, version, selector) {
288
291
  return transport.requestJson({
289
- path: withQuery(`/api/v1/orgs/${encodeURIComponent(orgId)}/processes/${encodeURIComponent(name)}/versions/${String(version)}/dependencies`, { releaseId }),
292
+ path: withQuery(`/api/v1/orgs/${encodeURIComponent(orgId)}/processes/${encodeURIComponent(name)}/versions/${String(version)}/dependencies`, selector),
290
293
  session
291
294
  });
292
295
  },
293
- async getWorkflowContext(session, orgId, releaseId) {
296
+ async getWorkflowContext(session, orgId, selector) {
294
297
  return transport.requestJson({
295
- path: withQuery(`/api/v1/orgs/${encodeURIComponent(orgId)}/workflows/context`, {
296
- releaseId
297
- }),
298
+ path: withQuery(`/api/v1/orgs/${encodeURIComponent(orgId)}/workflows/context`, selector),
298
299
  session
299
300
  });
300
301
  },
@@ -306,21 +307,21 @@ export function createPlatformApiClient(input) {
306
307
  session
307
308
  });
308
309
  },
309
- async getOrganizationManagementContext(session, orgId) {
310
+ async getOrganizationManagementContext(session, orgId, selector) {
310
311
  return transport.requestJson({
311
- path: `/api/v1/orgs/${encodeURIComponent(orgId)}/organization/management`,
312
+ path: withQuery(`/api/v1/orgs/${encodeURIComponent(orgId)}/organization/management`, selector),
312
313
  session
313
314
  });
314
315
  },
315
- async getCapabilityEditor(session, orgId, name) {
316
+ async getCapabilityEditor(session, orgId, name, selector) {
316
317
  return transport.requestJson({
317
- path: `/api/v1/orgs/${encodeURIComponent(orgId)}/capabilities/editor/${encodeURIComponent(name)}`,
318
+ path: withQuery(`/api/v1/orgs/${encodeURIComponent(orgId)}/capabilities/editor/${encodeURIComponent(name)}`, selector),
318
319
  session
319
320
  });
320
321
  },
321
- async getOperationsEditor(session, orgId) {
322
+ async getOperationsEditor(session, orgId, selector) {
322
323
  return transport.requestJson({
323
- path: `/api/v1/orgs/${encodeURIComponent(orgId)}/operations/editor`,
324
+ path: withQuery(`/api/v1/orgs/${encodeURIComponent(orgId)}/operations/editor`, selector),
324
325
  session
325
326
  });
326
327
  },
@@ -290,6 +290,13 @@ export interface AgentUsageReport extends CliRecord {
290
290
  }
291
291
  export interface RuntimeControlDeploymentRecord extends CliRecord {
292
292
  }
293
+ export interface WorkflowFailureSummary extends CliRecord {
294
+ causeChain: string[];
295
+ rootCause: string;
296
+ rootCauseCode?: string;
297
+ summary: string;
298
+ summaryCode?: string;
299
+ }
293
300
  export interface EnvironmentRecord extends CliRecord {
294
301
  currentDeploymentId?: string | null;
295
302
  id: string;
@@ -319,9 +326,22 @@ export interface EnvironmentDetailRecord extends CliRecord {
319
326
  }
320
327
  export interface RuntimeControlOverviewRecord extends CliRecord {
321
328
  }
322
- export interface RuntimeControlRunDetailRecord extends CliRecord {
323
- }
324
329
  export interface RuntimeControlRunRecord extends CliRecord {
330
+ closeTime?: string | null;
331
+ deploymentId?: string | null;
332
+ environmentKey?: string | null;
333
+ failure?: WorkflowFailureSummary;
334
+ releaseId?: string | null;
335
+ startTime?: string;
336
+ status: string;
337
+ workflowId: string;
338
+ }
339
+ export interface RuntimeControlRunDetailRecord extends RuntimeControlRunRecord {
340
+ failureLookupError?: string;
341
+ historyLength?: number;
342
+ historySizeBytes?: number;
343
+ state?: CliRecord | null;
344
+ stateQueryError?: string;
325
345
  }
326
346
  export interface RuntimeControlRunStateRecord extends CliRecord {
327
347
  }
@@ -1,15 +1,17 @@
1
- import { readFile, writeFile } from "node:fs/promises";
1
+ import { writeFile } from "node:fs/promises";
2
2
  import { basename, resolve } from "node:path";
3
3
  import { TextDecoder } from "node:util";
4
4
  import { genericProblem, usageProblem } from "./cli-errors.js";
5
5
  import { readOptionalNumberFlag, readOptionalStringFlag, readRequiredStringFlag } from "./command-flags.js";
6
6
  import { renderKeyValue, renderSuccess, renderTable } from "./format.js";
7
+ import { readLocalFileBytes } from "./files.js";
7
8
  import { confirmDestructive } from "./interaction.js";
8
9
  export async function executeArtifactUpload(parsed, context, api, resolveOrgScope) {
9
10
  const { org, session } = await resolveOrgScope(parsed, context, api);
10
11
  const pathValue = readRequiredArg(parsed, "path");
11
- const filePath = resolve(pathValue);
12
- const fileBytes = await readFile(filePath);
12
+ const { absolutePath: filePath, bytes: fileBytes } = await readLocalFileBytes(pathValue, parsed.definition.id, {
13
+ regularFileMessage: "Artifact upload path must be a regular file, not a directory or symbolic link."
14
+ });
13
15
  const data = await api.uploadArtifact(session, org.id, {
14
16
  body: new Uint8Array(fileBytes),
15
17
  ...(readOptionalStringFlag(parsed, "sha256") ? { expectedSha256: readOptionalStringFlag(parsed, "sha256") } : {}),
@@ -1,9 +1,9 @@
1
- import { authProblem, toProblem } from "./cli-errors.js";
1
+ import { authProblem, toProblem, usageProblem } from "./cli-errors.js";
2
2
  import { readOptionalStringFlag } from "./command-flags.js";
3
3
  import { writeCliConfig, readCliConfig, findRepoConfigPath } from "./config.js";
4
4
  import { renderKeyValue, renderSuccess } from "./format.js";
5
5
  import { chooseOrganization, ensureInteractive } from "./interaction.js";
6
- import { promptPassword, promptText } from "./prompts.js";
6
+ import { isInteractive, promptPassword, promptText } from "./prompts.js";
7
7
  export async function executeAuthLogin(parsed, context, api) {
8
8
  const prompts = parsed.json
9
9
  ? {
@@ -11,6 +11,9 @@ export async function executeAuthLogin(parsed, context, api) {
11
11
  stdout: context.stderr
12
12
  }
13
13
  : context;
14
+ if (parsed.flags.device === true || !isInteractive(prompts)) {
15
+ return executeDeviceLogin(parsed, context, api, prompts);
16
+ }
14
17
  ensureInteractive(prompts, "auth login");
15
18
  const resolvedBaseUrl = await resolveAuthBaseUrl(parsed, context, prompts);
16
19
  const { baseUrl } = resolvedBaseUrl;
@@ -25,8 +28,8 @@ export async function executeAuthLogin(parsed, context, api) {
25
28
  },
26
29
  human: [
27
30
  "SSO is required for this deployment.",
28
- `Open this URL in a browser: ${oidcLoginUrl}`,
29
- "The CLI does not complete browser SSO login in this release. API keys remain the supported non-browser CLI auth path."
31
+ "Run `kora auth login --device` to approve this login in the browser instead.",
32
+ `SSO sign-in URL: ${oidcLoginUrl}`
30
33
  ].join("\n"),
31
34
  kind: "access_auth_login_sso_required",
32
35
  meta: {
@@ -90,8 +93,8 @@ export async function executeAuthSignup(parsed, context, api) {
90
93
  },
91
94
  human: [
92
95
  "Local account signup is disabled for this deployment.",
93
- `Open this URL in a browser: ${oidcLoginUrl}`,
94
- "Account creation is managed by SSO for this deployment."
96
+ `Account creation is managed by SSO. Sign in once in a browser: ${oidcLoginUrl}`,
97
+ "Then run `kora auth login --device` to approve a CLI login in the browser."
95
98
  ].join("\n"),
96
99
  kind: "access_auth_signup_sso_required",
97
100
  meta: {
@@ -136,13 +139,86 @@ export async function executeAuthSignup(parsed, context, api) {
136
139
  }
137
140
  };
138
141
  }
139
- async function resolveAuthBaseUrl(parsed, context, prompts) {
142
+ async function executeDeviceLogin(parsed, context, api, prompts) {
143
+ const resolvedBaseUrl = await resolveAuthBaseUrl(parsed, context, prompts, {
144
+ allowPrompt: isInteractive(prompts)
145
+ });
146
+ const { baseUrl } = resolvedBaseUrl;
147
+ const started = await api.startDeviceLogin(baseUrl);
148
+ const verificationUrl = buildDeviceVerificationUrl(baseUrl, started.verificationPath, started.userCode);
149
+ prompts.stdout.write(`Open this URL in a browser to approve the login:\n\n ${verificationUrl}\n\n`);
150
+ prompts.stdout.write(`Confirmation code: ${started.userCode}\n`);
151
+ prompts.stdout.write("Waiting for browser approval...\n");
152
+ const expiresAtMs = Date.parse(started.expiresAt);
153
+ const pollIntervalMs = Math.max(0, started.pollIntervalSeconds * 1000);
154
+ let session = null;
155
+ while (Date.now() < expiresAtMs) {
156
+ const claim = await api.claimDeviceLogin({ baseUrl, deviceCode: started.deviceCode });
157
+ if (claim.status === "approved") {
158
+ session = claim.session;
159
+ break;
160
+ }
161
+ if (claim.status === "denied") {
162
+ throw authProblem("Device login was denied in the browser.", "auth login");
163
+ }
164
+ if (claim.status === "expired") {
165
+ break;
166
+ }
167
+ await sleep(pollIntervalMs);
168
+ }
169
+ if (!session) {
170
+ throw authProblem("Device login expired before it was approved. Run `kora auth login` again.", "auth login");
171
+ }
172
+ const nextSession = await writeSessionWithSelectedOrg({
173
+ allowInteractiveOrgChoice: isInteractive(prompts),
174
+ api,
175
+ context,
176
+ parsed,
177
+ prompts,
178
+ session
179
+ });
180
+ if (resolvedBaseUrl.shouldPersistGlobalBaseUrl) {
181
+ await writeCliConfig(context.configPath, {
182
+ ...resolvedBaseUrl.persistedGlobalConfig,
183
+ baseUrl
184
+ });
185
+ }
186
+ return {
187
+ data: {
188
+ activeOrg: nextSession.activeOrg,
189
+ method: "device",
190
+ user: nextSession.user
191
+ },
192
+ human: renderSuccess(nextSession.activeOrg
193
+ ? `Logged in as ${nextSession.user.email}. Active org: ${nextSession.activeOrg.slug}.`
194
+ : `Logged in as ${nextSession.user.email}. No active organization selected; use \`kora org select <org>\` or \`kora org create\`.`),
195
+ kind: "access_auth_login",
196
+ meta: {
197
+ command: "auth login",
198
+ orgId: nextSession.activeOrg?.id ?? null
199
+ }
200
+ };
201
+ }
202
+ function buildDeviceVerificationUrl(baseUrl, verificationPath, userCode) {
203
+ const normalizedBaseUrl = baseUrl.replace(/\/+$/u, "");
204
+ const normalizedPath = verificationPath.startsWith("/") ? verificationPath : `/${verificationPath}`;
205
+ return `${normalizedBaseUrl}${normalizedPath}?code=${encodeURIComponent(userCode)}`;
206
+ }
207
+ function sleep(ms) {
208
+ return new Promise((resolve) => {
209
+ setTimeout(resolve, ms);
210
+ });
211
+ }
212
+ async function resolveAuthBaseUrl(parsed, context, prompts, options = { allowPrompt: true }) {
140
213
  const persistedGlobalConfig = await readCliConfig(context.configPath);
141
214
  const repoConfigPath = await findRepoConfigPath(context.cwd);
142
215
  const repoConfig = repoConfigPath ? await readCliConfig(repoConfigPath) : {};
143
216
  const flagBaseUrl = readOptionalStringFlag(parsed, "base-url");
144
217
  const envBaseUrl = context.env.KORA_BASE_URL;
145
218
  const configuredBaseUrl = flagBaseUrl ?? envBaseUrl ?? repoConfig.baseUrl ?? persistedGlobalConfig.baseUrl;
219
+ if (!configuredBaseUrl && !options.allowPrompt) {
220
+ throw usageProblem("Pass --base-url <url> or set KORA_BASE_URL to log in from a non-interactive shell.", "auth login");
221
+ }
146
222
  const baseUrl = configuredBaseUrl ?? await promptText("Platform base URL: ", prompts);
147
223
  return {
148
224
  baseUrl,
@@ -154,7 +230,7 @@ async function writeSessionWithSelectedOrg(input) {
154
230
  const organizations = (await input.api.listOrganizations(input.session)).organizations;
155
231
  const activeOrg = organizations.length === 1
156
232
  ? organizations[0] ?? null
157
- : organizations.length > 1
233
+ : organizations.length > 1 && (input.allowInteractiveOrgChoice ?? true)
158
234
  ? await chooseOrganization(organizations, input.prompts, input.parsed)
159
235
  : null;
160
236
  const latestSession = await readLatestSessionForWrite(input.context.sessionStore, input.session);
@@ -1,13 +1,18 @@
1
1
  import { ApiError } from "./transport.js";
2
+ type CliProblemDetails = Record<string, unknown>;
2
3
  export declare class CliProblem extends Error {
4
+ readonly code: string;
3
5
  readonly detail: string;
6
+ readonly details?: CliProblemDetails;
4
7
  readonly exitCode: number;
5
8
  readonly instance: string;
6
9
  readonly status: number;
7
10
  readonly title: string;
8
11
  readonly type: string;
9
12
  constructor(input: {
13
+ code: string;
10
14
  detail: string;
15
+ details?: CliProblemDetails;
11
16
  exitCode: number;
12
17
  instance: string;
13
18
  status: number;
@@ -15,9 +20,10 @@ export declare class CliProblem extends Error {
15
20
  type: string;
16
21
  });
17
22
  }
18
- export declare function usageProblem(detail: string, instance: string): CliProblem;
23
+ export declare function usageProblem(detail: string, instance: string, details?: CliProblemDetails): CliProblem;
19
24
  export declare function authProblem(detail: string, instance: string): CliProblem;
20
25
  export declare function notFoundProblem(detail: string, instance: string): CliProblem;
21
26
  export declare function genericProblem(detail: string, instance: string): CliProblem;
22
27
  export declare function toProblem(error: unknown, instance: string): CliProblem | ApiError;
23
28
  export declare function exitCodeFromError(error: CliProblem | ApiError): number;
29
+ export {};
@@ -1,6 +1,8 @@
1
1
  import { ApiError } from "./transport.js";
2
2
  export class CliProblem extends Error {
3
+ code;
3
4
  detail;
5
+ details;
4
6
  exitCode;
5
7
  instance;
6
8
  status;
@@ -9,7 +11,11 @@ export class CliProblem extends Error {
9
11
  constructor(input) {
10
12
  super(input.detail);
11
13
  this.name = "CliProblem";
14
+ this.code = input.code;
12
15
  this.detail = input.detail;
16
+ if (input.details) {
17
+ this.details = input.details;
18
+ }
13
19
  this.exitCode = input.exitCode;
14
20
  this.instance = input.instance;
15
21
  this.status = input.status;
@@ -17,9 +23,11 @@ export class CliProblem extends Error {
17
23
  this.type = input.type;
18
24
  }
19
25
  }
20
- export function usageProblem(detail, instance) {
26
+ export function usageProblem(detail, instance, details) {
21
27
  return new CliProblem({
28
+ code: "cli/usage",
22
29
  detail,
30
+ ...(details ? { details } : {}),
23
31
  exitCode: 2,
24
32
  instance,
25
33
  status: 400,
@@ -29,6 +37,7 @@ export function usageProblem(detail, instance) {
29
37
  }
30
38
  export function authProblem(detail, instance) {
31
39
  return new CliProblem({
40
+ code: "cli/auth",
32
41
  detail,
33
42
  exitCode: 3,
34
43
  instance,
@@ -39,6 +48,7 @@ export function authProblem(detail, instance) {
39
48
  }
40
49
  export function notFoundProblem(detail, instance) {
41
50
  return new CliProblem({
51
+ code: "cli/not_found",
42
52
  detail,
43
53
  exitCode: 4,
44
54
  instance,
@@ -49,6 +59,7 @@ export function notFoundProblem(detail, instance) {
49
59
  }
50
60
  export function genericProblem(detail, instance) {
51
61
  return new CliProblem({
62
+ code: "cli/error",
52
63
  detail,
53
64
  exitCode: 1,
54
65
  instance,
@@ -1,4 +1,5 @@
1
1
  import type { ParsedCommand } from "./runner.js";
2
2
  export declare function readOptionalStringFlag(parsed: ParsedCommand, name: string): string | undefined;
3
3
  export declare function readRequiredStringFlag(parsed: ParsedCommand, name: string): string;
4
+ export declare function readRequiredStringFlagPreservingEmpty(parsed: ParsedCommand, name: string): string;
4
5
  export declare function readOptionalNumberFlag(parsed: ParsedCommand, name: string): number | undefined;
@@ -10,6 +10,13 @@ export function readRequiredStringFlag(parsed, name) {
10
10
  }
11
11
  return value;
12
12
  }
13
+ export function readRequiredStringFlagPreservingEmpty(parsed, name) {
14
+ const value = parsed.flags[name];
15
+ if (typeof value !== "string") {
16
+ throw usageProblem(`Missing required flag --${name}.`, parsed.definition.id);
17
+ }
18
+ return value;
19
+ }
13
20
  export function readOptionalNumberFlag(parsed, name) {
14
21
  const value = parsed.flags[name];
15
22
  return typeof value === "number" ? value : undefined;