@doist/automations-cli 1.21.0 → 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
@@ -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.21.0",
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",
@@ -32872,6 +32872,313 @@ async function findAutomationsUsingConnection(api, connectionId, options) {
32872
32872
  }
32873
32873
  }
32874
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
+
32875
33182
  // src/lib/auth-environment.ts
32876
33183
  import { InvalidArgumentError } from "commander";
32877
33184
  var ENVIRONMENTS = {
@@ -33095,7 +33402,7 @@ function createAutomationsTokenStore(options = {}) {
33095
33402
  }
33096
33403
 
33097
33404
  // src/lib/credentials.ts
33098
- 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";
33099
33406
  import { refreshAccessToken } from "@doist/cli-core/auth";
33100
33407
 
33101
33408
  // src/lib/auth-scopes.ts
@@ -33185,7 +33492,7 @@ function formatScopesHelp() {
33185
33492
  }
33186
33493
 
33187
33494
  // src/lib/auth-provider.ts
33188
- import { getErrorMessage } from "@doist/cli-core";
33495
+ import { getErrorMessage as getErrorMessage2 } from "@doist/cli-core";
33189
33496
  import { createPkceProvider } from "@doist/cli-core/auth";
33190
33497
  var ERROR_HINTS = ["Try again: tda auth login"];
33191
33498
  function createAutomationsAuthProvider() {
@@ -33225,7 +33532,7 @@ async function fetchTodoistUser(token, todoistApiUrl) {
33225
33532
  });
33226
33533
  } catch (error62) {
33227
33534
  throw new Error(
33228
- `Could not reach Todoist to identify the account: ${getErrorMessage(error62)}`
33535
+ `Could not reach Todoist to identify the account: ${getErrorMessage2(error62)}`
33229
33536
  );
33230
33537
  }
