@duvoai/cli 1.71.2 → 1.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin/duvo.js +416 -63
  2. package/package.json +1 -1
package/dist/bin/duvo.js CHANGED
@@ -3952,6 +3952,11 @@ var versioningApiFields = {
3952
3952
  sandbox_id: z12.string().nullable().describe("E2B sandbox id that produced AI rows; null for user edits."),
3953
3953
  last_session_id: z12.string().nullable().describe("Claude SDK session id for artifact-chat follow-up reuse.")
3954
3954
  };
3955
+ var versionCreatorEmailApiField = {
3956
+ user_email: z12.string().nullable().optional().describe(
3957
+ "Email of the creator user, resolved from `user_id`; null when the user is unknown or was deleted."
3958
+ )
3959
+ };
3955
3960
  var versionNumberApiField = {
3956
3961
  version_number: z12.number().int().positive().describe(
3957
3962
  "1-based position of this snapshot in the process's full chronological history for its kind, counted over every non-deleted row \u2014 not the row's index in the returned page."
@@ -4029,7 +4034,8 @@ var clarityCurrentProcessVersionApiSchema = z12.object({
4029
4034
  parent_current_process_id: z12.guid().nullable().describe("Parent current-process snapshot this draft forked from"),
4030
4035
  created_at: z12.string().describe("ISO 8601 creation timestamp"),
4031
4036
  ...versionNumberApiField,
4032
- ...versioningApiFields
4037
+ ...versioningApiFields,
4038
+ ...versionCreatorEmailApiField
4033
4039
  });
4034
4040
  var clarityTransformationProposalVersionApiSchema = z12.object({
4035
4041
  id: z12.guid().describe("Proposal id"),
@@ -4041,6 +4047,7 @@ var clarityTransformationProposalVersionApiSchema = z12.object({
4041
4047
  created_at: z12.string().describe("ISO 8601 creation timestamp"),
4042
4048
  ...versionNumberApiField,
4043
4049
  ...versioningApiFields,
4050
+ ...versionCreatorEmailApiField,
4044
4051
  extra_capture_requests: z12.array(clarityProposalExtraCaptureRequestSchema).optional().describe(
4045
4052
  "Active extra-capture requests for this proposal. Populated only on the live proposal version row in the read-model endpoint so the FE can render assignment UI without a second round-trip; historic versions return an empty array. Other endpoints (e.g. admin snapshot views) may omit it entirely, in which case it is `undefined`. The standalone list endpoint stays the source of truth for paginated/admin views."
4046
4053
  )
@@ -4098,12 +4105,14 @@ var clarityProcessSnapshotVersionApiSchema = z12.discriminatedUnion(
4098
4105
  clarityProcessSnapshotBaseApiSchema.extend({
4099
4106
  kind: z12.literal("current_process"),
4100
4107
  source_snapshot_id: z12.null(),
4101
- ...versionNumberApiField
4108
+ ...versionNumberApiField,
4109
+ ...versionCreatorEmailApiField
4102
4110
  }).omit({ updated_at: true }),
4103
4111
  clarityProcessSnapshotBaseApiSchema.extend({
4104
4112
  kind: z12.literal("transformation_proposal"),
4105
4113
  source_snapshot_id: z12.guid(),
4106
- ...versionNumberApiField
4114
+ ...versionNumberApiField,
4115
+ ...versionCreatorEmailApiField
4107
4116
  }).omit({ updated_at: true })
4108
4117
  ]
4109
4118
  );
@@ -9921,6 +9930,11 @@ var BUILDER_RUN_STATUSES = [
9921
9930
  "waiting_for_input",
9922
9931
  "waiting_for_approve"
9923
9932
  ];
9933
+ var BUILDER_RUN_WARNING_CODES = ["connection_not_in_team_catalog"];
9934
+ var builderRunWarningSchema = z42.object({
9935
+ code: z42.enum(BUILDER_RUN_WARNING_CODES),
9936
+ detail: z42.string().describe("Human-readable specifics, e.g. the slugs that did not resolve")
9937
+ });
9924
9938
  var builderRunStatusSchema = z42.object({
9925
9939
  id: z42.string(),
9926
9940
  status: z42.enum(BUILDER_RUN_STATUSES),
@@ -9932,7 +9946,10 @@ var builderRunStatusSchema = z42.object({
9932
9946
  // to work.
9933
9947
  checkpoint_config: storedStepConfigValueSchema.nullable(),
9934
9948
  pending_user_questions: askUserQuestionInputSchema.nullable(),
9935
- schedule_suggested: z42.boolean().optional()
9949
+ schedule_suggested: z42.boolean().optional(),
9950
+ // Optional rather than nullable-required: an existing client polling this
9951
+ // endpoint must keep parsing a response that predates the column.
9952
+ warnings: z42.array(builderRunWarningSchema).nullable().optional()
9936
9953
  });
9937
9954
  var MAX_CASE_ATTACHMENT_BYTES = 50 * 1024 * 1024;
9938
9955
  var CASE_ATTACHMENT_STATUS = {
@@ -12642,7 +12659,12 @@ async function assertResponseOk(response, method, path) {
12642
12659
  );
12643
12660
  }
12644
12661
  if (response.status === 404) {
12645
- throw new CliError(`Not found: ${method} ${path}`, EXIT_CODE.NOT_FOUND);
12662
+ const apiError = parseApiErrorDetail(await safeReadText(response));
12663
+ const suffix = apiError.message ? `: ${apiError.message}` : "";
12664
+ throw new CliError(
12665
+ `Not found: ${method} ${path}${suffix}`,
12666
+ EXIT_CODE.NOT_FOUND
12667
+ );
12646
12668
  }
12647
12669
  if (!response.ok) {
12648
12670
  const detail = await safeReadText(response);
@@ -20551,6 +20573,11 @@ var clarityInterviewVoiceSpeedSchema = z282.union([
20551
20573
  z282.literal(1.25),
20552
20574
  z282.literal(1.5)
20553
20575
  ]);
20576
+ function normalizeAgentName(name) {
20577
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
20578
+ }
20579
+ __name(normalizeAgentName, "normalizeAgentName");
20580
+ __name3(normalizeAgentName, "normalizeAgentName");
20554
20581
  function isStartStep2(step) {
20555
20582
  return step.nodeType === "event" && step.nodeSubtype === "start";
20556
20583
  }
@@ -21964,6 +21991,11 @@ var versioningApiFields2 = {
21964
21991
  sandbox_id: z323.string().nullable().describe("E2B sandbox id that produced AI rows; null for user edits."),
21965
21992
  last_session_id: z323.string().nullable().describe("Claude SDK session id for artifact-chat follow-up reuse.")
21966
21993
  };
21994
+ var versionCreatorEmailApiField2 = {
21995
+ user_email: z323.string().nullable().optional().describe(
21996
+ "Email of the creator user, resolved from `user_id`; null when the user is unknown or was deleted."
21997
+ )
21998
+ };
21967
21999
  var versionNumberApiField2 = {
21968
22000
  version_number: z323.number().int().positive().describe(
21969
22001
  "1-based position of this snapshot in the process's full chronological history for its kind, counted over every non-deleted row \u2014 not the row's index in the returned page."
@@ -22041,7 +22073,8 @@ var clarityCurrentProcessVersionApiSchema2 = z323.object({
22041
22073
  parent_current_process_id: z323.guid().nullable().describe("Parent current-process snapshot this draft forked from"),
22042
22074
  created_at: z323.string().describe("ISO 8601 creation timestamp"),
22043
22075
  ...versionNumberApiField2,
22044
- ...versioningApiFields2
22076
+ ...versioningApiFields2,
22077
+ ...versionCreatorEmailApiField2
22045
22078
  });
22046
22079
  var clarityTransformationProposalVersionApiSchema2 = z323.object({
22047
22080
  id: z323.guid().describe("Proposal id"),
@@ -22053,6 +22086,7 @@ var clarityTransformationProposalVersionApiSchema2 = z323.object({
22053
22086
  created_at: z323.string().describe("ISO 8601 creation timestamp"),
22054
22087
  ...versionNumberApiField2,
22055
22088
  ...versioningApiFields2,
22089
+ ...versionCreatorEmailApiField2,
22056
22090
  extra_capture_requests: z323.array(clarityProposalExtraCaptureRequestSchema2).optional().describe(
22057
22091
  "Active extra-capture requests for this proposal. Populated only on the live proposal version row in the read-model endpoint so the FE can render assignment UI without a second round-trip; historic versions return an empty array. Other endpoints (e.g. admin snapshot views) may omit it entirely, in which case it is `undefined`. The standalone list endpoint stays the source of truth for paginated/admin views."
22058
22092
  )
@@ -22110,12 +22144,14 @@ var clarityProcessSnapshotVersionApiSchema2 = z323.discriminatedUnion(
22110
22144
  clarityProcessSnapshotBaseApiSchema2.extend({
22111
22145
  kind: z323.literal("current_process"),
22112
22146
  source_snapshot_id: z323.null(),
22113
- ...versionNumberApiField2
22147
+ ...versionNumberApiField2,
22148
+ ...versionCreatorEmailApiField2
22114
22149
  }).omit({ updated_at: true }),
22115
22150
  clarityProcessSnapshotBaseApiSchema2.extend({
22116
22151
  kind: z323.literal("transformation_proposal"),
22117
22152
  source_snapshot_id: z323.guid(),
22118
- ...versionNumberApiField2
22153
+ ...versionNumberApiField2,
22154
+ ...versionCreatorEmailApiField2
22119
22155
  }).omit({ updated_at: true })
22120
22156
  ]
22121
22157
  );
@@ -25180,6 +25216,7 @@ var ALLOWED_GET_PATTERNS = [
25180
25216
  // Org-wide agent inventory with last-run and schedule state — the
25181
25217
  // org-level sibling of the team `agents` list, same disclosure tier.
25182
25218
  new RegExp(`^organizations/${UUID}/agents$`),
25219
+ new RegExp(`^organizations/${UUID}/runs$`),
25183
25220
  new RegExp(`^organizations/${UUID}/insights/metrics$`),
25184
25221
  new RegExp(`^organizations/${UUID}/insights/usage-chart$`),
25185
25222
  new RegExp(`^organizations/${UUID}/insights/headline$`),
@@ -30327,6 +30364,21 @@ var AGENT_CHAT_RUNTIME_FALLBACK_REASONS = [
30327
30364
  "opted_out",
30328
30365
  "cli_not_ready"
30329
30366
  ];
30367
+ var skillKeySchema = z722.string().trim().min(1).max(80);
30368
+ var agentChatSkillsSnapshotSchema = z722.object({
30369
+ keys: z722.array(skillKeySchema).max(64),
30370
+ source: z722.enum(["fresh", "reused", "restored"])
30371
+ });
30372
+ var legacySkillsSnapshotSchema = z722.object({
30373
+ gated: z722.array(skillKeySchema).max(32),
30374
+ always: z722.array(skillKeySchema).max(16),
30375
+ source: z722.enum(["fresh", "reconcile"])
30376
+ }).transform(
30377
+ ({ gated, always, source }) => ({
30378
+ keys: [.../* @__PURE__ */ new Set([...gated, ...always])],
30379
+ source: source === "reconcile" ? "reused" : source
30380
+ })
30381
+ );
30330
30382
  var agentChatMessageMetadataSchema = z722.object({
30331
30383
  /**
30332
30384
  * The Duvo page the user was on when they sent the message. All structured
@@ -30375,22 +30427,18 @@ var agentChatMessageMetadataSchema = z722.object({
30375
30427
  source: z722.string().trim().min(1).max(120)
30376
30428
  }).optional(),
30377
30429
  /**
30378
- * Confirmed meta-agent companion skills on disk for THIS turn. Written at
30379
- * dispatch after seed/reconcile so Chat Debugger can show what Ask Duvo
30380
- * actually had after the sandbox is gone. Deliberately NOT in the response
30381
- * projection: admin transcript only, like `connections` and `synthetic`.
30430
+ * Confirmed startup skills on disk for THIS turn. Written before dispatch
30431
+ * after initial seeding, reuse, or restoration so Chat Debugger can show
30432
+ * what Ask Duvo actually had after the sandbox is gone. Deliberately NOT in
30433
+ * the response projection: admin transcript only, like `connections` and `synthetic`.
30382
30434
  */
30383
- skills: z722.object({
30384
- gated: z722.array(z722.string().trim().min(1).max(80)).max(32),
30385
- always: z722.array(z722.string().trim().min(1).max(80)).max(16),
30386
- source: z722.enum(["fresh", "reconcile"])
30387
- }).optional(),
30435
+ skills: agentChatSkillsSnapshotSchema.or(legacySkillsSnapshotSchema).optional(),
30388
30436
  /**
30389
30437
  * Which tool transport THIS turn was dispatched with. `mcpServers` is the
30390
30438
  * key set of the exact map handed to cc-server: the SDK mounts nothing over
30391
30439
  * HTTP beyond that map, so the public `duvo` server's absence here is proof
30392
30440
  * it was not mounted for the turn, independent of whether any tool was
30393
- * called. A v2 chat that falls back to MCP names why in `fallbackReason`.
30441
+ * called. Historical MCP fallbacks may name why in `fallbackReason`.
30394
30442
  * Admin transcript only, like `skills` — not in the response projection.
30395
30443
  */
30396
30444
  runtime: z722.object({
@@ -31707,6 +31755,11 @@ var BUILDER_RUN_STATUSES2 = [
31707
31755
  "waiting_for_input",
31708
31756
  "waiting_for_approve"
31709
31757
  ];
31758
+ var BUILDER_RUN_WARNING_CODES2 = ["connection_not_in_team_catalog"];
31759
+ var builderRunWarningSchema2 = z83.object({
31760
+ code: z83.enum(BUILDER_RUN_WARNING_CODES2),
31761
+ detail: z83.string().describe("Human-readable specifics, e.g. the slugs that did not resolve")
31762
+ });
31710
31763
  var builderRunStatusSchema2 = z83.object({
31711
31764
  id: z83.string(),
31712
31765
  status: z83.enum(BUILDER_RUN_STATUSES2),
@@ -31718,7 +31771,10 @@ var builderRunStatusSchema2 = z83.object({
31718
31771
  // to work.
31719
31772
  checkpoint_config: storedStepConfigValueSchema2.nullable(),
31720
31773
  pending_user_questions: askUserQuestionInputSchema2.nullable(),
31721
- schedule_suggested: z83.boolean().optional()
31774
+ schedule_suggested: z83.boolean().optional(),
31775
+ // Optional rather than nullable-required: an existing client polling this
31776
+ // endpoint must keep parsing a response that predates the column.
31777
+ warnings: z83.array(builderRunWarningSchema2).nullable().optional()
31722
31778
  });
31723
31779
  var MAX_CASE_ATTACHMENT_BYTES2 = 50 * 1024 * 1024;
31724
31780
  var CASE_ATTACHMENT_STATUS2 = {
@@ -32986,6 +33042,11 @@ var FEATURE_FLAG_CATALOGUE = {
32986
33042
  defaultValue: false,
32987
33043
  description: "Routes the Agent Builder prompt input through Ask Duvo instead of building the AOP inline: the welcome hero and the config-editor composer hand their prompt to a fresh Ask Duvo chat that continues the conversation, and the builder input hides itself once handed off. When off, both inputs keep generating and editing the AOP in place. Requires AGENT_CHAT \u2014 the handoff needs the chat surface to land in, so with AGENT_CHAT off this flag does nothing. Separate from AGENT_CHAT so the builder handoff can roll out independently of the rest of Ask Duvo. Off by default for staged rollout."
32988
33044
  },
33045
+ BROWSING_RECORDING_SCREENSHOTS: {
33046
+ key: "browsing_recording_screenshots",
33047
+ defaultValue: false,
33048
+ description: "Per-team opt-in for capturing a screenshot on each action during a browsing recording. When on, `begin-recording` opens a CDP connection to the Browserbase session for the recording's lifetime and writes a JPEG per action into the recording bucket, which completion then publishes with the skill. When off (default), no connection is opened and recordings complete exactly as they did before screenshots existed. This is the kill switch for the capture path: it runs against every page the user visits during a recording, so it must be possible to turn off without a deploy."
33049
+ },
32989
33050
  AGENT_CHAT: {
32990
33051
  key: "agent_chat",
32991
33052
  defaultValue: false,
@@ -33276,6 +33337,11 @@ var FEATURE_FLAG_CATALOGUE = {
33276
33337
  defaultValue: false,
33277
33338
  description: "Master switch for Pulse live Connection data through the `window.duvo.mcp` view-time bridge. When ON, generation can author live sections for any backend-resolved Connection. Ingestion keeps tools that the Connection marks as read-only and that pass the server read-only name policy. The backend executor serves creator-only reads from the stored dashboard manifest. Connections with user-supplied URLs stay on the static build-time path until the backend has an SSRF-safe MCP transport. A validated live manifest makes the Pulse private, removes existing member shares, and blocks new sharing. When OFF (default), generation bakes Connection data into the HTML, ingestion clears stale manifest authority without contacting Connections, the canvas omits the bridge, and the executor refuses all calls. SECURITY: the bridge uses the opaque-origin sandbox postMessage broker (DUVO-6410 / PR #6884). The frame has no credentials. The backend resolves the artifact from the trusted proxy-stamped `X-Pulse-Artifact-Id` header and enforces the artifact manifest. Shared-viewer reads require a separate trust model."
33278
33339
  },
33340
+ RECORDED_SKILL_DETAILS: {
33341
+ key: "recorded_skill_details",
33342
+ defaultValue: false,
33343
+ description: "Enables the full-page detail experience for recorded skills and workflows, including revision history, recorded-step playback, screenshot review, and detail-page actions. When off, skill and workflow cards open the existing staging dialogs and direct detail URLs return to those dialogs. Off-by-default for staged rollout."
33344
+ },
33279
33345
  SHARED_CONNECTIONS: {
33280
33346
  key: "shared_connections",
33281
33347
  defaultValue: false,
@@ -33502,6 +33568,7 @@ function readDesktopStepParams(step) {
33502
33568
  double: params.double === true,
33503
33569
  modifiers: strArray(params.modifiers),
33504
33570
  key: str(params, "key"),
33571
+ typedText: str(params, "typedText"),
33505
33572
  textLength: num(params, "textLength"),
33506
33573
  scroll: readScroll(params),
33507
33574
  find: readFind(params),
@@ -33606,8 +33673,13 @@ var mcpStepSchema = z103.object({
33606
33673
  params: z103.record(z103.string(), z103.unknown()).describe("Tool-specific parameters"),
33607
33674
  fallbackSelectors: z103.array(z103.string()).optional().describe("Alternative CSS selectors if primary fails"),
33608
33675
  description: z103.string().optional().describe("Human-readable step description"),
33609
- screenshot: z103.string().optional().describe("Base64 screenshot at this step for visual context"),
33676
+ screenshot: z103.string().optional().describe(
33677
+ "Path to this step's captured frame, relative to the skill root (e.g. `assets/screenshots/12.jpg`). Never inline image data \u2014 the frame is a separate asset in the skill bundle."
33678
+ ),
33610
33679
  snapshot: z103.string().optional().describe("DOM snapshot in accessibility tree format at this step"),
33680
+ coordinates: z103.object({ x: z103.number(), y: z103.number() }).optional().describe(
33681
+ "Where in the viewport the action happened, in CSS pixels relative to `viewport`. Presentation only \u2014 replay targets the element, never the point \u2014 so a player can mark the spot on this step's `screenshot`."
33682
+ ),
33611
33683
  framePath: z103.array(z103.number()).optional().describe(
33612
33684
  "Index-based path to the target iframe (e.g., [0] for first child iframe, [1, 0] for nested)"
33613
33685
  ),
@@ -34327,6 +34399,43 @@ var computerUseElementTargetSchema = z109.object({
34327
34399
  /** Convenience flag: `matchCount === 1`. Absent on older recordings. */
34328
34400
  unique: z109.boolean().optional()
34329
34401
  });
34402
+ var EDITABLE_COMPUTER_USE_ROLES = /* @__PURE__ */ new Set([
34403
+ "combobox",
34404
+ "document",
34405
+ "edit",
34406
+ "entry",
34407
+ "searchbox",
34408
+ "search box",
34409
+ "text",
34410
+ "textbox"
34411
+ ]);
34412
+ var SENSITIVE_COMPUTER_USE_FIELD_PATTERN = /\b(?:account number|address|api key|card number|credit card|cvc|cvv|date of birth|dob|e\s*mail|first name|full name|iban|last name|one time|otp|passcode|password|phone|pin|routing number|security code|secret|social security|ssn|token|user name|username|verification code)\b/i;
34413
+ function canCaptureComputerUseTypedText(target) {
34414
+ const element = target?.element;
34415
+ if (!element) {
34416
+ return false;
34417
+ }
34418
+ const identity = [
34419
+ target.selector,
34420
+ element.role,
34421
+ element.name,
34422
+ element.automationId,
34423
+ element.className
34424
+ ].filter((value) => Boolean(value)).join(" ");
34425
+ const normalizedIdentity = identity.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ");
34426
+ if (!identity || SENSITIVE_COMPUTER_USE_FIELD_PATTERN.test(normalizedIdentity)) {
34427
+ return false;
34428
+ }
34429
+ const role = element.role?.trim().toLowerCase();
34430
+ const hasFieldIdentity = Boolean(
34431
+ element.name?.trim() || element.automationId?.trim() || element.className?.trim()
34432
+ );
34433
+ return Boolean(
34434
+ role && EDITABLE_COMPUTER_USE_ROLES.has(role) && hasFieldIdentity
34435
+ );
34436
+ }
34437
+ __name(canCaptureComputerUseTypedText, "canCaptureComputerUseTypedText");
34438
+ __name3(canCaptureComputerUseTypedText, "canCaptureComputerUseTypedText");
34330
34439
  var computerUseElementResolutionSchema = z109.object({
34331
34440
  target: computerUseElementTargetSchema.nullable(),
34332
34441
  activeWindow: z109.string().nullable()
@@ -34372,11 +34481,11 @@ var computerUseScrollEventSchema = computerUseEventBaseSchema.extend({
34372
34481
  });
34373
34482
  var computerUseTypeEventSchema = computerUseEventBaseSchema.extend({
34374
34483
  type: z109.literal("type"),
34375
- /** Number of characters typed (redacted; no literal text). */
34484
+ /** Number of characters typed. */
34376
34485
  textLength: z109.number().int().nonnegative(),
34377
34486
  /**
34378
- * @deprecated Never persisted. Literal typed text can contain secrets; the
34379
- * recorder sends `textLength` instead and the backend strips any `text`.
34487
+ * Present only when the focused control is a known non-sensitive text field.
34488
+ * Sensitive and unresolved controls keep only `textLength`.
34380
34489
  */
34381
34490
  text: z109.string().optional()
34382
34491
  });
@@ -44966,7 +45075,7 @@ var CLARITY_READ_TOOLS = [
44966
45075
  path: "/v2/clarity/processes/:processId/members"
44967
45076
  }
44968
45077
  ],
44969
- description: "List users with accepted access to a Clarity process."
45078
+ description: "List users with accepted access to a Clarity process, plus the people invited who have not accepted."
44970
45079
  },
44971
45080
  {
44972
45081
  name: "listClarityFolders",
@@ -49652,10 +49761,18 @@ var processMemberSchema = z147.object({
49652
49761
  role: z147.string().nullable(),
49653
49762
  createdAt: z147.string()
49654
49763
  });
49764
+ var processPendingInviteSchema = z147.object({
49765
+ id: z147.guid(),
49766
+ email: z147.string(),
49767
+ createdAt: z147.string()
49768
+ });
49655
49769
  var processMembersResponseSchema = z147.object({
49656
49770
  visibility: z147.enum(["team", "restricted"]),
49657
49771
  owner: processAccessUserSchema,
49658
- members: z147.array(processMemberSchema)
49772
+ members: z147.array(processMemberSchema),
49773
+ // Defaulted so a backend that predates the field still parses, and so the
49774
+ // renderer never has to branch on undefined.
49775
+ pending: z147.array(processPendingInviteSchema).default([])
49659
49776
  });
49660
49777
  var MEMBER_COLUMNS = [
49661
49778
  { header: "ID", cell: /* @__PURE__ */ __name((member) => member.id, "cell") },
@@ -49671,6 +49788,18 @@ var MEMBER_COLUMNS = [
49671
49788
  },
49672
49789
  { header: "ROLE", cell: /* @__PURE__ */ __name((member) => member.role ?? "", "cell") }
49673
49790
  ];
49791
+ var PENDING_COLUMNS = [
49792
+ { header: "ID", cell: /* @__PURE__ */ __name((invite) => invite.id, "cell") },
49793
+ {
49794
+ header: "EMAIL",
49795
+ cell: /* @__PURE__ */ __name((invite) => invite.email, "cell"),
49796
+ maxWidth: 40
49797
+ },
49798
+ {
49799
+ header: "INVITED",
49800
+ cell: /* @__PURE__ */ __name((invite) => invite.createdAt, "cell")
49801
+ }
49802
+ ];
49674
49803
  function registerClarityProcessAdministration(clarity) {
49675
49804
  registerGuidance(clarity);
49676
49805
  registerSettings(clarity);
@@ -49709,8 +49838,10 @@ function registerSharing(clarity) {
49709
49838
  }
49710
49839
  __name(registerSharing, "registerSharing");
49711
49840
  function registerMembers(clarity) {
49712
- const members = clarity.command("members").description("Manage accepted access to a Clarity process");
49713
- members.command("list <process-id>").description("List accepted process members").option("--json", "output raw JSON from the API").action(listMembers);
49841
+ const members = clarity.command("members").description(
49842
+ "Manage a Clarity process's access: accepted members and pending invitations"
49843
+ );
49844
+ members.command("list <process-id>").description("List accepted process members and pending invitations").option("--json", "output raw JSON from the API").action(listMembers);
49714
49845
  members.command("remove <process-id> <member-id>").description("Remove a member's accepted process access").option("-y, --yes", "skip the confirmation prompt").option("--json", "output raw JSON from the API").action(removeMember);
49715
49846
  }
49716
49847
  __name(registerMembers, "registerMembers");
@@ -49844,16 +49975,21 @@ async function listMembers(rawProcessId, options) {
49844
49975
  });
49845
49976
  const response = parseClarityResponse(processMembersResponseSchema, raw);
49846
49977
  if (options.json) {
49847
- printJson(response);
49978
+ printJson(raw);
49848
49979
  return;
49849
49980
  }
49850
49981
  const owner = response.owner.name ?? response.owner.email ?? response.owner.id;
49851
49982
  console.log(`Visibility: ${response.visibility}; owner: ${owner}`);
49852
49983
  if (response.members.length === 0) {
49853
49984
  console.log("No accepted process members found.");
49854
- return;
49985
+ } else {
49986
+ console.log(renderTable(MEMBER_COLUMNS, response.members));
49987
+ }
49988
+ if (response.pending.length > 0) {
49989
+ console.log("");
49990
+ console.log("Invited, not yet accepted:");
49991
+ console.log(renderTable(PENDING_COLUMNS, response.pending));
49855
49992
  }
49856
- console.log(renderTable(MEMBER_COLUMNS, response.members));
49857
49993
  }
49858
49994
  __name(listMembers, "listMembers");
49859
49995
  async function removeMember(rawProcessId, rawMemberId, options) {
@@ -59894,7 +60030,7 @@ function registerRunsList(runs) {
59894
60030
  "filter by user ID (superadmins only; other callers always see their own runs)"
59895
60031
  ).option("--case-queue <id>", "filter by queue ID").option("--source <source>", "filter by source").option("--search <query>", "free-text search across runs").option(
59896
60032
  "--has-issues <true|false>",
59897
- `show only runs with (or without) issues (${HAS_ISSUES_VALUES.join("|")})`
60033
+ "filter on the run's latest successful evaluation: true = only runs it flagged, false = only runs it did not. Only the latest counts \u2014 an older flag does not match \u2014 and a run whose latest evaluation errored, or that has none, counts as unflagged"
59898
60034
  ).option(
59899
60035
  "--since <iso>",
59900
60036
  "include runs completed at or after this ISO-8601 timestamp"
@@ -63256,21 +63392,237 @@ ${total_teams} teams${suffix} \xB7 ${org_member_count} org members`
63256
63392
  }
63257
63393
  __name(registerTeamsOrgMetrics, "registerTeamsOrgMetrics");
63258
63394
 
63259
- // src/commands/teams/org-usage.ts
63395
+ // src/commands/teams/org-runs.ts
63260
63396
  import { z as z171 } from "zod";
63397
+ var DEFAULT_LIMIT25 = 20;
63398
+ var MIN_LIMIT27 = 1;
63399
+ var MAX_LIMIT25 = 500;
63400
+ var NAME_PREVIEW_WIDTH2 = 28;
63401
+ var ISSUE_SEVERITIES = ["critical", "medium", "low"];
63402
+ var RUN_STATUSES = [
63403
+ ...AGENT_RUN_STATUSES2,
63404
+ "stopped",
63405
+ "needs_attention"
63406
+ ];
63407
+ var HAS_ISSUES_VALUES2 = ["true", "false"];
63408
+ var orgRunSchema = z171.object({
63409
+ id: z171.string(),
63410
+ status: z171.string(),
63411
+ source: z171.string().nullable(),
63412
+ team_id: z171.string(),
63413
+ team_name: z171.string().nullable(),
63414
+ agent_id: z171.string(),
63415
+ agent_name: z171.string(),
63416
+ build_id: z171.string().nullable(),
63417
+ automation_id: z171.string().nullable(),
63418
+ automation_name: z171.string().nullable(),
63419
+ user_id: z171.string().nullable(),
63420
+ created_at: z171.string(),
63421
+ updated_at: z171.string(),
63422
+ started_at: z171.string().nullable(),
63423
+ completed_at: z171.string().nullable()
63424
+ });
63425
+ var orgRunsResponseSchema = z171.object({
63426
+ runs: z171.array(orgRunSchema),
63427
+ total: z171.number().nullable(),
63428
+ limit: z171.number(),
63429
+ // Optional so the CLI keeps working against an API that predates them.
63430
+ created_after: z171.string().nullable().optional(),
63431
+ default_window_applied: z171.boolean().optional(),
63432
+ has_more: z171.boolean().optional(),
63433
+ next_cursor: z171.string().nullable().optional()
63434
+ });
63435
+ var DEFAULT_WINDOW_NOTE = "default window: last 7 days \u2014 pass --created-after (or any time bound) to widen";
63436
+ function buildOrgRunsQuery(options, limit) {
63437
+ const query = new URLSearchParams({ limit: String(limit) });
63438
+ if (options.cursor) {
63439
+ query.set("cursor", options.cursor);
63440
+ }
63441
+ const filters = [
63442
+ ["team_id", options.team],
63443
+ ["agent_id", options.agent],
63444
+ ["status", options.status],
63445
+ ["source", options.source],
63446
+ ["has_issues", options.hasIssues],
63447
+ ["issue_severity", options.issueSeverity],
63448
+ ["created_after", options.createdAfter],
63449
+ ["created_before", options.createdBefore],
63450
+ ["started_after", options.startedAfter],
63451
+ ["started_before", options.startedBefore],
63452
+ // `--since` / `--until` are the API's aliases for the completion window,
63453
+ // kept because `duvo runs list` ships them. Send whichever the caller
63454
+ // passed under its own name: the API rejects both at once, so forwarding
63455
+ // both when both are set surfaces that rather than silently picking one.
63456
+ ["completed_after", options.completedAfter],
63457
+ ["completed_before", options.completedBefore],
63458
+ ["since", options.since],
63459
+ ["until", options.until],
63460
+ ["count_only", options.countOnly ? "true" : void 0],
63461
+ ["include_total", options.includeTotal ? "true" : void 0]
63462
+ ];
63463
+ for (const [param, value] of filters) {
63464
+ if (value !== void 0) {
63465
+ query.set(param, value);
63466
+ }
63467
+ }
63468
+ return query;
63469
+ }
63470
+ __name(buildOrgRunsQuery, "buildOrgRunsQuery");
63471
+ function registerTeamsOrgRuns(teams) {
63472
+ teams.command("org-runs <org-id>").description("List runs across every team in an organization").option("--team <team-id>", "only runs on this team").option("--agent <agent-id>", "only runs for this agent").option(
63473
+ "--status <status>",
63474
+ `only runs with this status (${RUN_STATUSES.join(" | ")})`
63475
+ ).option("--source <source>", "only runs from this source").option(
63476
+ "--has-issues <true|false>",
63477
+ "filter on the run's latest successful evaluation: true = only runs it flagged, false = only runs it did not. Only the latest counts \u2014 an older flag does not match \u2014 and a run whose latest evaluation errored, or that has none, counts as unflagged"
63478
+ ).option(
63479
+ "--issue-severity <severity>",
63480
+ `only runs flagged at this severity (${ISSUE_SEVERITIES.join(" | ")})`
63481
+ ).option(
63482
+ "--created-after <iso>",
63483
+ "runs created at or after this ISO timestamp. Every run has a creation time, so this window returns all runs in a period whatever state they reached"
63484
+ ).option(
63485
+ "--created-before <iso>",
63486
+ "runs created before this ISO timestamp (exclusive)"
63487
+ ).option(
63488
+ "--started-after <iso>",
63489
+ "runs that started at or after this ISO timestamp. Runs that never started are excluded"
63490
+ ).option(
63491
+ "--started-before <iso>",
63492
+ "runs that started before this ISO timestamp (use with --status running to find stuck runs, and with --started-after to bound the window)"
63493
+ ).option(
63494
+ "--completed-after <iso>",
63495
+ "runs completed at or after this ISO timestamp. Runs that have not completed are excluded, including stuck ones"
63496
+ ).option(
63497
+ "--completed-before <iso>",
63498
+ "runs completed before this ISO timestamp (exclusive)"
63499
+ ).option(
63500
+ "--since <iso>",
63501
+ "alias of --completed-after, matching `duvo runs list`. Prefer --completed-after"
63502
+ ).option(
63503
+ "--until <iso>",
63504
+ "alias of --completed-before. Prefer --completed-before"
63505
+ ).option("--count-only", "print just the matching total").option(
63506
+ "--include-total",
63507
+ "also return the total matching count. Off by default: counting walks every match where the page stops at --limit, so it costs far more than the rows on a large organization"
63508
+ ).option(
63509
+ "--limit <n>",
63510
+ `page size (${MIN_LIMIT27}-${MAX_LIMIT25}, default ${DEFAULT_LIMIT25})`
63511
+ ).option(
63512
+ "--cursor <cursor>",
63513
+ "resume after a previous page: the value the previous page printed as Next page. The only way to page here \u2014 runs are created constantly, and a cursor cannot repeat or skip a run the way an offset can"
63514
+ ).option("--json", "output raw JSON from the API").action(/* @__PURE__ */ __name(async function orgRunsAction(orgId, options) {
63515
+ if (options.issueSeverity && !ISSUE_SEVERITIES.includes(options.issueSeverity)) {
63516
+ throw new CliError(
63517
+ `--issue-severity must be one of: ${ISSUE_SEVERITIES.join(", ")}`
63518
+ );
63519
+ }
63520
+ if (options.status !== void 0 && !RUN_STATUSES.includes(options.status)) {
63521
+ throw new CliError(
63522
+ `--status must be one of: ${RUN_STATUSES.join(", ")}`
63523
+ );
63524
+ }
63525
+ if (options.hasIssues && !HAS_ISSUES_VALUES2.includes(options.hasIssues)) {
63526
+ throw new CliError(
63527
+ `--has-issues must be one of: ${HAS_ISSUES_VALUES2.join(", ")}`
63528
+ );
63529
+ }
63530
+ const { apiKey, apiBaseUrl } = await requireAuthenticatedConfig(this);
63531
+ const limit = parseLimit(options.limit, {
63532
+ defaultValue: DEFAULT_LIMIT25,
63533
+ min: MIN_LIMIT27,
63534
+ max: MAX_LIMIT25
63535
+ });
63536
+ const query = buildOrgRunsQuery(options, limit);
63537
+ const raw = await httpRequest({
63538
+ method: "GET",
63539
+ path: `/v2/organizations/${encodeURIComponent(orgId)}/runs?${query.toString()}`,
63540
+ apiKey,
63541
+ apiBaseUrl
63542
+ });
63543
+ if (options.json) {
63544
+ printJson(raw);
63545
+ return;
63546
+ }
63547
+ const parsed = orgRunsResponseSchema.safeParse(raw);
63548
+ if (!parsed.success) {
63549
+ throw new CliError(
63550
+ `Unexpected response from GET /v2/organizations/:orgId/runs: ${parsed.error.message}`
63551
+ );
63552
+ }
63553
+ const { runs, total } = parsed.data;
63554
+ const windowNote = parsed.data.default_window_applied ? ` (${DEFAULT_WINDOW_NOTE})` : "";
63555
+ if (options.countOnly) {
63556
+ if (total === null) {
63557
+ throw new CliError(
63558
+ "The API returned no total for a --count-only request."
63559
+ );
63560
+ }
63561
+ console.log(`${total}${windowNote}`);
63562
+ return;
63563
+ }
63564
+ if (runs.length === 0) {
63565
+ console.log(`No runs matched.${windowNote}`);
63566
+ return;
63567
+ }
63568
+ console.log(
63569
+ renderTable(
63570
+ [
63571
+ { header: "ID", cell: /* @__PURE__ */ __name((r) => r.id, "cell") },
63572
+ { header: "STATUS", cell: /* @__PURE__ */ __name((r) => r.status, "cell") },
63573
+ {
63574
+ header: "AGENT",
63575
+ cell: /* @__PURE__ */ __name((r) => r.agent_name, "cell"),
63576
+ maxWidth: NAME_PREVIEW_WIDTH2
63577
+ },
63578
+ {
63579
+ header: "TEAM",
63580
+ cell: /* @__PURE__ */ __name((r) => r.team_name ?? "-", "cell"),
63581
+ maxWidth: NAME_PREVIEW_WIDTH2
63582
+ },
63583
+ { header: "SOURCE", cell: /* @__PURE__ */ __name((r) => r.source ?? "-", "cell") },
63584
+ { header: "STARTED", cell: /* @__PURE__ */ __name((r) => r.started_at ?? "-", "cell") },
63585
+ {
63586
+ header: "COMPLETED",
63587
+ cell: /* @__PURE__ */ __name((r) => r.completed_at ?? "-", "cell")
63588
+ }
63589
+ ],
63590
+ runs
63591
+ )
63592
+ );
63593
+ const { next_cursor: nextCursor } = parsed.data;
63594
+ const nextPageHint = nextCursor ? ` Next page: --cursor '${nextCursor}'` : "";
63595
+ if (total !== null) {
63596
+ console.log(
63597
+ `
63598
+ Showing ${runs.length} of ${total} runs${windowNote}.${nextPageHint}`
63599
+ );
63600
+ return;
63601
+ }
63602
+ console.log(
63603
+ nextCursor ? `
63604
+ Showing ${runs.length} runs${windowNote}. More available.${nextPageHint}` : `
63605
+ Showing ${runs.length} runs${windowNote}; pass --include-total for the total`
63606
+ );
63607
+ }, "orgRunsAction"));
63608
+ }
63609
+ __name(registerTeamsOrgRuns, "registerTeamsOrgRuns");
63610
+
63611
+ // src/commands/teams/org-usage.ts
63612
+ import { z as z173 } from "zod";
63261
63613
  var GRANULARITIES = ["day", "week", "month"];
63262
63614
  var DEFAULT_LOOKBACK_MS = 30 * 24 * 60 * 60 * 1e3;
63263
- var bucketSchema = z171.object({
63264
- period: z171.string(),
63265
- total_runs: z171.number(),
63266
- scheduled_runs: z171.number(),
63267
- on_demand_runs: z171.number()
63268
- });
63269
- var usageChartResponseSchema = z171.object({
63270
- buckets: z171.array(bucketSchema),
63271
- granularity: z171.enum(GRANULARITIES),
63272
- truncated: z171.boolean(),
63273
- total_teams: z171.number()
63615
+ var bucketSchema = z173.object({
63616
+ period: z173.string(),
63617
+ total_runs: z173.number(),
63618
+ scheduled_runs: z173.number(),
63619
+ on_demand_runs: z173.number()
63620
+ });
63621
+ var usageChartResponseSchema = z173.object({
63622
+ buckets: z173.array(bucketSchema),
63623
+ granularity: z173.enum(GRANULARITIES),
63624
+ truncated: z173.boolean(),
63625
+ total_teams: z173.number()
63274
63626
  });
63275
63627
  function registerTeamsOrgUsage(teams) {
63276
63628
  teams.command("org-usage <org-id>").description("Show org-wide run volume over time (scheduled vs on-demand)").option("--start-date <date>", "window start (ISO 8601, default 30d ago)").option("--end-date <date>", "window end (ISO 8601, default now)").option(
@@ -63329,17 +63681,17 @@ function registerTeamsOrgUsage(teams) {
63329
63681
  __name(registerTeamsOrgUsage, "registerTeamsOrgUsage");
63330
63682
 
63331
63683
  // src/commands/teams/org.ts
63332
- import { z as z173 } from "zod";
63333
- var orgTeamSchema = z173.object({
63334
- id: z173.string(),
63335
- name: z173.string(),
63336
- userIsMember: z173.boolean(),
63337
- userRole: z173.string().nullable(),
63338
- memberCount: z173.number()
63684
+ import { z as z174 } from "zod";
63685
+ var orgTeamSchema = z174.object({
63686
+ id: z174.string(),
63687
+ name: z174.string(),
63688
+ userIsMember: z174.boolean(),
63689
+ userRole: z174.string().nullable(),
63690
+ memberCount: z174.number()
63339
63691
  });
63340
- var listOrgTeamsResponseSchema = z173.object({
63341
- teams: z173.array(orgTeamSchema),
63342
- total: z173.number()
63692
+ var listOrgTeamsResponseSchema = z174.object({
63693
+ teams: z174.array(orgTeamSchema),
63694
+ total: z174.number()
63343
63695
  });
63344
63696
  var SORT_BY_VALUES = ["createdAt", "name"];
63345
63697
  function registerTeamsOrg(teams) {
@@ -63400,17 +63752,17 @@ function registerTeamsOrg(teams) {
63400
63752
  __name(registerTeamsOrg, "registerTeamsOrg");
63401
63753
 
63402
63754
  // src/commands/teams/orgs.ts
63403
- import { z as z174 } from "zod";
63404
- var orgMembershipSchema = z174.object({
63405
- organization: z174.object({
63406
- id: z174.string(),
63407
- name: z174.string()
63755
+ import { z as z175 } from "zod";
63756
+ var orgMembershipSchema = z175.object({
63757
+ organization: z175.object({
63758
+ id: z175.string(),
63759
+ name: z175.string()
63408
63760
  }),
63409
- role: z174.string(),
63410
- memberCount: z174.number()
63761
+ role: z175.string(),
63762
+ memberCount: z175.number()
63411
63763
  });
63412
- var listMyOrganizationsResponseSchema = z174.object({
63413
- memberships: z174.array(orgMembershipSchema)
63764
+ var listMyOrganizationsResponseSchema = z175.object({
63765
+ memberships: z175.array(orgMembershipSchema)
63414
63766
  });
63415
63767
  function registerTeamsOrgs(teams) {
63416
63768
  teams.command("orgs").description("List the organizations you belong to and your role in each").option("--json", "output raw JSON from the API").action(/* @__PURE__ */ __name(async function orgsAction(options) {
@@ -63460,6 +63812,7 @@ function registerTeams(program) {
63460
63812
  registerTeamsOrgAgents(teams);
63461
63813
  registerTeamsOrgInsights(teams);
63462
63814
  registerTeamsOrgMetrics(teams);
63815
+ registerTeamsOrgRuns(teams);
63463
63816
  registerTeamsOrgUsage(teams);
63464
63817
  }
63465
63818
  __name(registerTeams, "registerTeams");
@@ -63588,7 +63941,7 @@ __name(isStandaloneBinary, "isStandaloneBinary");
63588
63941
  import { createRequire } from "node:module";
63589
63942
  function getPackageMeta() {
63590
63943
  if (true) {
63591
- return { name: "@duvoai/cli", version: "1.71.2" };
63944
+ return { name: "@duvoai/cli", version: "1.72.0" };
63592
63945
  }
63593
63946
  const require2 = createRequire(import.meta.url);
63594
63947
  const raw = require2("../../package.json");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duvoai/cli",
3
- "version": "1.71.2",
3
+ "version": "1.72.0",
4
4
  "description": "Command-line interface for the Duvo public API",
5
5
  "bin": {
6
6
  "duvo": "./dist/bin/duvo.js"