@doist/automations-cli 1.20.3 → 1.21.1

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/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  collectSkillStatus,
8
8
  getInstaller,
9
9
  updateAllInstalledSkills
10
- } from "./chunk-GUWS3EHG.js";
10
+ } from "./chunk-ZWFZ2DD3.js";
11
11
 
12
12
  // ../../node_modules/ajv/dist/compile/codegen/code.js
13
13
  var require_code = __commonJS({
@@ -6899,7 +6899,7 @@ import { fileURLToPath } from "node:url";
6899
6899
  // package.json
6900
6900
  var package_default = {
6901
6901
  name: "@doist/automations-cli",
6902
- version: "1.20.3",
6902
+ version: "1.21.1",
6903
6903
  description: "Command-line client for Todoist Automations",
6904
6904
  type: "module",
6905
6905
  bin: {
@@ -6944,7 +6944,7 @@ var package_default = {
6944
6944
  },
6945
6945
  dependencies: {
6946
6946
  "@doist/cli-core": "1.7.0",
6947
- "@napi-rs/keyring": "2.1.0",
6947
+ "@napi-rs/keyring": "2.1.1",
6948
6948
  ai: "7.0.137",
6949
6949
  commander: "15.0.0",
6950
6950
  oauth4webapi: "3.8.8",
@@ -28149,6 +28149,7 @@ var cronScheduleSchema = (() => {
28149
28149
  });
28150
28150
  })();
28151
28151
  var scheduleValueSchema = external_exports.union([intervalScheduleSchema, cronScheduleSchema]).meta({ description: "The schedule definition for the trigger." });
28152
+ var TRIGGER_TYPES = ["schedule", "webhook", "api"];
28152
28153
  var scheduleTriggerSchema = external_exports.object({
28153
28154
  type: external_exports.literal("schedule"),
28154
28155
  value: scheduleValueSchema,
@@ -29326,8 +29327,29 @@ var automationDraftListResponseSchema = external_exports.object({
29326
29327
  var finalizeAutomationDraftResponseSchema = external_exports.object({
29327
29328
  automation: automationSchema
29328
29329
  });
29330
+ var draftTestTriggerSelectorSchema = external_exports.union([
29331
+ external_exports.object({ index: external_exports.int().min(0) }),
29332
+ external_exports.object({ event: external_exports.string().min(1) })
29333
+ ]);
29334
+ var testAutomationDraftRequestSchema = external_exports.object({
29335
+ trigger: draftTestTriggerSelectorSchema.optional(),
29336
+ payload: external_exports.unknown().optional(),
29337
+ metadata: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
29338
+ }).meta({ id: "TestAutomationDraftRequest" });
29339
+ var draftTestTriggerSchema = external_exports.object({
29340
+ index: external_exports.int().min(0),
29341
+ count: external_exports.int().min(1),
29342
+ type: external_exports.enum(TRIGGER_TYPES),
29343
+ event: external_exports.string().optional(),
29344
+ inputSource: external_exports.enum(["schedule", "sample-event", "supplied", "empty"])
29345
+ });
29329
29346
  var testAutomationDraftResponseSchema = external_exports.object({
29330
- success: external_exports.literal(true)
29347
+ success: external_exports.literal(true),
29348
+ draftId: external_exports.string(),
29349
+ testRunId: external_exports.string(),
29350
+ status: external_exports.enum(AUTOMATION_RUN_STATUSES),
29351
+ connectorCalls: external_exports.literal("mocked"),
29352
+ trigger: draftTestTriggerSchema
29331
29353
  });
29332
29354
  var AUTOMATION_OPEN_DRAFT_CONFLICT_CODE = "automation_open_draft_conflict";
29333
29355
  var AutomationOpenDraftConflictSchema = external_exports.object({
@@ -29340,6 +29362,18 @@ var AutomationDraftNotReadySchema = external_exports.object({
29340
29362
  code: external_exports.literal(AUTOMATION_DRAFT_NOT_READY_CODE),
29341
29363
  message: external_exports.string()
29342
29364
  });
29365
+ var DRAFT_TEST_REQUEST_ERROR_CODES = [
29366
+ "draft_test_not_ready",
29367
+ "draft_test_trigger_not_found",
29368
+ "draft_test_payload_required",
29369
+ "draft_test_payload_filtered",
29370
+ "draft_test_payload_invalid",
29371
+ "draft_test_payload_unsupported"
29372
+ ];
29373
+ var DraftTestRequestErrorSchema = external_exports.object({
29374
+ code: external_exports.enum(DRAFT_TEST_REQUEST_ERROR_CODES),
29375
+ message: external_exports.string()
29376
+ });
29343
29377
 
29344
29378
  // ../../packages/core/dist/esm/schemas/automation-draft-connection.schema.js
29345
29379
  var CreateAutomationDraftConnectionSchema = external_exports.object({
@@ -29651,10 +29685,23 @@ var automationTestRunStepStatusSchema = external_exports.object({
29651
29685
  stepId: external_exports.string().min(1),
29652
29686
  status: external_exports.enum(AUTOMATION_RUN_STATUSES)
29653
29687
  });
29688
+ var automationTestRunFailedStepSchema = external_exports.object({
29689
+ stepId: external_exports.string().min(1),
29690
+ description: external_exports.string().optional(),
29691
+ error: external_exports.string().optional()
29692
+ });
29654
29693
  var automationTestRunProgressSchema = external_exports.object({
29655
29694
  draftId: external_exports.string().min(1),
29656
29695
  status: external_exports.enum(AUTOMATION_RUN_STATUSES),
29657
- steps: external_exports.array(automationTestRunStepStatusSchema).default([])
29696
+ steps: external_exports.array(automationTestRunStepStatusSchema).default([]),
29697
+ // Optional so results stored before these fields existed still parse.
29698
+ runId: external_exports.string().min(1).optional(),
29699
+ failedStep: automationTestRunFailedStepSchema.optional(),
29700
+ /** Why a failed or skipped run stopped. */
29701
+ error: external_exports.string().optional(),
29702
+ result: AutomationRunResultSchema.nullable().optional(),
29703
+ /** A test run simulates every connector call, reads included. */
29704
+ connectorCalls: external_exports.literal("mocked").optional()
29658
29705
  });
29659
29706
  var automationTestRunPreviewTriggerSchema = external_exports.union([
29660
29707
  external_exports.object({
@@ -29947,6 +29994,47 @@ var cohortMatrixResponseSchema = external_exports.object({
29947
29994
  rows: external_exports.array(cohortMatrixRowSchema)
29948
29995
  });
29949
29996
 
29997
+ // ../../packages/core/dist/esm/schemas/draft-test-run.schema.js
29998
+ var DraftTestRunFailedStepSchema = external_exports.object({
29999
+ stepId: external_exports.string(),
30000
+ description: external_exports.string(),
30001
+ error: external_exports.string().nullable()
30002
+ });
30003
+ var draftTestRunDetailDtoBaseShape = {
30004
+ id: external_exports.string(),
30005
+ draftId: external_exports.string(),
30006
+ // The automation an edit draft belongs to; null for a draft of a new automation.
30007
+ automationId: external_exports.string().nullable(),
30008
+ mode: external_exports.literal("test"),
30009
+ // A test run simulates every connector call, reads included, with a model.
30010
+ connectorCalls: external_exports.literal("mocked"),
30011
+ createdAt: external_exports.string(),
30012
+ updatedAt: external_exports.string(),
30013
+ result: AutomationRunResultSchema.nullable(),
30014
+ temporalUrl: external_exports.string().nullish(),
30015
+ steps: external_exports.array(AutomationRunStepDtoSchema),
30016
+ failedStep: DraftTestRunFailedStepSchema.nullable()
30017
+ };
30018
+ var DraftTestRunDetailDtoSchema = external_exports.discriminatedUnion("status", [
30019
+ external_exports.object({ ...draftTestRunDetailDtoBaseShape, status: external_exports.literal("pending") }),
30020
+ external_exports.object({ ...draftTestRunDetailDtoBaseShape, status: external_exports.literal("running") }),
30021
+ external_exports.object({ ...draftTestRunDetailDtoBaseShape, status: external_exports.literal("completed") }),
30022
+ external_exports.object({ ...draftTestRunDetailDtoBaseShape, status: external_exports.literal("cancelled") }),
30023
+ external_exports.object({ ...draftTestRunDetailDtoBaseShape, status: external_exports.literal("data-expired") }),
30024
+ external_exports.object({
30025
+ ...draftTestRunDetailDtoBaseShape,
30026
+ status: external_exports.literal("failed"),
30027
+ failureReason: external_exports.string().nullish(),
30028
+ failureCategory: external_exports.enum(FAILURE_CATEGORIES).nullish().catch(null)
30029
+ }),
30030
+ external_exports.object({
30031
+ ...draftTestRunDetailDtoBaseShape,
30032
+ status: external_exports.literal("skipped"),
30033
+ skipReason: external_exports.string().nullish(),
30034
+ skipCategory: external_exports.enum(SKIP_CATEGORIES).nullish()
30035
+ })
30036
+ ]);
30037
+
29950
30038
  // ../../packages/core/dist/esm/types/workspace.types.js
29951
30039
  var WORKSPACE_PLANS = ["STARTER", "BUSINESS"];
29952
30040
  var WORKSPACE_ROLES = ["ADMIN", "MEMBER", "GUEST"];
@@ -31117,7 +31205,8 @@ var automationDraftsEndpoints = {
31117
31205
  list: root3,
31118
31206
  detail: (id) => `${root3}/${encodeURIComponent(id)}`,
31119
31207
  finalize: (id) => `${root3}/${encodeURIComponent(id)}/finalize`,
31120
- test: (id) => `${root3}/${encodeURIComponent(id)}/test`
31208
+ test: (id) => `${root3}/${encodeURIComponent(id)}/test`,
31209
+ testRun: (id, runId) => `${root3}/${encodeURIComponent(id)}/test-runs/${encodeURIComponent(runId)}`
31121
31210
  };
31122
31211
 
31123
31212
  // ../../packages/sdk/dist/esm/clients/automation-drafts/automation-drafts-client.js
@@ -31131,6 +31220,16 @@ function getDraftNotReadyError(error62) {
31131
31220
  const parsed = AutomationDraftNotReadySchema.safeParse(error62.responseData);
31132
31221
  return parsed.success ? parsed.data : null;
31133
31222
  }
31223
+ function getDraftTestRequestError(error62) {
31224
+ if (!(error62 instanceof AutomationsRequestError) || error62.httpStatusCode !== 400) {
31225
+ return null;
31226
+ }
31227
+ const parsed = DraftTestRequestErrorSchema.safeParse(error62.responseData);
31228
+ return parsed.success ? parsed.data : null;
31229
+ }
31230
+ function isRequestOptions(value) {
31231
+ return Boolean(value && ("signal" in value || "headers" in value));
31232
+ }
31134
31233
  var AutomationDraftsClient = class extends BaseClient {
31135
31234
  async list(args = {}, options) {
31136
31235
  const { cursor, limit } = CursorPaginationSchema.parse(args);
@@ -31191,16 +31290,35 @@ var AutomationDraftsClient = class extends BaseClient {
31191
31290
  });
31192
31291
  return finalizeAutomationDraftResponseSchema.parse(data);
31193
31292
  }
31194
- async test(draftId, options) {
31293
+ /** Starts a test run; `request` picks the trigger and supplies the event to test with. */
31294
+ async test(draftId, requestOrOptions, options) {
31195
31295
  const id = IdSchema.parse(draftId);
31296
+ const request2 = isRequestOptions(requestOrOptions) ? void 0 : requestOrOptions;
31196
31297
  const data = await this.request({
31197
31298
  method: "POST",
31198
31299
  path: automationDraftsEndpoints.test(id),
31199
- options
31300
+ body: request2 ? testAutomationDraftRequestSchema.parse(request2) : void 0,
31301
+ options: isRequestOptions(requestOrOptions) ? requestOrOptions : options
31200
31302
  });
31201
31303
  return testAutomationDraftResponseSchema.parse(data);
31202
31304
  }
31305
+ /** A test run of this draft, with its steps; `args` match the run detail's. */
31306
+ async getTestRun(draftId, runId, args = {}, options) {
31307
+ const data = await this.request({
31308
+ method: "GET",
31309
+ path: automationDraftsEndpoints.testRun(IdSchema.parse(draftId), IdSchema.parse(runId)),
31310
+ query: toTestRunQuery(args),
31311
+ options
31312
+ });
31313
+ return DraftTestRunDetailDtoSchema.parse(data);
31314
+ }
31203
31315
  };
31316
+ function toTestRunQuery(args) {
31317
+ return {
31318
+ includeGeneratedCode: args.includeGeneratedCode ? true : void 0,
31319
+ includeToolCallPreviews: args.includeToolCallPreviews
31320
+ };
31321
+ }
31204
31322
 
31205
31323
  // ../../packages/sdk/dist/esm/clients/automation-runs/endpoints.js
31206
31324
  var automationRunsEndpoints = {
@@ -31209,7 +31327,7 @@ var automationRunsEndpoints = {
31209
31327
  };
31210
31328
 
31211
31329
  // ../../packages/sdk/dist/esm/clients/automation-runs/automation-runs-client.js
31212
- function isRequestOptions(value) {
31330
+ function isRequestOptions2(value) {
31213
31331
  return Boolean(value && ("signal" in value || "headers" in value));
31214
31332
  }
31215
31333
  var AutomationRunsClient = class extends BaseClient {
@@ -31228,8 +31346,8 @@ var AutomationRunsClient = class extends BaseClient {
31228
31346
  async get(automationId, runId, argsOrOptions, options) {
31229
31347
  const aId = IdSchema.parse(automationId);
31230
31348
  const rId = IdSchema.parse(runId);
31231
- const args = isRequestOptions(argsOrOptions) ? void 0 : argsOrOptions;
31232
- const requestOptions = isRequestOptions(argsOrOptions) ? argsOrOptions : options;
31349
+ const args = isRequestOptions2(argsOrOptions) ? void 0 : argsOrOptions;
31350
+ const requestOptions = isRequestOptions2(argsOrOptions) ? argsOrOptions : options;
31233
31351
  const data = await this.request({
31234
31352
  method: "GET",
31235
31353
  path: automationRunsEndpoints.detail(aId, rId),
@@ -31488,7 +31606,9 @@ var WorkspaceAutomationDraftConnectionsClient = class extends BaseClient {
31488
31606
  var workspaceAutomationDraftsEndpoints = {
31489
31607
  list: (workspaceId) => `${root7(workspaceId)}`,
31490
31608
  detail: (workspaceId, id) => `${root7(workspaceId)}/${encodeURIComponent(id)}`,
31491
- finalize: (workspaceId, id) => `${root7(workspaceId)}/${encodeURIComponent(id)}/finalize`
31609
+ finalize: (workspaceId, id) => `${root7(workspaceId)}/${encodeURIComponent(id)}/finalize`,
31610
+ test: (workspaceId, id) => `${root7(workspaceId)}/${encodeURIComponent(id)}/test`,
31611
+ testRun: (workspaceId, id, runId) => `${root7(workspaceId)}/${encodeURIComponent(id)}/test-runs/${encodeURIComponent(runId)}`
31492
31612
  };
31493
31613
  function root7(workspaceId) {
31494
31614
  return `/workspaces/${encodeURIComponent(workspaceId)}/automation-drafts`;
@@ -31554,6 +31674,24 @@ var WorkspaceAutomationDraftsClient = class extends BaseClient {
31554
31674
  });
31555
31675
  return finalizeAutomationDraftResponseSchema.parse(data);
31556
31676
  }
31677
+ async test(workspaceId, draftId, request2 = {}, options) {
31678
+ const data = await this.request({
31679
+ method: "POST",
31680
+ path: workspaceAutomationDraftsEndpoints.test(IdSchema.parse(workspaceId), IdSchema.parse(draftId)),
31681
+ body: testAutomationDraftRequestSchema.parse(request2),
31682
+ options
31683
+ });
31684
+ return testAutomationDraftResponseSchema.parse(data);
31685
+ }
31686
+ async getTestRun(workspaceId, draftId, runId, args = {}, options) {
31687
+ const data = await this.request({
31688
+ method: "GET",
31689
+ path: workspaceAutomationDraftsEndpoints.testRun(IdSchema.parse(workspaceId), IdSchema.parse(draftId), IdSchema.parse(runId)),
31690
+ query: toTestRunQuery(args),
31691
+ options
31692
+ });
31693
+ return DraftTestRunDetailDtoSchema.parse(data);
31694
+ }
31557
31695
  };
31558
31696
 
31559
31697
  // ../../packages/sdk/dist/esm/clients/workspace-chat/endpoints.js
@@ -32497,25 +32635,47 @@ function summarizeRun(run) {
32497
32635
  result: run.result,
32498
32636
  temporalUrl: run.temporalUrl,
32499
32637
  triggeredBy: run.triggeredBy,
32500
- steps: run.steps.map((step) => ({
32501
- stepId: step.stepId,
32502
- type: step.type,
32503
- description: step.description,
32504
- status: step.status,
32505
- startedAt: step.startedAt,
32506
- completedAt: step.completedAt,
32507
- error: step.error,
32508
- toolCalls: step.toolCalls?.map((toolCall) => ({
32509
- id: toolCall.id,
32510
- name: toolCall.name,
32511
- argumentKeys: toolCall.argumentKeys,
32512
- hasResult: toolCall.hasResult,
32513
- hasError: toolCall.hasError,
32514
- resultPreview: toolCall.resultPreview,
32515
- errorPreview: toolCall.errorPreview,
32516
- timestamp: toolCall.timestamp
32517
- }))
32638
+ steps: summarizeRunSteps(run.steps)
32639
+ };
32640
+ }
32641
+ function summarizeRunSteps(steps) {
32642
+ return steps.map((step) => ({
32643
+ stepId: step.stepId,
32644
+ type: step.type,
32645
+ description: step.description,
32646
+ status: step.status,
32647
+ startedAt: step.startedAt,
32648
+ completedAt: step.completedAt,
32649
+ error: step.error,
32650
+ toolCalls: step.toolCalls?.map((toolCall) => ({
32651
+ id: toolCall.id,
32652
+ name: toolCall.name,
32653
+ argumentKeys: toolCall.argumentKeys,
32654
+ hasResult: toolCall.hasResult,
32655
+ hasError: toolCall.hasError,
32656
+ resultPreview: toolCall.resultPreview,
32657
+ errorPreview: toolCall.errorPreview,
32658
+ timestamp: toolCall.timestamp
32518
32659
  }))
32660
+ }));
32661
+ }
32662
+
32663
+ // ../../packages/sdk/dist/esm/summaries/draft-test-run-summary.js
32664
+ function summarizeDraftTestRun(run) {
32665
+ return {
32666
+ id: run.id,
32667
+ draftId: run.draftId,
32668
+ automationId: run.automationId,
32669
+ status: run.status,
32670
+ connectorCalls: run.connectorCalls,
32671
+ createdAt: run.createdAt,
32672
+ updatedAt: run.updatedAt,
32673
+ failureCategory: getRunOutcomeCategory(run),
32674
+ failureReason: getRunOutcomeReason(run),
32675
+ failedStep: run.failedStep,
32676
+ result: run.result,
32677
+ temporalUrl: run.temporalUrl,
32678
+ steps: summarizeRunSteps(run.steps)
32519
32679
  };
32520
32680
  }
32521
32681
 
@@ -32712,6 +32872,313 @@ async function findAutomationsUsingConnection(api, connectionId, options) {
32712
32872
  }
32713
32873
  }
32714
32874
 
32875
+ // ../../packages/sdk/dist/esm/drafts/draft-api.js
32876
+ function bindDraftApi(api, workspaceId, { signal } = {}) {
32877
+ const createOptions = { signal, translate: false };
32878
+ const listConnections = (args) => api.connections.list(args, { signal });
32879
+ if (workspaceId === null) {
32880
+ return {
32881
+ workspaceId: null,
32882
+ get: (draftId) => api.automationDrafts.get(draftId, { signal }),
32883
+ create: (manifest) => api.automationDrafts.create(manifest, createOptions),
32884
+ update: (draftId, manifest) => api.automationDrafts.update(draftId, manifest, { signal }),
32885
+ createAutomationDraft: (automationId) => api.automations.createDraft(automationId, { signal }),
32886
+ upsertConnection: (draftId, body) => api.automationDraftConnections.upsert(draftId, body, { signal }),
32887
+ getConnectionStatus: async (draftId) => (await api.automationDraftConnections.getStatus(draftId, { signal })).validation,
32888
+ listConnections,
32889
+ finalize: (draftId) => api.automationDrafts.finalize(draftId, { signal }),
32890
+ getAutomation: (automationId) => api.automations.get(automationId, { signal }),
32891
+ test: (draftId, request2) => api.automationDrafts.test(draftId, request2, { signal }),
32892
+ getTestRun: (draftId, runId, args, requestSignal) => api.automationDrafts.getTestRun(draftId, runId, args, {
32893
+ signal: requestSignal ?? signal
32894
+ })
32895
+ };
32896
+ }
32897
+ return {
32898
+ workspaceId,
32899
+ get: (draftId) => api.workspaceAutomationDrafts.get(workspaceId, draftId, { signal }),
32900
+ create: (manifest) => api.workspaceAutomationDrafts.create(workspaceId, manifest, createOptions),
32901
+ update: (draftId, manifest) => api.workspaceAutomationDrafts.update(workspaceId, draftId, manifest, { signal }),
32902
+ createAutomationDraft: (automationId) => api.workspaceAutomations.createDraft(workspaceId, automationId, { signal }),
32903
+ upsertConnection: (draftId, body) => api.workspaceAutomationDraftConnections.upsert(workspaceId, draftId, body, { signal }),
32904
+ getConnectionStatus: async (draftId) => (await api.workspaceAutomationDraftConnections.getStatus(workspaceId, draftId, {
32905
+ signal
32906
+ })).validation,
32907
+ listConnections,
32908
+ finalize: (draftId) => api.workspaceAutomationDrafts.finalize(workspaceId, draftId, { signal }),
32909
+ getAutomation: (automationId) => api.workspaceAutomations.get(workspaceId, automationId, { signal }),
32910
+ test: (draftId, request2) => api.workspaceAutomationDrafts.test(workspaceId, draftId, request2, { signal }),
32911
+ getTestRun: (draftId, runId, args, requestSignal) => api.workspaceAutomationDrafts.getTestRun(workspaceId, draftId, runId, args, {
32912
+ signal: requestSignal ?? signal
32913
+ })
32914
+ };
32915
+ }
32916
+
32917
+ // ../../packages/sdk/dist/esm/drafts/draft-workflow-error.js
32918
+ var DraftWorkflowError = class extends Error {
32919
+ code;
32920
+ draftId;
32921
+ automationId;
32922
+ constructor(code, message, options = {}) {
32923
+ super(message, { cause: options.cause });
32924
+ this.name = "DraftWorkflowError";
32925
+ this.code = code;
32926
+ this.draftId = options.draftId;
32927
+ this.automationId = options.automationId;
32928
+ }
32929
+ };
32930
+ function getErrorMessage(error62) {
32931
+ return error62 instanceof Error ? error62.message : String(error62);
32932
+ }
32933
+
32934
+ // ../../packages/sdk/dist/esm/drafts/draft-changes.js
32935
+ async function createDraft(draftApi, { manifest, automationId }) {
32936
+ const partialManifest = parsePartialManifest(manifest);
32937
+ if (automationId === void 0) {
32938
+ const result = await draftApi.create(partialManifest);
32939
+ return { action: "created", target: toDraftTarget(result.draft), ...result };
32940
+ }
32941
+ const { draft } = await openEditDraft(draftApi, automationId);
32942
+ try {
32943
+ const result = await draftApi.update(draft.id, partialManifest);
32944
+ return { action: "created", target: toDraftTarget(result.draft), ...result };
32945
+ } catch (error62) {
32946
+ if (!isManifestRejection(error62))
32947
+ throw error62;
32948
+ throw new DraftWorkflowError("draft_manifest_rejected", `Opened draft ${draft.id} for automation ${automationId}, but the manifest was rejected: ${getErrorMessage(error62)}`, { draftId: draft.id, automationId, cause: error62 });
32949
+ }
32950
+ }
32951
+ async function updateDraft(draftApi, { draftId, manifest }) {
32952
+ const partialManifest = parsePartialManifest(manifest);
32953
+ const result = await draftApi.update(draftId, partialManifest);
32954
+ return { action: "updated", target: toDraftTarget(result.draft), ...result };
32955
+ }
32956
+ function toDraftTarget(draft) {
32957
+ return draft.automationId === null ? { kind: "new" } : { kind: "automation", automationId: draft.automationId };
32958
+ }
32959
+ function parsePartialManifest(value) {
32960
+ const validation = validatePartialManifest(value);
32961
+ if (!validation.isValid) {
32962
+ throw new DraftWorkflowError("manifest_invalid", `Manifest is invalid: ${validation.errors.join("; ")}`);
32963
+ }
32964
+ return validation.data;
32965
+ }
32966
+ async function openEditDraft(draftApi, automationId) {
32967
+ try {
32968
+ return await draftApi.createAutomationDraft(automationId);
32969
+ } catch (error62) {
32970
+ const conflict = getOpenDraftConflictError(error62);
32971
+ if (!conflict)
32972
+ throw error62;
32973
+ throw new DraftWorkflowError("draft_conflict", `Automation ${automationId} already has an open draft ${conflict.draftId}.`, { draftId: conflict.draftId, automationId, cause: error62 });
32974
+ }
32975
+ }
32976
+ function isManifestRejection(error62) {
32977
+ return error62 instanceof AutomationsRequestError && (error62.httpStatusCode === 400 || error62.httpStatusCode === 422);
32978
+ }
32979
+
32980
+ // ../../packages/sdk/dist/esm/drafts/connect-draft.js
32981
+ async function connectDraft(draftApi, { draftId, selections, auto }) {
32982
+ const bound = [];
32983
+ const required2 = selections.length > 0 ? getRequiredConnectorsFromManifest((await draftApi.get(draftId)).draft.partialManifest) : /* @__PURE__ */ new Map();
32984
+ const explicit = selections.flatMap((selection) => {
32985
+ const types = required2.get(selection.connectorId);
32986
+ if (!types || selection.connectionType && !types.has(selection.connectionType)) {
32987
+ const slot = selection.connectionType ? `${selection.connectorId} (${selection.connectionType})` : selection.connectorId;
32988
+ throw new DraftWorkflowError("connector_not_required", `Connector ${slot} is not used by draft ${draftId}. Required: ${describeRequired(required2)}.`, { draftId });
32989
+ }
32990
+ const connectionTypes = selection.connectionType ? [selection.connectionType] : [...types];
32991
+ return connectionTypes.map((connectionType) => ({ ...selection, connectionType }));
32992
+ });
32993
+ for (const { connectorId, connectionType, connectionId } of explicit) {
32994
+ await draftApi.upsertConnection(draftId, { connectorId, connectionType, connectionId });
32995
+ bound.push({ connectorId, connectionType, connectionId, mode: "explicit" });
32996
+ }
32997
+ const unresolved = [];
32998
+ if (auto) {
32999
+ const status = await draftApi.getConnectionStatus(draftId);
33000
+ for (const connector of status.connectors) {
33001
+ for (const connectionType of ["mcp", "trigger"]) {
33002
+ const slot = connector[connectionType];
33003
+ if (!slot.required || slot.status !== "missing")
33004
+ continue;
33005
+ const { connections } = await draftApi.listConnections({
33006
+ connectorId: connector.id,
33007
+ capability: connectionType,
33008
+ status: "authorized"
33009
+ });
33010
+ const [only] = connections;
33011
+ if (connections.length === 1 && only) {
33012
+ await draftApi.upsertConnection(draftId, {
33013
+ connectorId: connector.id,
33014
+ connectionType,
33015
+ connectionId: only.id
33016
+ });
33017
+ bound.push({
33018
+ connectorId: connector.id,
33019
+ connectionType,
33020
+ connectionId: only.id,
33021
+ mode: "auto"
33022
+ });
33023
+ continue;
33024
+ }
33025
+ unresolved.push({
33026
+ connectorId: connector.id,
33027
+ connectionType,
33028
+ reason: connections.length === 0 ? "no_connection" : "multiple_connections",
33029
+ candidateConnectionIds: connections.map((connection) => connection.id)
33030
+ });
33031
+ }
33032
+ }
33033
+ }
33034
+ const { isValid, missing } = await draftApi.getConnectionStatus(draftId);
33035
+ return { action: "connected", draftId, bound, unresolved, isValid, missing };
33036
+ }
33037
+ function describeRequired(required2) {
33038
+ if (required2.size === 0)
33039
+ return "none";
33040
+ return [...required2].map(([connectorId, types]) => `${connectorId} (${[...types].join(", ")})`).join(", ");
33041
+ }
33042
+
33043
+ // ../../packages/sdk/dist/esm/drafts/manifest-diff.js
33044
+ function stableStringify(value) {
33045
+ return JSON.stringify(sortKeys(value));
33046
+ }
33047
+ function isDeepEqual(left, right) {
33048
+ return stableStringify(left) === stableStringify(right);
33049
+ }
33050
+ function diffManifests(before, after) {
33051
+ const previous = asManifest(before);
33052
+ const next = asManifest(after);
33053
+ const changedProperties = [.../* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(next)])].filter((key) => key !== "steps").filter((key) => !isDeepEqual(previous[key], next[key])).sort();
33054
+ const previousSteps = stepsById(previous.steps);
33055
+ const nextSteps = stepsById(next.steps);
33056
+ const added = [...nextSteps.keys()].filter((id) => !previousSteps.has(id));
33057
+ const removed = [...previousSteps.keys()].filter((id) => !nextSteps.has(id));
33058
+ const changed = [...nextSteps].filter(([id, step]) => previousSteps.has(id) && !isDeepEqual(previousSteps.get(id), step)).map(([id]) => id);
33059
+ const sharedBefore = [...previousSteps.keys()].filter((id) => nextSteps.has(id));
33060
+ const sharedAfter = [...nextSteps.keys()].filter((id) => previousSteps.has(id));
33061
+ const reordered = sharedBefore.some((id, index) => sharedAfter[index] !== id);
33062
+ return {
33063
+ changedProperties,
33064
+ steps: { added, removed, changed, reordered },
33065
+ isEmpty: changedProperties.length === 0 && added.length === 0 && removed.length === 0 && changed.length === 0 && !reordered
33066
+ };
33067
+ }
33068
+ function asManifest(value) {
33069
+ return isManifestRecord(value) ? value : {};
33070
+ }
33071
+ function stepsById(steps) {
33072
+ const byId = /* @__PURE__ */ new Map();
33073
+ if (!Array.isArray(steps))
33074
+ return byId;
33075
+ steps.forEach((step, index) => {
33076
+ const id = isManifestRecord(step) && typeof step.id === "string" ? step.id : `#${index}`;
33077
+ byId.set(id, step);
33078
+ });
33079
+ return byId;
33080
+ }
33081
+ function sortKeys(value) {
33082
+ if (Array.isArray(value))
33083
+ return value.map(sortKeys);
33084
+ if (!isManifestRecord(value))
33085
+ return value;
33086
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortKeys(value[key])]));
33087
+ }
33088
+
33089
+ // ../../packages/sdk/dist/esm/drafts/publish-draft.js
33090
+ async function publishDraft(draftApi, { draftId, confirmed }) {
33091
+ const { summary, manifest } = await summarizeForPublish(draftApi, draftId);
33092
+ if (!confirmed) {
33093
+ return { action: "publish_preview", summary };
33094
+ }
33095
+ let published;
33096
+ try {
33097
+ published = await draftApi.finalize(draftId);
33098
+ } catch (error62) {
33099
+ const notReady = getDraftNotReadyError(error62);
33100
+ if (!notReady)
33101
+ throw error62;
33102
+ throw new DraftWorkflowError("draft_not_ready", notReady.message, {
33103
+ draftId,
33104
+ cause: error62
33105
+ });
33106
+ }
33107
+ let automation;
33108
+ try {
33109
+ automation = await draftApi.getAutomation(published.automation.id);
33110
+ } catch (error62) {
33111
+ return {
33112
+ action: "published",
33113
+ summary,
33114
+ automation: published.automation,
33115
+ verification: { status: "unavailable", reason: getErrorMessage(error62) }
33116
+ };
33117
+ }
33118
+ const expected = normaliseManifest(manifest);
33119
+ const actual = normaliseManifest(automation.manifest);
33120
+ return {
33121
+ action: "published",
33122
+ summary,
33123
+ automation,
33124
+ verification: isDeepEqual(expected, actual) ? { status: "matched" } : { status: "mismatched", mismatch: diffManifests(expected, actual) }
33125
+ };
33126
+ }
33127
+ async function summarizeForPublish(draftApi, draftId) {
33128
+ const [detail, connections] = await Promise.all([
33129
+ draftApi.get(draftId),
33130
+ draftApi.getConnectionStatus(draftId)
33131
+ ]);
33132
+ const { draft } = detail;
33133
+ const target = toDraftTarget(draft);
33134
+ const published = target.kind === "automation" ? await draftApi.getAutomation(target.automationId) : null;
33135
+ const diff = published ? diffManifests(normaliseManifest(published.manifest), normaliseManifest(draft.partialManifest)) : null;
33136
+ const summary = {
33137
+ draftId,
33138
+ name: readManifestName(draft.partialManifest),
33139
+ scope: draft.workspaceId === null ? { kind: "personal" } : {
33140
+ kind: "workspace",
33141
+ workspaceId: draft.workspaceId,
33142
+ // A new workspace automation publishes team-visible; an
33143
+ // edit keeps whatever the automation already has.
33144
+ visibility: published?.visibility ?? "workspace"
33145
+ },
33146
+ target,
33147
+ enabled: published?.enabled ?? true,
33148
+ validation: { state: detail.state, issues: detail.issues },
33149
+ connections: {
33150
+ isValid: connections.isValid,
33151
+ connected: connections.connected.map(({ connectorId, connectionType, connectionId }) => ({
33152
+ connectorId,
33153
+ connectionType,
33154
+ connectionId
33155
+ })),
33156
+ missing: connections.missing
33157
+ },
33158
+ schedule: describeSchedule(draft.partialManifest),
33159
+ diff
33160
+ };
33161
+ return { summary, manifest: draft.partialManifest };
33162
+ }
33163
+ function describeSchedule(manifest) {
33164
+ if (!hasTriggerType(manifest))
33165
+ return null;
33166
+ const schedule = getManifestTriggers(manifest).find((trigger) => trigger.type === "schedule");
33167
+ if (!schedule || schedule.type !== "schedule")
33168
+ return null;
33169
+ return getScheduleTriggerDescription({ value: schedule.value, timeFormat: "24h" });
33170
+ }
33171
+ function normaliseManifest(manifest) {
33172
+ const parsed = manifestSchema.safeParse(manifest);
33173
+ return parsed.success ? parsed.data : manifest;
33174
+ }
33175
+ function readManifestName(manifest) {
33176
+ if (!isManifestRecord(manifest))
33177
+ return "(unnamed)";
33178
+ const { name } = manifest;
33179
+ return typeof name === "string" && name.trim() ? name : "(unnamed)";
33180
+ }
33181
+
32715
33182
  // src/lib/auth-environment.ts
32716
33183
  import { InvalidArgumentError } from "commander";
32717
33184
  var ENVIRONMENTS = {
@@ -32935,7 +33402,7 @@ function createAutomationsTokenStore(options = {}) {
32935
33402
  }
32936
33403
 
32937
33404
  // src/lib/credentials.ts
32938
- import { CliError, getConfigPath as getConfigPath3, getErrorMessage as getErrorMessage3 } from "@doist/cli-core";
33405
+ import { CliError, getConfigPath as getConfigPath3, getErrorMessage as getErrorMessage4 } from "@doist/cli-core";
32939
33406
  import { refreshAccessToken } from "@doist/cli-core/auth";
32940
33407
 
32941
33408
  // src/lib/auth-scopes.ts
@@ -33025,7 +33492,7 @@ function formatScopesHelp() {
33025
33492
  }
33026
33493
 
33027
33494
  // src/lib/auth-provider.ts
33028
- import { getErrorMessage } from "@doist/cli-core";
33495
+ import { getErrorMessage as getErrorMessage2 } from "@doist/cli-core";
33029
33496
  import { createPkceProvider } from "@doist/cli-core/auth";
33030
33497
  var ERROR_HINTS = ["Try again: tda auth login"];
33031
33498
  function createAutomationsAuthProvider() {
@@ -33065,7 +33532,7 @@ async function fetchTodoistUser(token, todoistApiUrl) {
33065
33532
  });
33066
33533
  } catch (error62) {
33067
33534
  throw new Error(
33068
- `Could not reach Todoist to identify the account: ${getErrorMessage(error62)}`
33535
+ `Could not reach Todoist to identify the account: ${getErrorMessage2(error62)}`
33069
33536
  );
33070
33537
  }
33071
33538
  if (!response.ok) {
@@ -33078,7 +33545,7 @@ async function fetchTodoistUser(token, todoistApiUrl) {
33078
33545
  user = await response.json();
33079
33546
  } catch (error62) {
33080
33547
  throw new Error(
33081
- `Todoist returned an unreadable identity response: ${getErrorMessage(error62)}`
33548
+ `Todoist returned an unreadable identity response: ${getErrorMessage2(error62)}`
33082
33549
  );
33083
33550
  }
33084
33551
  return { id: requireUserId(user.id), email: typeof user.email === "string" ? user.email : "" };
@@ -33090,13 +33557,13 @@ function requireUserId(id) {
33090
33557
  }
33091
33558
 
33092
33559
  // src/lib/errors.ts
33093
- import { getErrorMessage as getErrorMessage2 } from "@doist/cli-core";
33560
+ import { getErrorMessage as getErrorMessage3 } from "@doist/cli-core";
33094
33561
  function getErrorCode(error62) {
33095
33562
  return typeof error62 === "object" && error62 !== null && "code" in error62 ? String(error62.code) : void 0;
33096
33563
  }
33097
33564
  function formatError2(error62) {
33098
33565
  const hints = errorHints(error62);
33099
- return [getErrorMessage2(error62), ...hints.map((hint) => ` - ${hint}`)].join("\n");
33566
+ return [getErrorMessage3(error62), ...hints.map((hint) => ` - ${hint}`)].join("\n");
33100
33567
  }
33101
33568
  function errorHints(error62) {
33102
33569
  if (typeof error62 !== "object" || error62 === null || !("hints" in error62)) return [];
@@ -33176,7 +33643,7 @@ function accessTokenAfterFailedRefresh(error62, snapshot) {
33176
33643
  if (hasTokenLifeLeft(snapshot)) return snapshot.bundle.accessToken;
33177
33644
  throw new CredentialError(
33178
33645
  "REFRESH_FAILED",
33179
- `Could not refresh your access token: ${getErrorMessage3(error62)}`,
33646
+ `Could not refresh your access token: ${getErrorMessage4(error62)}`,
33180
33647
  ["Check your network connection and try again."]
33181
33648
  );
33182
33649
  }
@@ -33204,19 +33671,19 @@ function resolveAfterStoreFailure(error62) {
33204
33671
  if (code !== "AUTH_STORE_READ_FAILED") {
33205
33672
  throw new CredentialError(
33206
33673
  "CREDENTIAL_UNREADABLE",
33207
- `Could not read the stored credential: ${getErrorMessage3(error62)}`,
33674
+ `Could not read the stored credential: ${getErrorMessage4(error62)}`,
33208
33675
  ["Run `tda auth logout` and then `tda auth login` to replace it."]
33209
33676
  );
33210
33677
  }
33211
33678
  const fallback = fromEnvironment();
33212
33679
  if (fallback) {
33213
33680
  process.stderr.write(
33214
- `Warning: could not read the stored credential (${getErrorMessage3(error62)}). Using ${TOKEN_ENV_VAR}.
33681
+ `Warning: could not read the stored credential (${getErrorMessage4(error62)}). Using ${TOKEN_ENV_VAR}.
33215
33682
  `
33216
33683
  );
33217
33684
  return fallback;
33218
33685
  }
33219
- throw new CredentialError("CREDENTIAL_UNREADABLE", getErrorMessage3(error62), [
33686
+ throw new CredentialError("CREDENTIAL_UNREADABLE", getErrorMessage4(error62), [
33220
33687
  `Set ${TOKEN_ENV_VAR} to bypass the credential store.`,
33221
33688
  "Or re-run `tda auth login --credential-store plaintext`."
33222
33689
  ]);
@@ -33879,7 +34346,7 @@ function attachAutomationsLoginCommand(auth) {
33879
34346
  }
33880
34347
 
33881
34348
  // src/commands/auth/logout.ts
33882
- import { getErrorMessage as getErrorMessage4 } from "@doist/cli-core";
34349
+ import { getErrorMessage as getErrorMessage5 } from "@doist/cli-core";
33883
34350
  import { attachLogoutCommand } from "@doist/cli-core/auth";
33884
34351
 
33885
34352
  // src/lib/spinner.ts
@@ -33927,7 +34394,7 @@ async function revokeToken(token) {
33927
34394
  });
33928
34395
  return response.ok ? void 0 : `HTTP ${response.status}`;
33929
34396
  } catch (error62) {
33930
- return getErrorMessage4(error62);
34397
+ return getErrorMessage5(error62);
33931
34398
  }
33932
34399
  }
33933
34400
 
@@ -34058,7 +34525,7 @@ function parsePositiveInteger(value) {
34058
34525
  }
34059
34526
 
34060
34527
  // src/lib/command-error.ts
34061
- import { formatJson as formatJson4, formatNdjson as formatNdjson4, getErrorMessage as getErrorMessage5 } from "@doist/cli-core";
34528
+ import { formatJson as formatJson4, formatNdjson as formatNdjson4, getErrorMessage as getErrorMessage6 } from "@doist/cli-core";
34062
34529
 
34063
34530
  // src/lib/billing-errors.ts
34064
34531
  import { CliError as CliError2 } from "@doist/cli-core";
@@ -34137,7 +34604,7 @@ function renderFailure(error62, context = {}) {
34137
34604
  const explained = explainBillingFailure(authExplained);
34138
34605
  const code = getErrorCode(explained);
34139
34606
  return {
34140
- message: getErrorMessage5(explained),
34607
+ message: getErrorMessage6(explained),
34141
34608
  humanMessage: formatError2(explained),
34142
34609
  ...code ? { code } : {}
34143
34610
  };
@@ -34168,7 +34635,9 @@ async function runApiCommand(command, operation, presentation, execution = {}) {
34168
34635
  try {
34169
34636
  const globalOptions = getGlobalOptions(command);
34170
34637
  mode = getOutputMode(globalOptions);
34171
- const apiConfiguration = await resolveApiConfiguration(globalOptions);
34638
+ const apiConfiguration = await resolveApiConfiguration(globalOptions, {
34639
+ minimumTokenLifetimeMs: execution.minimumTokenLifetimeMs
34640
+ });
34172
34641
  source = apiConfiguration.source;
34173
34642
  const context = {
34174
34643
  api: createAutomationsApi(apiConfiguration),
@@ -34184,6 +34653,8 @@ async function runApiCommand(command, operation, presentation, execution = {}) {
34184
34653
  );
34185
34654
  stopEarlySpinner();
34186
34655
  writeResult(mode, result, presentation);
34656
+ const exitCode = execution.exitCode?.(result);
34657
+ if (exitCode !== void 0) process.exitCode = exitCode;
34187
34658
  } catch (error62) {
34188
34659
  if (abortController.signal.aborted) {
34189
34660
  writeFailure(mode, "interrupted", {
@@ -34351,16 +34822,21 @@ function formatRun(run) {
34351
34822
  `Failure reason: ${getRunOutcomeReason(run) ?? "none"}`,
34352
34823
  `Result: ${run.result?.text ?? "none"}`,
34353
34824
  `Steps: ${run.steps.length}`,
34354
- ...isInsufficientCredit(run) ? ["", topUpAdvisory()] : []
34825
+ ...isInsufficientCredit(run) ? ["", topUpAdvisory()] : [],
34826
+ ...formatRunSteps(run.steps)
34355
34827
  ];
34356
- for (const [index, step] of run.steps.entries()) {
34828
+ return lines.join("\n");
34829
+ }
34830
+ function formatRunSteps(steps, { toolCallLabel = "Tool" } = {}) {
34831
+ const lines = [];
34832
+ for (const [index, step] of steps.entries()) {
34357
34833
  lines.push("", `${index + 1}. ${step.stepId} [${step.status}] ${step.description}`);
34358
34834
  lines.push(` Type: ${step.type}`);
34359
34835
  if (step.startedAt) lines.push(` Started: ${step.startedAt}`);
34360
34836
  if (step.completedAt) lines.push(` Completed: ${step.completedAt}`);
34361
34837
  if (step.error) lines.push(` Error: ${step.error}`);
34362
34838
  for (const toolCall of step.toolCalls ?? []) {
34363
- lines.push(` Tool: ${toolCall.name} (${toolCall.id})`);
34839
+ lines.push(` ${toolCallLabel}: ${toolCall.name} (${toolCall.id})`);
34364
34840
  if (toolCall.argumentKeys.length > 0) {
34365
34841
  lines.push(` Args: ${toolCall.argumentKeys.join(", ")}`);
34366
34842
  }
@@ -34370,7 +34846,7 @@ function formatRun(run) {
34370
34846
  if (toolCall.resultPreview) lines.push(` Result preview: ${toolCall.resultPreview}`);
34371
34847
  }
34372
34848
  }
34373
- return lines.join("\n");
34849
+ return lines;
34374
34850
  }
34375
34851
  function getResourceName(manifest) {
34376
34852
  if (!isRecord2(manifest)) return "(unnamed)";
@@ -36786,7 +37262,7 @@ function registerConnectorCommand(program) {
36786
37262
  }
36787
37263
 
36788
37264
  // src/lib/manifest-input.ts
36789
- import { CliError as CliError3, getErrorMessage as getErrorMessage6 } from "@doist/cli-core";
37265
+ import { CliError as CliError3, getErrorMessage as getErrorMessage7 } from "@doist/cli-core";
36790
37266
  import { Option as Option6 } from "commander";
36791
37267
  import { readFile as readFile2 } from "node:fs/promises";
36792
37268
  import { text } from "node:stream/consumers";
@@ -36802,20 +37278,10 @@ async function readManifestInput(options, input2 = process.stdin) {
36802
37278
  } catch (error62) {
36803
37279
  throw new CliError3(
36804
37280
  "MANIFEST_UNREADABLE",
36805
- `${source.label} is not valid JSON: ${getErrorMessage6(error62)}`
37281
+ `${source.label} is not valid JSON: ${getErrorMessage7(error62)}`
36806
37282
  );
36807
37283
  }
36808
37284
  }
36809
- function parsePartialManifestInput(value) {
36810
- const validation = validatePartialManifest(value);
36811
- if (!validation.isValid) {
36812
- throw new CliError3(
36813
- "MANIFEST_INVALID",
36814
- `Manifest is invalid: ${validation.errors.join("; ")}`
36815
- );
36816
- }
36817
- return validation.data;
36818
- }
36819
37285
  async function readSource(options, input2) {
36820
37286
  if (options.file !== void 0) {
36821
37287
  try {
@@ -36823,7 +37289,7 @@ async function readSource(options, input2) {
36823
37289
  } catch (error62) {
36824
37290
  throw new CliError3(
36825
37291
  "MANIFEST_UNREADABLE",
36826
- `Could not read ${options.file}: ${getErrorMessage6(error62)}`
37292
+ `Could not read ${options.file}: ${getErrorMessage7(error62)}`
36827
37293
  );
36828
37294
  }
36829
37295
  }
@@ -36833,7 +37299,7 @@ async function readSource(options, input2) {
36833
37299
  } catch (error62) {
36834
37300
  throw new CliError3(
36835
37301
  "MANIFEST_UNREADABLE",
36836
- `Could not read standard input: ${getErrorMessage6(error62)}`
37302
+ `Could not read standard input: ${getErrorMessage7(error62)}`
36837
37303
  );
36838
37304
  }
36839
37305
  }
@@ -36841,124 +37307,56 @@ async function readSource(options, input2) {
36841
37307
  }
36842
37308
 
36843
37309
  // src/commands/draft/draft-service.ts
36844
- import { CliError as CliError4, getErrorMessage as getErrorMessage7 } from "@doist/cli-core";
36845
-
36846
- // src/lib/manifest-diff.ts
36847
- function stableStringify(value) {
36848
- return JSON.stringify(sortKeys(value));
36849
- }
36850
- function isDeepEqual(left, right) {
36851
- return stableStringify(left) === stableStringify(right);
37310
+ import { CliError as CliError4, getErrorMessage as getErrorMessage8 } from "@doist/cli-core";
37311
+ import { readFile as readFile3 } from "node:fs/promises";
37312
+ import { setTimeout as wait2 } from "node:timers/promises";
37313
+ function createDraft2(draftApi, input2) {
37314
+ return withRecoveryHints(draftApi, createDraft(draftApi, input2));
36852
37315
  }
36853
- function diffManifests(before, after) {
36854
- const previous = asManifest(before);
36855
- const next = asManifest(after);
36856
- const changedProperties = [.../* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(next)])].filter((key) => key !== "steps").filter((key) => !isDeepEqual(previous[key], next[key])).sort();
36857
- const previousSteps = stepsById(previous.steps);
36858
- const nextSteps = stepsById(next.steps);
36859
- const added = [...nextSteps.keys()].filter((id) => !previousSteps.has(id));
36860
- const removed = [...previousSteps.keys()].filter((id) => !nextSteps.has(id));
36861
- const changed = [...nextSteps].filter(([id, step]) => previousSteps.has(id) && !isDeepEqual(previousSteps.get(id), step)).map(([id]) => id);
36862
- const sharedBefore = [...previousSteps.keys()].filter((id) => nextSteps.has(id));
36863
- const sharedAfter = [...nextSteps.keys()].filter((id) => previousSteps.has(id));
36864
- const reordered = sharedBefore.some((id, index) => sharedAfter[index] !== id);
36865
- return {
36866
- changedProperties,
36867
- steps: { added, removed, changed, reordered },
36868
- isEmpty: changedProperties.length === 0 && added.length === 0 && removed.length === 0 && changed.length === 0 && !reordered
36869
- };
37316
+ function updateDraft2(draftApi, input2) {
37317
+ return withRecoveryHints(draftApi, updateDraft(draftApi, input2));
36870
37318
  }
36871
- function asManifest(value) {
36872
- return isRecord3(value) ? value : {};
37319
+ function connectDraft2(draftApi, input2) {
37320
+ return withRecoveryHints(draftApi, connectDraft(draftApi, input2));
36873
37321
  }
36874
- function stepsById(steps) {
36875
- const byId = /* @__PURE__ */ new Map();
36876
- if (!Array.isArray(steps)) return byId;
36877
- steps.forEach((step, index) => {
36878
- const id = isRecord3(step) && typeof step.id === "string" ? step.id : `#${index}`;
36879
- byId.set(id, step);
36880
- });
36881
- return byId;
36882
- }
36883
- function sortKeys(value) {
36884
- if (Array.isArray(value)) return value.map(sortKeys);
36885
- if (!isRecord3(value)) return value;
36886
- return Object.fromEntries(
36887
- Object.keys(value).sort().map((key) => [key, sortKeys(value[key])])
36888
- );
36889
- }
36890
- function isRecord3(value) {
36891
- return typeof value === "object" && value !== null && !Array.isArray(value);
36892
- }
36893
-
36894
- // src/commands/draft/draft-service.ts
36895
- function resolveDraftApi(api, workspaceId, signal) {
36896
- const createOptions = { signal, translate: false };
36897
- if (workspaceId === null) {
36898
- return {
36899
- scope: null,
36900
- get: (draftId) => api.automationDrafts.get(draftId, { signal }),
36901
- create: (manifest) => api.automationDrafts.create(manifest, createOptions),
36902
- update: (draftId, manifest) => api.automationDrafts.update(draftId, manifest, { signal }),
36903
- createAutomationDraft: (automationId) => api.automations.createDraft(automationId, { signal }),
36904
- upsertConnection: (draftId, body) => api.automationDraftConnections.upsert(draftId, body, { signal }),
36905
- getConnectionStatus: async (draftId) => (await api.automationDraftConnections.getStatus(draftId, { signal })).validation,
36906
- finalize: (draftId) => api.automationDrafts.finalize(draftId, { signal }),
36907
- getAutomation: (automationId) => api.automations.get(automationId, { signal })
36908
- };
36909
- }
37322
+ async function publishDraft2(draftApi, input2) {
37323
+ const result = await withRecoveryHints(draftApi, publishDraft(draftApi, input2));
37324
+ if (result.action === "publish_preview") return result;
37325
+ const { verification } = result;
37326
+ if (verification.status !== "unavailable") return { ...result, verification };
36910
37327
  return {
36911
- scope: workspaceId,
36912
- get: (draftId) => api.workspaceAutomationDrafts.get(workspaceId, draftId, { signal }),
36913
- create: (manifest) => api.workspaceAutomationDrafts.create(workspaceId, manifest, createOptions),
36914
- update: (draftId, manifest) => api.workspaceAutomationDrafts.update(workspaceId, draftId, manifest, { signal }),
36915
- createAutomationDraft: (automationId) => api.workspaceAutomations.createDraft(workspaceId, automationId, { signal }),
36916
- upsertConnection: (draftId, body) => api.workspaceAutomationDraftConnections.upsert(workspaceId, draftId, body, { signal }),
36917
- getConnectionStatus: async (draftId) => (await api.workspaceAutomationDraftConnections.getStatus(workspaceId, draftId, {
36918
- signal
36919
- })).validation,
36920
- finalize: (draftId) => api.workspaceAutomationDrafts.finalize(workspaceId, draftId, { signal }),
36921
- getAutomation: (automationId) => api.workspaceAutomations.get(workspaceId, automationId, { signal })
37328
+ ...result,
37329
+ verification: {
37330
+ ...verification,
37331
+ command: `tda automation view ${result.automation.id} ${scopeFlag(draftApi.workspaceId)}`
37332
+ }
36922
37333
  };
36923
37334
  }
36924
- async function createDraft(draftApi, { manifest, automationId }) {
36925
- const partialManifest = parsePartialManifestInput(manifest);
36926
- if (automationId === void 0) {
36927
- const result = await draftApi.create(partialManifest);
36928
- return { action: "created", target: toTarget(result.draft), ...result };
36929
- }
36930
- const { draft } = await openEditDraft(draftApi, automationId);
37335
+ async function withRecoveryHints(draftApi, operation) {
36931
37336
  try {
36932
- const result = await draftApi.update(draft.id, partialManifest);
36933
- return { action: "created", target: toTarget(result.draft), ...result };
37337
+ return await operation;
36934
37338
  } catch (error62) {
36935
- if (!isManifestRejection(error62)) throw error62;
36936
- throw new CliError4(
36937
- "DRAFT_MANIFEST_REJECTED",
36938
- `Opened draft ${draft.id} for automation ${automationId}, but the manifest was rejected: ${getErrorMessage7(error62)}`,
36939
- { hints: draftHints(draftApi.scope, draft.id) }
36940
- );
36941
- }
36942
- }
36943
- function isManifestRejection(error62) {
36944
- return error62 instanceof AutomationsRequestError && (error62.httpStatusCode === 400 || error62.httpStatusCode === 422);
36945
- }
36946
- async function updateDraft(draftApi, { draftId, manifest }) {
36947
- const partialManifest = parsePartialManifestInput(manifest);
36948
- const result = await draftApi.update(draftId, partialManifest);
36949
- return { action: "updated", target: toTarget(result.draft), ...result };
36950
- }
36951
- async function openEditDraft(draftApi, automationId) {
36952
- try {
36953
- return await draftApi.createAutomationDraft(automationId);
36954
- } catch (error62) {
36955
- const conflict = getOpenDraftConflictError(error62);
36956
- if (!conflict) throw error62;
36957
- throw new CliError4(
36958
- "DRAFT_CONFLICT",
36959
- `Automation ${automationId} already has an open draft ${conflict.draftId}.`,
36960
- { hints: draftHints(draftApi.scope, conflict.draftId) }
36961
- );
37339
+ if (!(error62 instanceof DraftWorkflowError)) throw error62;
37340
+ const scope = draftApi.workspaceId;
37341
+ const draftId = error62.draftId ?? "<draft-id>";
37342
+ switch (error62.code) {
37343
+ case "manifest_invalid":
37344
+ throw new CliError4("MANIFEST_INVALID", error62.message);
37345
+ case "draft_conflict":
37346
+ throw new CliError4("DRAFT_CONFLICT", error62.message, {
37347
+ hints: draftHints(scope, draftId)
37348
+ });
37349
+ case "draft_manifest_rejected":
37350
+ throw new CliError4("DRAFT_MANIFEST_REJECTED", error62.message, {
37351
+ hints: draftHints(scope, draftId)
37352
+ });
37353
+ case "draft_not_ready":
37354
+ throw draftNotReadyError(error62.message, draftId, scope);
37355
+ case "connector_not_required":
37356
+ throw new Error(error62.message);
37357
+ default:
37358
+ throw error62;
37359
+ }
36962
37360
  }
36963
37361
  }
36964
37362
  function draftHints(scope, draftId) {
@@ -36970,172 +37368,135 @@ function draftHints(scope, draftId) {
36970
37368
  function scopeFlag(scope) {
36971
37369
  return scope === null ? "--personal" : `--workspace ${scope}`;
36972
37370
  }
36973
- function toTarget(draft) {
36974
- return draft.automationId === null ? { kind: "new" } : { kind: "automation", automationId: draft.automationId };
36975
- }
36976
- async function connectDraft(draftApi, api, {
37371
+ var DEFAULT_POLL_INTERVALS_MS = { first: 2e3, max: 1e4 };
37372
+ var STATUS_REQUEST_GRACE_MS = 1e4;
37373
+ var OPEN_RUN_STATUSES = /* @__PURE__ */ new Set([
37374
+ "pending",
37375
+ "running"
37376
+ ]);
37377
+ async function startDraftTest(draftApi, {
36977
37378
  draftId,
36978
- selections,
36979
- auto
37379
+ request: request2,
37380
+ wait: waitFor,
37381
+ signal
36980
37382
  }) {
36981
- const bound = [];
36982
- const required2 = selections.length > 0 ? getRequiredConnectorsFromManifest(
36983
- (await draftApi.get(draftId)).draft.partialManifest
36984
- ) : /* @__PURE__ */ new Map();
36985
- for (const selection of selections) {
36986
- const types = required2.get(selection.connectorId);
36987
- if (!types || selection.connectionType && !types.has(selection.connectionType)) {
36988
- const slot = selection.connectionType ? `${selection.connectorId} (${selection.connectionType})` : selection.connectorId;
36989
- throw new Error(
36990
- `Connector ${slot} is not used by draft ${draftId}. Required: ${describeRequired(required2)}.`
37383
+ let started;
37384
+ try {
37385
+ started = await draftApi.test(draftId, request2);
37386
+ } catch (error62) {
37387
+ const refusal = getDraftTestRequestError(error62);
37388
+ if (!refusal) throw error62;
37389
+ throw toDraftTestCliError(refusal, draftId, draftApi.workspaceId);
37390
+ }
37391
+ const base = {
37392
+ draftId,
37393
+ testRunId: started.testRunId,
37394
+ trigger: started.trigger,
37395
+ connectorCalls: started.connectorCalls,
37396
+ command: `tda draft test-run ${draftId} ${started.testRunId} ${scopeFlag(draftApi.workspaceId)}`
37397
+ };
37398
+ if (!waitFor) {
37399
+ return { action: "test_started", ...base, status: started.status };
37400
+ }
37401
+ const intervals = waitFor.pollIntervalsMs ?? DEFAULT_POLL_INTERVALS_MS;
37402
+ const deadline = Date.now() + waitFor.timeoutMs;
37403
+ let intervalMs = intervals.first;
37404
+ let last = null;
37405
+ while (true) {
37406
+ const requestTimeout = AbortSignal.timeout(
37407
+ Math.max(deadline - Date.now(), 0) + (waitFor.requestGraceMs ?? STATUS_REQUEST_GRACE_MS)
37408
+ );
37409
+ try {
37410
+ last = await draftApi.getTestRun(
37411
+ draftId,
37412
+ started.testRunId,
37413
+ void 0,
37414
+ AbortSignal.any([signal, requestTimeout])
36991
37415
  );
37416
+ } catch (error62) {
37417
+ if (signal.aborted || !requestTimeout.aborted) throw error62;
37418
+ return toFinished(base, last, "timed_out");
36992
37419
  }
36993
- const connectionTypes = selection.connectionType ? [selection.connectionType] : [...types];
36994
- for (const connectionType of connectionTypes) {
36995
- await draftApi.upsertConnection(draftId, {
36996
- connectorId: selection.connectorId,
36997
- connectionType,
36998
- connectionId: selection.connectionId
36999
- });
37000
- bound.push({ ...selection, connectionType, mode: "explicit" });
37420
+ if (!OPEN_RUN_STATUSES.has(last.status)) {
37421
+ return toFinished(base, last, toOutcome(last.status));
37001
37422
  }
37002
- }
37003
- const unresolved = [];
37004
- if (auto) {
37005
- const status = await draftApi.getConnectionStatus(draftId);
37006
- for (const connector of status.connectors) {
37007
- for (const connectionType of ["mcp", "trigger"]) {
37008
- const slot = connector[connectionType];
37009
- if (!slot.required || slot.status !== "missing") continue;
37010
- const { connections } = await api.connections.list({
37011
- connectorId: connector.id,
37012
- capability: connectionType,
37013
- status: "authorized"
37014
- });
37015
- const [only] = connections;
37016
- if (connections.length === 1 && only) {
37017
- await draftApi.upsertConnection(draftId, {
37018
- connectorId: connector.id,
37019
- connectionType,
37020
- connectionId: only.id
37021
- });
37022
- bound.push({
37023
- connectorId: connector.id,
37024
- connectionType,
37025
- connectionId: only.id,
37026
- mode: "auto"
37027
- });
37028
- continue;
37029
- }
37030
- unresolved.push({
37031
- connectorId: connector.id,
37032
- connectionType,
37033
- reason: connections.length === 0 ? "no_connection" : "multiple_connections",
37034
- candidateConnectionIds: connections.map((connection) => connection.id)
37035
- });
37036
- }
37423
+ const remainingMs = deadline - Date.now();
37424
+ if (remainingMs <= 0) {
37425
+ return toFinished(base, last, "timed_out");
37037
37426
  }
37427
+ await wait2(Math.min(intervalMs, remainingMs), void 0, { signal });
37428
+ intervalMs = Math.min(intervalMs * 2, intervals.max);
37038
37429
  }
37039
- const { isValid, missing } = await draftApi.getConnectionStatus(draftId);
37040
- return { action: "connected", draftId, bound, unresolved, isValid, missing };
37041
37430
  }
37042
- function describeRequired(required2) {
37043
- if (required2.size === 0) return "none";
37044
- return [...required2].map(([connectorId, types]) => `${connectorId} (${[...types].join(", ")})`).join(", ");
37431
+ function toFinished(base, run, outcome) {
37432
+ return {
37433
+ action: "test_finished",
37434
+ ...base,
37435
+ outcome,
37436
+ status: run?.status ?? "pending",
37437
+ failedStep: run?.failedStep ?? null,
37438
+ error: run?.failedStep?.error ?? (run ? getRunOutcomeReason(run) : null) ?? (run?.status === "data-expired" ? "Temporal no longer holds this run's history, so its outcome is unknown" : null),
37439
+ result: run?.result ?? null
37440
+ };
37441
+ }
37442
+ function toOutcome(status) {
37443
+ switch (status) {
37444
+ case "completed":
37445
+ return "passed";
37446
+ case "skipped":
37447
+ return "skipped";
37448
+ case "cancelled":
37449
+ return "cancelled";
37450
+ default:
37451
+ return "failed";
37452
+ }
37045
37453
  }
37046
- async function publishDraft(draftApi, { draftId, confirmed }) {
37047
- const { summary, manifest } = await summarizeForPublish(draftApi, draftId);
37048
- if (!confirmed) {
37049
- return { action: "publish_preview", summary };
37454
+ function draftNotReadyError(message, draftId, scope) {
37455
+ const flag = scopeFlag(scope);
37456
+ return new CliError4("DRAFT_NOT_READY", message, {
37457
+ hints: [
37458
+ `See the issues: tda draft view ${draftId} ${flag}`,
37459
+ `Bind connections: tda draft connect ${draftId} --auto ${flag}`
37460
+ ]
37461
+ });
37462
+ }
37463
+ function toDraftTestCliError(refusal, draftId, scope) {
37464
+ const flag = scopeFlag(scope);
37465
+ switch (refusal.code) {
37466
+ case "draft_test_not_ready":
37467
+ return draftNotReadyError(refusal.message, draftId, scope);
37468
+ case "draft_test_payload_required":
37469
+ case "draft_test_payload_filtered":
37470
+ return new CliError4(refusal.code.toUpperCase(), refusal.message, {
37471
+ hints: [
37472
+ `Test with a real event: tda draft test ${draftId} --trigger <event> --payload-file <path> ${flag}`
37473
+ ]
37474
+ });
37475
+ default:
37476
+ return new CliError4(refusal.code.toUpperCase(), refusal.message);
37050
37477
  }
37051
- let published;
37478
+ }
37479
+ function parseTriggerOption(value) {
37480
+ return /^\d+$/.test(value) ? { index: Number(value) } : { event: value };
37481
+ }
37482
+ async function readTestPayloadFile(path) {
37483
+ let content;
37052
37484
  try {
37053
- published = await draftApi.finalize(draftId);
37485
+ content = await readFile3(path, "utf8");
37054
37486
  } catch (error62) {
37055
- const notReady = getDraftNotReadyError(error62);
37056
- if (!notReady) throw error62;
37057
- throw new CliError4("DRAFT_NOT_READY", notReady.message, {
37058
- hints: [
37059
- `See the issues: tda draft view ${draftId}`,
37060
- `Bind connections: tda draft connect ${draftId} --auto`
37061
- ]
37062
- });
37487
+ throw new CliError4(
37488
+ "PAYLOAD_UNREADABLE",
37489
+ `Could not read ${path}: ${getErrorMessage8(error62)}`
37490
+ );
37063
37491
  }
37064
- const automationId = published.automation.id;
37065
- let automation;
37066
37492
  try {
37067
- automation = await draftApi.getAutomation(automationId);
37493
+ return JSON.parse(content);
37068
37494
  } catch (error62) {
37069
- return {
37070
- action: "published",
37071
- summary,
37072
- automation: published.automation,
37073
- verification: {
37074
- status: "unavailable",
37075
- reason: getErrorMessage7(error62),
37076
- command: `tda automation view ${automationId} ${scopeFlag(draftApi.scope)}`
37077
- }
37078
- };
37495
+ throw new CliError4(
37496
+ "PAYLOAD_UNREADABLE",
37497
+ `${path} is not valid JSON: ${getErrorMessage8(error62)}`
37498
+ );
37079
37499
  }
37080
- const expected = normaliseManifest(manifest);
37081
- const actual = normaliseManifest(automation.manifest);
37082
- return {
37083
- action: "published",
37084
- summary,
37085
- automation,
37086
- verification: isDeepEqual(expected, actual) ? { status: "matched" } : { status: "mismatched", mismatch: diffManifests(expected, actual) }
37087
- };
37088
- }
37089
- async function summarizeForPublish(draftApi, draftId) {
37090
- const [detail, connections] = await Promise.all([
37091
- draftApi.get(draftId),
37092
- draftApi.getConnectionStatus(draftId)
37093
- ]);
37094
- const { draft } = detail;
37095
- const target = toTarget(draft);
37096
- const published = target.kind === "automation" ? await draftApi.getAutomation(target.automationId) : null;
37097
- const diff = published ? diffManifests(
37098
- normaliseManifest(published.manifest),
37099
- normaliseManifest(draft.partialManifest)
37100
- ) : null;
37101
- const summary = {
37102
- draftId,
37103
- name: getResourceName(draft.partialManifest),
37104
- scope: draft.workspaceId === null ? { kind: "personal" } : {
37105
- kind: "workspace",
37106
- workspaceId: draft.workspaceId,
37107
- // A new workspace automation publishes team-visible; an
37108
- // edit keeps whatever the automation already has.
37109
- visibility: published?.visibility ?? "workspace"
37110
- },
37111
- target,
37112
- enabled: published?.enabled ?? true,
37113
- validation: { state: detail.state, issues: detail.issues },
37114
- connections: {
37115
- isValid: connections.isValid,
37116
- connected: connections.connected.map(
37117
- ({ connectorId, connectionType, connectionId }) => ({
37118
- connectorId,
37119
- connectionType,
37120
- connectionId
37121
- })
37122
- ),
37123
- missing: connections.missing
37124
- },
37125
- schedule: describeSchedule(draft.partialManifest),
37126
- diff
37127
- };
37128
- return { summary, manifest: draft.partialManifest };
37129
- }
37130
- function describeSchedule(manifest) {
37131
- if (!hasTriggerType(manifest)) return null;
37132
- const schedule = getManifestTriggers(manifest).find((trigger) => trigger.type === "schedule");
37133
- if (!schedule || schedule.type !== "schedule") return null;
37134
- return getScheduleTriggerDescription({ value: schedule.value, timeFormat: "24h" });
37135
- }
37136
- function normaliseManifest(manifest) {
37137
- const parsed = manifestSchema.safeParse(manifest);
37138
- return parsed.success ? parsed.data : manifest;
37139
37500
  }
37140
37501
 
37141
37502
  // src/commands/draft/format.ts
@@ -37246,6 +37607,72 @@ function describeDiff(diff) {
37246
37607
  if (diff.steps.reordered) parts.push("steps reordered");
37247
37608
  return parts.join("; ");
37248
37609
  }
37610
+ var MOCKED_CONNECTOR_CALLS_LINE = "Connector calls: mocked (simulated by a model; nothing was read from or written to your services)";
37611
+ function formatDraftTestRun(run) {
37612
+ const reason = getRunOutcomeReason(run);
37613
+ const lines = [
37614
+ `Test run: ${run.id}`,
37615
+ `Draft: ${run.draftId}`,
37616
+ `Automation: ${run.automationId ?? "none (the draft creates a new one)"}`,
37617
+ `Status: ${run.status}`,
37618
+ MOCKED_CONNECTOR_CALLS_LINE,
37619
+ `Created: ${run.createdAt}`,
37620
+ `Updated: ${run.updatedAt}`
37621
+ ];
37622
+ if (run.failedStep) {
37623
+ lines.push(
37624
+ `Failed step: ${run.failedStep.stepId} (${run.failedStep.description})`,
37625
+ `Error: ${run.failedStep.error ?? reason ?? "none reported"}`
37626
+ );
37627
+ } else if (reason) {
37628
+ lines.push(`${run.status === "skipped" ? "Skip" : "Failure"} reason: ${reason}`);
37629
+ }
37630
+ lines.push(
37631
+ `Result: ${run.result?.text ?? "none"}`,
37632
+ `Steps: ${run.steps.length}`,
37633
+ ...formatRunSteps(run.steps, { toolCallLabel: "Tool (mocked)" })
37634
+ );
37635
+ return lines.join("\n");
37636
+ }
37637
+ var INPUT_SOURCE_LABELS = {
37638
+ schedule: "as a scheduled run",
37639
+ "sample-event": "with the built-in sample event",
37640
+ supplied: "with the supplied payload",
37641
+ empty: "with an empty request body"
37642
+ };
37643
+ var OUTCOME_LABELS = {
37644
+ passed: "passed",
37645
+ failed: "failed",
37646
+ skipped: "was skipped",
37647
+ cancelled: "was cancelled",
37648
+ timed_out: "did not finish before the timeout"
37649
+ };
37650
+ function formatDraftTest(result) {
37651
+ const { trigger } = result;
37652
+ const which = trigger.type === "webhook" ? `webhook ${trigger.event}` : trigger.type;
37653
+ const lines = [
37654
+ result.action === "test_started" ? `Started test run ${result.testRunId} for draft ${result.draftId}.` : `Test run ${result.testRunId} for draft ${result.draftId} ${OUTCOME_LABELS[result.outcome]}.`,
37655
+ `Trigger: ${trigger.index} of ${trigger.count} (${which}), ${INPUT_SOURCE_LABELS[trigger.inputSource]}`
37656
+ ];
37657
+ if (result.action === "test_finished") {
37658
+ if (result.outcome === "timed_out") lines.push(`Status: ${result.status}`);
37659
+ if (result.failedStep) {
37660
+ lines.push(
37661
+ `Failed step: ${result.failedStep.stepId} (${result.failedStep.description})`
37662
+ );
37663
+ }
37664
+ if (result.error)
37665
+ lines.push(`${result.outcome === "skipped" ? "Reason" : "Error"}: ${result.error}`);
37666
+ if (result.result) lines.push(`Result: ${result.result.text}`);
37667
+ }
37668
+ lines.push(MOCKED_CONNECTOR_CALLS_LINE, `Details: ${result.command}`);
37669
+ if (trigger.count > 1) {
37670
+ lines.push(
37671
+ `This draft has ${trigger.count} triggers; test another with --trigger <index|event>.`
37672
+ );
37673
+ }
37674
+ return lines.join("\n");
37675
+ }
37249
37676
 
37250
37677
  // src/commands/draft/validate-command.ts
37251
37678
  import { CliError as CliError5 } from "@doist/cli-core";
@@ -37300,8 +37727,10 @@ function formatManifestValidation(result) {
37300
37727
  }
37301
37728
 
37302
37729
  // src/commands/draft/index.ts
37730
+ var DEFAULT_TEST_WAIT = "10m";
37731
+ var TOKEN_LIFETIME_MARGIN_MS = 2 * 60 * 1e3;
37303
37732
  function registerDraftCommand(program) {
37304
- const draft = program.command("draft").description("Author and inspect automation drafts");
37733
+ const draft = program.command("draft").description("Author, test and inspect automation drafts");
37305
37734
  registerDraftValidateCommand(draft);
37306
37735
  addWorkspaceScopeOptions(
37307
37736
  addCursorPaginationOptions(
@@ -37358,8 +37787,8 @@ function registerDraftCommand(program) {
37358
37787
  command,
37359
37788
  async ({ api, signal }) => {
37360
37789
  const manifest = await readManifestInput(options);
37361
- const draftApi = resolveDraftApi(api, await resolveWorkspaceScope(options), signal);
37362
- return createDraft(draftApi, { manifest, automationId: options.automation });
37790
+ const draftApi = bindDraftApi(api, await resolveWorkspaceScope(options), { signal });
37791
+ return createDraft2(draftApi, { manifest, automationId: options.automation });
37363
37792
  },
37364
37793
  { human: formatDraftChange },
37365
37794
  { spinner: "Creating draft..." }
@@ -37374,8 +37803,8 @@ function registerDraftCommand(program) {
37374
37803
  command,
37375
37804
  async ({ api, signal }) => {
37376
37805
  const manifest = await readManifestInput(options);
37377
- const draftApi = resolveDraftApi(api, await resolveWorkspaceScope(options), signal);
37378
- return updateDraft(draftApi, { draftId, manifest });
37806
+ const draftApi = bindDraftApi(api, await resolveWorkspaceScope(options), { signal });
37807
+ return updateDraft2(draftApi, { draftId, manifest });
37379
37808
  },
37380
37809
  { human: formatDraftChange },
37381
37810
  { spinner: "Updating draft..." }
@@ -37401,8 +37830,8 @@ function registerDraftCommand(program) {
37401
37830
  );
37402
37831
  }
37403
37832
  const selections = options.connection.map(parseConnectionSelectionOption);
37404
- const draftApi = resolveDraftApi(api, await resolveWorkspaceScope(options), signal);
37405
- return connectDraft(draftApi, api, {
37833
+ const draftApi = bindDraftApi(api, await resolveWorkspaceScope(options), { signal });
37834
+ return connectDraft2(draftApi, {
37406
37835
  draftId,
37407
37836
  selections,
37408
37837
  auto: options.auto === true
@@ -37418,13 +37847,70 @@ function registerDraftCommand(program) {
37418
37847
  await runApiCommand(
37419
37848
  command,
37420
37849
  async ({ api, signal }) => {
37421
- const draftApi = resolveDraftApi(api, await resolveWorkspaceScope(options), signal);
37422
- return publishDraft(draftApi, { draftId, confirmed: options.yes === true });
37850
+ const draftApi = bindDraftApi(api, await resolveWorkspaceScope(options), { signal });
37851
+ return publishDraft2(draftApi, { draftId, confirmed: options.yes === true });
37423
37852
  },
37424
37853
  { human: formatDraftPublish, json: summarizeDraftPublish },
37425
37854
  { spinner: options.yes === true ? "Publishing..." : "Loading draft..." }
37426
37855
  );
37427
37856
  });
37857
+ addWorkspaceScopeOptions(
37858
+ draft.command("test").description("Start a test run of a draft, with mocked connector calls").argument("<draft-id>", "Draft ID").option("--wait", "Wait for the run to end and exit 1 unless it passed").option(
37859
+ "--timeout <duration>",
37860
+ `How long --wait waits, such as 90s or 15m (default ${DEFAULT_TEST_WAIT})`
37861
+ ).option(
37862
+ "--trigger <index|event>",
37863
+ "The trigger to test: its position in the manifest's triggers, or a webhook event"
37864
+ ).option(
37865
+ "--payload-file <path>",
37866
+ "A JSON event to test with: a webhook trigger's event or an API trigger's body"
37867
+ )
37868
+ ).action(async (draftId, options, command) => {
37869
+ await runApiCommand(
37870
+ command,
37871
+ async ({ api, signal }) => {
37872
+ if (options.timeout !== void 0 && options.wait !== true) {
37873
+ throw new Error("--timeout only applies with --wait.");
37874
+ }
37875
+ const timeoutMs = parseTestTimeout(options.timeout ?? DEFAULT_TEST_WAIT);
37876
+ const draftApi = bindDraftApi(api, await resolveWorkspaceScope(options), { signal });
37877
+ return startDraftTest(draftApi, {
37878
+ draftId,
37879
+ request: {
37880
+ ...options.trigger !== void 0 ? { trigger: parseTriggerOption(options.trigger) } : {},
37881
+ ...options.payloadFile !== void 0 ? { payload: await readTestPayloadFile(options.payloadFile) } : {}
37882
+ },
37883
+ wait: options.wait === true ? { timeoutMs } : null,
37884
+ signal
37885
+ });
37886
+ },
37887
+ { human: formatDraftTest },
37888
+ {
37889
+ spinner: options.wait === true ? "Testing draft..." : "Starting test run...",
37890
+ minimumTokenLifetimeMs: options.wait === true ? tryParseTestTimeout(options.timeout ?? DEFAULT_TEST_WAIT) + TOKEN_LIFETIME_MARGIN_MS : void 0,
37891
+ exitCode: (result) => result.action === "test_finished" && result.outcome !== "passed" ? 1 : void 0
37892
+ }
37893
+ );
37894
+ });
37895
+ addWorkspaceScopeOptions(
37896
+ draft.command("test-run").description("Show a draft's test run with step and failure details").argument("<draft-id>", "Draft ID").argument("<run-id>", "Test run ID").option("--full", "Include raw step results and generated code in machine output")
37897
+ ).action(async (draftId, runId, options, command) => {
37898
+ await runApiCommand(
37899
+ command,
37900
+ async ({ api, signal }) => {
37901
+ const draftApi = bindDraftApi(api, await resolveWorkspaceScope(options), { signal });
37902
+ return draftApi.getTestRun(draftId, runId, {
37903
+ includeGeneratedCode: options.full === true,
37904
+ includeToolCallPreviews: true
37905
+ });
37906
+ },
37907
+ {
37908
+ human: formatDraftTestRun,
37909
+ json: options.full === true ? void 0 : summarizeDraftTestRun
37910
+ },
37911
+ { spinner: "Loading test run..." }
37912
+ );
37913
+ });
37428
37914
  addWorkspaceScopeOptions(
37429
37915
  draft.command("delete").description("Delete a draft after previewing which one it is").argument("<draft-id>", "Draft ID").option("--yes", "Confirm deletion")
37430
37916
  ).action(async (draftId, options, command) => {
@@ -37461,6 +37947,20 @@ function streamDraftList(options, fetchPage) {
37461
37947
  async (args) => summarizeDraftListPage(await fetchPage(args))
37462
37948
  );
37463
37949
  }
37950
+ function tryParseTestTimeout(value) {
37951
+ try {
37952
+ return durationStringToMilliseconds(value);
37953
+ } catch {
37954
+ return 0;
37955
+ }
37956
+ }
37957
+ function parseTestTimeout(value) {
37958
+ try {
37959
+ return durationStringToMilliseconds(value);
37960
+ } catch {
37961
+ throw new Error(`Invalid --timeout ${value}; use a duration such as 90s, 15m or 1h.`);
37962
+ }
37963
+ }
37464
37964
 
37465
37965
  // src/commands/run/index.ts
37466
37966
  import { Option as Option7 } from "commander";