33231
33538
  if (!response.ok) {
@@ -33238,7 +33545,7 @@ async function fetchTodoistUser(token, todoistApiUrl) {
33238
33545
  user = await response.json();
33239
33546
  } catch (error62) {
33240
33547
  throw new Error(
33241
- `Todoist returned an unreadable identity response: ${getErrorMessage(error62)}`
33548
+ `Todoist returned an unreadable identity response: ${getErrorMessage2(error62)}`
33242
33549
  );
33243
33550
  }
33244
33551
  return { id: requireUserId(user.id), email: typeof user.email === "string" ? user.email : "" };
@@ -33250,13 +33557,13 @@ function requireUserId(id) {
33250
33557
  }
33251
33558
 
33252
33559
  // src/lib/errors.ts
33253
- import { getErrorMessage as getErrorMessage2 } from "@doist/cli-core";
33560
+ import { getErrorMessage as getErrorMessage3 } from "@doist/cli-core";
33254
33561
  function getErrorCode(error62) {
33255
33562
  return typeof error62 === "object" && error62 !== null && "code" in error62 ? String(error62.code) : void 0;
33256
33563
  }
33257
33564
  function formatError2(error62) {
33258
33565
  const hints = errorHints(error62);
33259
- return [getErrorMessage2(error62), ...hints.map((hint) => ` - ${hint}`)].join("\n");
33566
+ return [getErrorMessage3(error62), ...hints.map((hint) => ` - ${hint}`)].join("\n");
33260
33567
  }
33261
33568
  function errorHints(error62) {
33262
33569
  if (typeof error62 !== "object" || error62 === null || !("hints" in error62)) return [];
@@ -33336,7 +33643,7 @@ function accessTokenAfterFailedRefresh(error62, snapshot) {
33336
33643
  if (hasTokenLifeLeft(snapshot)) return snapshot.bundle.accessToken;
33337
33644
  throw new CredentialError(
33338
33645
  "REFRESH_FAILED",
33339
- `Could not refresh your access token: ${getErrorMessage3(error62)}`,
33646
+ `Could not refresh your access token: ${getErrorMessage4(error62)}`,
33340
33647
  ["Check your network connection and try again."]
33341
33648
  );
33342
33649
  }
@@ -33364,19 +33671,19 @@ function resolveAfterStoreFailure(error62) {
33364
33671
  if (code !== "AUTH_STORE_READ_FAILED") {
33365
33672
  throw new CredentialError(
33366
33673
  "CREDENTIAL_UNREADABLE",
33367
- `Could not read the stored credential: ${getErrorMessage3(error62)}`,
33674
+ `Could not read the stored credential: ${getErrorMessage4(error62)}`,
33368
33675
  ["Run `tda auth logout` and then `tda auth login` to replace it."]
33369
33676
  );
33370
33677
  }
33371
33678
  const fallback = fromEnvironment();
33372
33679
  if (fallback) {
33373
33680
  process.stderr.write(
33374
- `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}.
33375
33682
  `
33376
33683
  );
33377
33684
  return fallback;
33378
33685
  }
33379
- throw new CredentialError("CREDENTIAL_UNREADABLE", getErrorMessage3(error62), [
33686
+ throw new CredentialError("CREDENTIAL_UNREADABLE", getErrorMessage4(error62), [
33380
33687
  `Set ${TOKEN_ENV_VAR} to bypass the credential store.`,
33381
33688
  "Or re-run `tda auth login --credential-store plaintext`."
33382
33689
  ]);
@@ -34039,7 +34346,7 @@ function attachAutomationsLoginCommand(auth) {
34039
34346
  }
34040
34347
 
34041
34348
  // src/commands/auth/logout.ts
34042
- import { getErrorMessage as getErrorMessage4 } from "@doist/cli-core";
34349
+ import { getErrorMessage as getErrorMessage5 } from "@doist/cli-core";
34043
34350
  import { attachLogoutCommand } from "@doist/cli-core/auth";
34044
34351
 
34045
34352
  // src/lib/spinner.ts
@@ -34087,7 +34394,7 @@ async function revokeToken(token) {
34087
34394
  });
34088
34395
  return response.ok ? void 0 : `HTTP ${response.status}`;
34089
34396
  } catch (error62) {
34090
- return getErrorMessage4(error62);
34397
+ return getErrorMessage5(error62);
34091
34398
  }
34092
34399
  }
34093
34400
 
@@ -34218,7 +34525,7 @@ function parsePositiveInteger(value) {
34218
34525
  }
34219
34526
 
34220
34527
  // src/lib/command-error.ts
34221
- 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";
34222
34529
 
34223
34530
  // src/lib/billing-errors.ts
34224
34531
  import { CliError as CliError2 } from "@doist/cli-core";
@@ -34297,7 +34604,7 @@ function renderFailure(error62, context = {}) {
34297
34604
  const explained = explainBillingFailure(authExplained);
34298
34605
  const code = getErrorCode(explained);
34299
34606
  return {
34300
- message: getErrorMessage5(explained),
34607
+ message: getErrorMessage6(explained),
34301
34608
  humanMessage: formatError2(explained),
34302
34609
  ...code ? { code } : {}
34303
34610
  };
@@ -36955,7 +37262,7 @@ function registerConnectorCommand(program) {
36955
37262
  }
36956
37263
 
36957
37264
  // src/lib/manifest-input.ts
36958
- import { CliError as CliError3, getErrorMessage as getErrorMessage6 } from "@doist/cli-core";
37265
+ import { CliError as CliError3, getErrorMessage as getErrorMessage7 } from "@doist/cli-core";
36959
37266
  import { Option as Option6 } from "commander";
36960
37267
  import { readFile as readFile2 } from "node:fs/promises";
36961
37268
  import { text } from "node:stream/consumers";
@@ -36971,20 +37278,10 @@ async function readManifestInput(options, input2 = process.stdin) {
36971
37278
  } catch (error62) {
36972
37279
  throw new CliError3(
36973
37280
  "MANIFEST_UNREADABLE",
36974
- `${source.label} is not valid JSON: ${getErrorMessage6(error62)}`
37281
+ `${source.label} is not valid JSON: ${getErrorMessage7(error62)}`
36975
37282
  );
36976
37283
  }
36977
37284
  }
36978
- function parsePartialManifestInput(value) {
36979
- const validation = validatePartialManifest(value);
36980
- if (!validation.isValid) {
36981
- throw new CliError3(
36982
- "MANIFEST_INVALID",
36983
- `Manifest is invalid: ${validation.errors.join("; ")}`
36984
- );
36985
- }
36986
- return validation.data;
36987
- }
36988
37285
  async function readSource(options, input2) {
36989
37286
  if (options.file !== void 0) {
36990
37287
  try {
@@ -36992,7 +37289,7 @@ async function readSource(options, input2) {
36992
37289
  } catch (error62) {
36993
37290
  throw new CliError3(
36994
37291
  "MANIFEST_UNREADABLE",
36995
- `Could not read ${options.file}: ${getErrorMessage6(error62)}`
37292
+ `Could not read ${options.file}: ${getErrorMessage7(error62)}`
36996
37293
  );
36997
37294
  }
36998
37295
  }
@@ -37002,7 +37299,7 @@ async function readSource(options, input2) {
37002
37299
  } catch (error62) {
37003
37300
  throw new CliError3(
37004
37301
  "MANIFEST_UNREADABLE",
37005
- `Could not read standard input: ${getErrorMessage6(error62)}`
37302
+ `Could not read standard input: ${getErrorMessage7(error62)}`
37006
37303
  );
37007
37304
  }
37008
37305
  }
@@ -37010,134 +37307,56 @@ async function readSource(options, input2) {
37010
37307
  }
37011
37308
 
37012
37309
  // src/commands/draft/draft-service.ts
37013
- import { CliError as CliError4, getErrorMessage as getErrorMessage7 } from "@doist/cli-core";
37310
+ import { CliError as CliError4, getErrorMessage as getErrorMessage8 } from "@doist/cli-core";
37014
37311
  import { readFile as readFile3 } from "node:fs/promises";
37015
37312
  import { setTimeout as wait2 } from "node:timers/promises";
37016
-
37017
- // src/lib/manifest-diff.ts
37018
- function stableStringify(value) {
37019
- return JSON.stringify(sortKeys(value));
37313
+ function createDraft2(draftApi, input2) {
37314
+ return withRecoveryHints(draftApi, createDraft(draftApi, input2));
37020
37315
  }
37021
- function isDeepEqual(left, right) {
37022
- return stableStringify(left) === stableStringify(right);
37316
+ function updateDraft2(draftApi, input2) {
37317
+ return withRecoveryHints(draftApi, updateDraft(draftApi, input2));
37023
37318
  }
37024
- function diffManifests(before, after) {
37025
- const previous = asManifest(before);
37026
- const next = asManifest(after);
37027
- const changedProperties = [.../* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(next)])].filter((key) => key !== "steps").filter((key) => !isDeepEqual(previous[key], next[key])).sort();
37028
- const previousSteps = stepsById(previous.steps);
37029
- const nextSteps = stepsById(next.steps);
37030
- const added = [...nextSteps.keys()].filter((id) => !previousSteps.has(id));
37031
- const removed = [...previousSteps.keys()].filter((id) => !nextSteps.has(id));
37032
- const changed = [...nextSteps].filter(([id, step]) => previousSteps.has(id) && !isDeepEqual(previousSteps.get(id), step)).map(([id]) => id);
37033
- const sharedBefore = [...previousSteps.keys()].filter((id) => nextSteps.has(id));
37034
- const sharedAfter = [...nextSteps.keys()].filter((id) => previousSteps.has(id));
37035
- const reordered = sharedBefore.some((id, index) => sharedAfter[index] !== id);
37036
- return {
37037
- changedProperties,
37038
- steps: { added, removed, changed, reordered },
37039
- isEmpty: changedProperties.length === 0 && added.length === 0 && removed.length === 0 && changed.length === 0 && !reordered
37040
- };
37041
- }
37042
- function asManifest(value) {
37043
- return isRecord3(value) ? value : {};
37044
- }
37045
- function stepsById(steps) {
37046
- const byId = /* @__PURE__ */ new Map();
37047
- if (!Array.isArray(steps)) return byId;
37048
- steps.forEach((step, index) => {
37049
- const id = isRecord3(step) && typeof step.id === "string" ? step.id : `#${index}`;
37050
- byId.set(id, step);
37051
- });
37052
- return byId;
37053
- }
37054
- function sortKeys(value) {
37055
- if (Array.isArray(value)) return value.map(sortKeys);
37056
- if (!isRecord3(value)) return value;
37057
- return Object.fromEntries(
37058
- Object.keys(value).sort().map((key) => [key, sortKeys(value[key])])
37059
- );
37319
+ function connectDraft2(draftApi, input2) {
37320
+ return withRecoveryHints(draftApi, connectDraft(draftApi, input2));
37060
37321
  }
37061
- function isRecord3(value) {
37062
- return typeof value === "object" && value !== null && !Array.isArray(value);
37063
- }
37064
-
37065
- // src/commands/draft/draft-service.ts
37066
- function resolveDraftApi(api, workspaceId, signal) {
37067
- const createOptions = { signal, translate: false };
37068
- if (workspaceId === null) {
37069
- return {
37070
- scope: null,
37071
- get: (draftId) => api.automationDrafts.get(draftId, { signal }),
37072
- create: (manifest) => api.automationDrafts.create(manifest, createOptions),
37073
- update: (draftId, manifest) => api.automationDrafts.update(draftId, manifest, { signal }),
37074
- createAutomationDraft: (automationId) => api.automations.createDraft(automationId, { signal }),
37075
- upsertConnection: (draftId, body) => api.automationDraftConnections.upsert(draftId, body, { signal }),
37076
- getConnectionStatus: async (draftId) => (await api.automationDraftConnections.getStatus(draftId, { signal })).validation,
37077
- finalize: (draftId) => api.automationDrafts.finalize(draftId, { signal }),
37078
- getAutomation: (automationId) => api.automations.get(automationId, { signal }),
37079
- test: (draftId, request2) => api.automationDrafts.test(draftId, request2, { signal }),
37080
- getTestRun: (draftId, runId, args, requestSignal) => api.automationDrafts.getTestRun(draftId, runId, args, {
37081
- signal: requestSignal ?? signal
37082
- })
37083
- };
37084
- }
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 };
37085
37327
  return {
37086
- scope: workspaceId,
37087
- get: (draftId) => api.workspaceAutomationDrafts.get(workspaceId, draftId, { signal }),
37088
- create: (manifest) => api.workspaceAutomationDrafts.create(workspaceId, manifest, createOptions),
37089
- update: (draftId, manifest) => api.workspaceAutomationDrafts.update(workspaceId, draftId, manifest, { signal }),
37090
- createAutomationDraft: (automationId) => api.workspaceAutomations.createDraft(workspaceId, automationId, { signal }),
37091
- upsertConnection: (draftId, body) => api.workspaceAutomationDraftConnections.upsert(workspaceId, draftId, body, { signal }),
37092
- getConnectionStatus: async (draftId) => (await api.workspaceAutomationDraftConnections.getStatus(workspaceId, draftId, {
37093
- signal
37094
- })).validation,
37095
- finalize: (draftId) => api.workspaceAutomationDrafts.finalize(workspaceId, draftId, { signal }),
37096
- getAutomation: (automationId) => api.workspaceAutomations.get(workspaceId, automationId, { signal }),
37097
- test: (draftId, request2) => api.workspaceAutomationDrafts.test(workspaceId, draftId, request2, { signal }),
37098
- getTestRun: (draftId, runId, args, requestSignal) => api.workspaceAutomationDrafts.getTestRun(workspaceId, draftId, runId, args, {
37099
- signal: requestSignal ?? signal
37100
- })
37328
+ ...result,
37329
+ verification: {
37330
+ ...verification,
37331
+ command: `tda automation view ${result.automation.id} ${scopeFlag(draftApi.workspaceId)}`
37332
+ }
37101
37333
  };
37102
37334
  }
37103
- async function createDraft(draftApi, { manifest, automationId }) {
37104
- const partialManifest = parsePartialManifestInput(manifest);
37105
- if (automationId === void 0) {
37106
- const result = await draftApi.create(partialManifest);
37107
- return { action: "created", target: toTarget(result.draft), ...result };
37108
- }
37109
- const { draft } = await openEditDraft(draftApi, automationId);
37335
+ async function withRecoveryHints(draftApi, operation) {
37110
37336
  try {
37111
- const result = await draftApi.update(draft.id, partialManifest);
37112
- return { action: "created", target: toTarget(result.draft), ...result };
37337
+ return await operation;
37113
37338
  } catch (error62) {
37114
- if (!isManifestRejection(error62)) throw error62;
37115
- throw new CliError4(
37116
- "DRAFT_MANIFEST_REJECTED",
37117
- `Opened draft ${draft.id} for automation ${automationId}, but the manifest was rejected: ${getErrorMessage7(error62)}`,
37118
- { hints: draftHints(draftApi.scope, draft.id) }
37119
- );
37120
- }
37121
- }
37122
- function isManifestRejection(error62) {
37123
- return error62 instanceof AutomationsRequestError && (error62.httpStatusCode === 400 || error62.httpStatusCode === 422);
37124
- }
37125
- async function updateDraft(draftApi, { draftId, manifest }) {
37126
- const partialManifest = parsePartialManifestInput(manifest);
37127
- const result = await draftApi.update(draftId, partialManifest);
37128
- return { action: "updated", target: toTarget(result.draft), ...result };
37129
- }
37130
- async function openEditDraft(draftApi, automationId) {
37131
- try {
37132
- return await draftApi.createAutomationDraft(automationId);
37133
- } catch (error62) {
37134
- const conflict = getOpenDraftConflictError(error62);
37135
- if (!conflict) throw error62;
37136
- throw new CliError4(
37137
- "DRAFT_CONFLICT",
37138
- `Automation ${automationId} already has an open draft ${conflict.draftId}.`,
37139
- { hints: draftHints(draftApi.scope, conflict.draftId) }
37140
- );
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
+ }
37141
37360
  }
37142
37361
  }
37143
37362
  function draftHints(scope, draftId) {
@@ -37149,168 +37368,6 @@ function draftHints(scope, draftId) {
37149
37368
  function scopeFlag(scope) {
37150
37369
  return scope === null ? "--personal" : `--workspace ${scope}`;
37151
37370
  }
37152
- function toTarget(draft) {
37153
- return draft.automationId === null ? { kind: "new" } : { kind: "automation", automationId: draft.automationId };
37154
- }
37155
- async function connectDraft(draftApi, api, {
37156
- draftId,
37157
- selections,
37158
- auto
37159
- }) {
37160
- const bound = [];
37161
- const required2 = selections.length > 0 ? getRequiredConnectorsFromManifest(
37162
- (await draftApi.get(draftId)).draft.partialManifest
37163
- ) : /* @__PURE__ */ new Map();
37164
- for (const selection of selections) {
37165
- const types = required2.get(selection.connectorId);
37166
- if (!types || selection.connectionType && !types.has(selection.connectionType)) {
37167
- const slot = selection.connectionType ? `${selection.connectorId} (${selection.connectionType})` : selection.connectorId;
37168
- throw new Error(
37169
- `Connector ${slot} is not used by draft ${draftId}. Required: ${describeRequired(required2)}.`
37170
- );
37171
- }
37172
- const connectionTypes = selection.connectionType ? [selection.connectionType] : [...types];
37173
- for (const connectionType of connectionTypes) {
37174
- await draftApi.upsertConnection(draftId, {
37175
- connectorId: selection.connectorId,
37176
- connectionType,
37177
- connectionId: selection.connectionId
37178
- });
37179
- bound.push({ ...selection, connectionType, mode: "explicit" });
37180
- }
37181
- }
37182
- const unresolved = [];
37183
- if (auto) {
37184
- const status = await draftApi.getConnectionStatus(draftId);
37185
- for (const connector of status.connectors) {
37186
- for (const connectionType of ["mcp", "trigger"]) {
37187
- const slot = connector[connectionType];
37188
- if (!slot.required || slot.status !== "missing") continue;
37189
- const { connections } = await api.connections.list({
37190
- connectorId: connector.id,
37191
- capability: connectionType,
37192
- status: "authorized"
37193
- });
37194
- const [only] = connections;
37195
- if (connections.length === 1 && only) {
37196
- await draftApi.upsertConnection(draftId, {
37197
- connectorId: connector.id,
37198
- connectionType,
37199
- connectionId: only.id
37200
- });
37201
- bound.push({
37202
- connectorId: connector.id,
37203
- connectionType,
37204
- connectionId: only.id,
37205
- mode: "auto"
37206
- });
37207
- continue;
37208
- }
37209
- unresolved.push({
37210
- connectorId: connector.id,
37211
- connectionType,
37212
- reason: connections.length === 0 ? "no_connection" : "multiple_connections",
37213
- candidateConnectionIds: connections.map((connection) => connection.id)
37214
- });
37215
- }
37216
- }
37217
- }
37218
- const { isValid, missing } = await draftApi.getConnectionStatus(draftId);
37219
- return { action: "connected", draftId, bound, unresolved, isValid, missing };
37220
- }
37221
- function describeRequired(required2) {
37222
- if (required2.size === 0) return "none";
37223
- return [...required2].map(([connectorId, types]) => `${connectorId} (${[...types].join(", ")})`).join(", ");
37224
- }
37225
- async function publishDraft(draftApi, { draftId, confirmed }) {
37226
- const { summary, manifest } = await summarizeForPublish(draftApi, draftId);
37227
- if (!confirmed) {
37228
- return { action: "publish_preview", summary };
37229
- }
37230
- let published;
37231
- try {
37232
- published = await draftApi.finalize(draftId);
37233
- } catch (error62) {
37234
- const notReady = getDraftNotReadyError(error62);
37235
- if (!notReady) throw error62;
37236
- throw draftNotReadyError(notReady.message, draftId, draftApi.scope);
37237
- }
37238
- const automationId = published.automation.id;
37239
- let automation;
37240
- try {
37241
- automation = await draftApi.getAutomation(automationId);
37242
- } catch (error62) {
37243
- return {
37244
- action: "published",
37245
- summary,
37246
- automation: published.automation,
37247
- verification: {
37248
- status: "unavailable",
37249
- reason: getErrorMessage7(error62),
37250
- command: `tda automation view ${automationId} ${scopeFlag(draftApi.scope)}`
37251
- }
37252
- };
37253
- }
37254
- const expected = normaliseManifest(manifest);
37255
- const actual = normaliseManifest(automation.manifest);
37256
- return {
37257
- action: "published",
37258
- summary,
37259
- automation,
37260
- verification: isDeepEqual(expected, actual) ? { status: "matched" } : { status: "mismatched", mismatch: diffManifests(expected, actual) }
37261
- };
37262
- }
37263
- async function summarizeForPublish(draftApi, draftId) {
37264
- const [detail, connections] = await Promise.all([
37265
- draftApi.get(draftId),
37266
- draftApi.getConnectionStatus(draftId)
37267
- ]);
37268
- const { draft } = detail;
37269
- const target = toTarget(draft);
37270
- const published = target.kind === "automation" ? await draftApi.getAutomation(target.automationId) : null;
37271
- const diff = published ? diffManifests(
37272
- normaliseManifest(published.manifest),
37273
- normaliseManifest(draft.partialManifest)
37274
- ) : null;
37275
- const summary = {
37276
- draftId,
37277
- name: getResourceName(draft.partialManifest),
37278
- scope: draft.workspaceId === null ? { kind: "personal" } : {
37279
- kind: "workspace",
37280
- workspaceId: draft.workspaceId,
37281
- // A new workspace automation publishes team-visible; an
37282
- // edit keeps whatever the automation already has.
37283
- visibility: published?.visibility ?? "workspace"
37284
- },
37285
- target,
37286
- enabled: published?.enabled ?? true,
37287
- validation: { state: detail.state, issues: detail.issues },
37288
- connections: {
37289
- isValid: connections.isValid,
37290
- connected: connections.connected.map(
37291
- ({ connectorId, connectionType, connectionId }) => ({
37292
- connectorId,
37293
- connectionType,
37294
- connectionId
37295
- })
37296
- ),
37297
- missing: connections.missing
37298
- },
37299
- schedule: describeSchedule(draft.partialManifest),
37300
- diff
37301
- };
37302
- return { summary, manifest: draft.partialManifest };
37303
- }
37304
- function describeSchedule(manifest) {
37305
- if (!hasTriggerType(manifest)) return null;
37306
- const schedule = getManifestTriggers(manifest).find((trigger) => trigger.type === "schedule");
37307
- if (!schedule || schedule.type !== "schedule") return null;
37308
- return getScheduleTriggerDescription({ value: schedule.value, timeFormat: "24h" });
37309
- }
37310
- function normaliseManifest(manifest) {
37311
- const parsed = manifestSchema.safeParse(manifest);
37312
- return parsed.success ? parsed.data : manifest;
37313
- }
37314
37371
  var DEFAULT_POLL_INTERVALS_MS = { first: 2e3, max: 1e4 };
37315
37372
  var STATUS_REQUEST_GRACE_MS = 1e4;
37316
37373
  var OPEN_RUN_STATUSES = /* @__PURE__ */ new Set([
@@ -37329,14 +37386,14 @@ async function startDraftTest(draftApi, {
37329
37386
  } catch (error62) {
37330
37387
  const refusal = getDraftTestRequestError(error62);
37331
37388
  if (!refusal) throw error62;
37332
- throw toDraftTestCliError(refusal, draftId, draftApi.scope);
37389
+ throw toDraftTestCliError(refusal, draftId, draftApi.workspaceId);
37333
37390
  }
37334
37391
  const base = {
37335
37392
  draftId,
37336
37393
  testRunId: started.testRunId,
37337
37394
  trigger: started.trigger,
37338
37395
  connectorCalls: started.connectorCalls,
37339
- command: `tda draft test-run ${draftId} ${started.testRunId} ${scopeFlag(draftApi.scope)}`
37396
+ command: `tda draft test-run ${draftId} ${started.testRunId} ${scopeFlag(draftApi.workspaceId)}`
37340
37397
  };
37341
37398
  if (!waitFor) {
37342
37399
  return { action: "test_started", ...base, status: started.status };
@@ -37429,7 +37486,7 @@ async function readTestPayloadFile(path) {
37429
37486
  } catch (error62) {
37430
37487
  throw new CliError4(
37431
37488
  "PAYLOAD_UNREADABLE",
37432
- `Could not read ${path}: ${getErrorMessage7(error62)}`
37489
+ `Could not read ${path}: ${getErrorMessage8(error62)}`
37433
37490
  );
37434
37491
  }
37435
37492
  try {
@@ -37437,7 +37494,7 @@ async function readTestPayloadFile(path) {
37437
37494
  } catch (error62) {
37438
37495
  throw new CliError4(
37439
37496
  "PAYLOAD_UNREADABLE",
37440
- `${path} is not valid JSON: ${getErrorMessage7(error62)}`
37497
+ `${path} is not valid JSON: ${getErrorMessage8(error62)}`
37441
37498
  );
37442
37499
  }
37443
37500
  }
@@ -37730,8 +37787,8 @@ function registerDraftCommand(program) {
37730
37787
  command,
37731
37788
  async ({ api, signal }) => {
37732
37789
  const manifest = await readManifestInput(options);
37733
- const draftApi = resolveDraftApi(api, await resolveWorkspaceScope(options), signal);
37734
- return createDraft(draftApi, { manifest, automationId: options.automation });
37790
+ const draftApi = bindDraftApi(api, await resolveWorkspaceScope(options), { signal });
37791
+ return createDraft2(draftApi, { manifest, automationId: options.automation });
37735
37792
  },
37736
37793
  { human: formatDraftChange },
37737
37794
  { spinner: "Creating draft..." }
@@ -37746,8 +37803,8 @@ function registerDraftCommand(program) {
37746
37803
  command,
37747
37804
  async ({ api, signal }) => {
37748
37805
  const manifest = await readManifestInput(options);
37749
- const draftApi = resolveDraftApi(api, await resolveWorkspaceScope(options), signal);
37750
- return updateDraft(draftApi, { draftId, manifest });
37806
+ const draftApi = bindDraftApi(api, await resolveWorkspaceScope(options), { signal });
37807
+ return updateDraft2(draftApi, { draftId, manifest });
37751
37808
  },
37752
37809
  { human: formatDraftChange },
37753
37810
  { spinner: "Updating draft..." }
@@ -37773,8 +37830,8 @@ function registerDraftCommand(program) {
37773
37830
  );
37774
37831
  }
37775
37832
  const selections = options.connection.map(parseConnectionSelectionOption);
37776
- const draftApi = resolveDraftApi(api, await resolveWorkspaceScope(options), signal);
37777
- return connectDraft(draftApi, api, {
37833
+ const draftApi = bindDraftApi(api, await resolveWorkspaceScope(options), { signal });
37834
+ return connectDraft2(draftApi, {
37778
37835
  draftId,
37779
37836
  selections,
37780
37837
  auto: options.auto === true
@@ -37790,8 +37847,8 @@ function registerDraftCommand(program) {
37790
37847
  await runApiCommand(
37791
37848
  command,
37792
37849
  async ({ api, signal }) => {
37793
- const draftApi = resolveDraftApi(api, await resolveWorkspaceScope(options), signal);
37794
- 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 });
37795
37852
  },
37796
37853
  { human: formatDraftPublish, json: summarizeDraftPublish },
37797
37854
  { spinner: options.yes === true ? "Publishing..." : "Loading draft..." }
@@ -37816,7 +37873,7 @@ function registerDraftCommand(program) {
37816
37873
  throw new Error("--timeout only applies with --wait.");
37817
37874
  }
37818
37875
  const timeoutMs = parseTestTimeout(options.timeout ?? DEFAULT_TEST_WAIT);
37819
- const draftApi = resolveDraftApi(api, await resolveWorkspaceScope(options), signal);
37876
+ const draftApi = bindDraftApi(api, await resolveWorkspaceScope(options), { signal });
37820
37877
  return startDraftTest(draftApi, {
37821
37878
  draftId,
37822
37879
  request: {
@@ -37841,7 +37898,7 @@ function registerDraftCommand(program) {
37841
37898
  await runApiCommand(
37842
37899
  command,
37843
37900
  async ({ api, signal }) => {
37844
- const draftApi = resolveDraftApi(api, await resolveWorkspaceScope(options), signal);
37901
+ const draftApi = bindDraftApi(api, await resolveWorkspaceScope(options), { signal });
37845
37902
  return draftApi.getTestRun(draftId, runId, {
37846
37903
  includeGeneratedCode: options.full === true,
37847
37904
  includeToolCallPreviews: true