@topolo/cli 0.12.11 → 0.12.13

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
@@ -9,7 +9,7 @@ import { platform as platform2 } from "os";
9
9
  import { createInterface } from "readline/promises";
10
10
  import { stdin as input } from "process";
11
11
 
12
- // node_modules/.pnpm/@topolo+sdk@0.21.4/node_modules/@topolo/sdk/dist/index.js
12
+ // node_modules/.pnpm/@topolo+sdk@0.23.1/node_modules/@topolo/sdk/dist/index.js
13
13
  function applyAuthHeaders(headers, credential) {
14
14
  if (credential.kind === "api_key") {
15
15
  headers.set("X-Api-Key", credential.apiKey);
@@ -83,6 +83,9 @@ var PUBLIC_ERROR_CODES = /* @__PURE__ */ new Set([
83
83
  "unknown_service"
84
84
  ]);
85
85
  var ERROR_CODE_PATTERN = /^[a-z][a-z0-9_]{0,63}$/;
86
+ var SERVICE_ERROR_CODE_PATTERN = /^[a-z][a-z0-9]*(?:_[a-z][a-z0-9]*){1,9}$/;
87
+ var SERVICE_MESSAGE_MAX_LENGTH = 300;
88
+ var SERVICE_ISSUE_LIMIT = 32;
86
89
  var TOPOLO_REQUEST_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
87
90
  var CLOUDFLARE_RAY_ID_PATTERN = /^[0-9a-f]{16}-[A-Z]{3}$/;
88
91
  var PERMISSION_PATTERN = /^[a-z][a-z0-9_-]{0,63}(?:\.[a-z][a-z0-9_-]{0,63})*:(?:[a-z][a-z0-9_-]{0,63}|\*)$/;
@@ -107,6 +110,24 @@ function canonicalTopoloErrorCode(value) {
107
110
  if (!ERROR_CODE_PATTERN.test(candidate) || !PUBLIC_ERROR_CODES.has(candidate)) return null;
108
111
  return candidate;
109
112
  }
113
+ function canonicalTopoloServiceErrorCode(value) {
114
+ const platform4 = canonicalTopoloErrorCode(value);
115
+ if (platform4) return platform4;
116
+ if (typeof value !== "string") return null;
117
+ const candidate = value.trim();
118
+ if (candidate.length > 64 || !SERVICE_ERROR_CODE_PATTERN.test(candidate)) return null;
119
+ const wordLike = candidate.split("_").every((segment) => {
120
+ const digits = segment.replace(/[a-z]/g, "").length;
121
+ return digits === 0 || digits < segment.length - digits && segment.length <= 8;
122
+ });
123
+ return wordLike ? candidate : null;
124
+ }
125
+ function sanitizeTopoloServiceMessage(value) {
126
+ if (typeof value !== "string") return void 0;
127
+ const text3 = value.replace(/[\u0000-\u001f\u007f-\u009f\u200b-\u200f\u2028-\u202e\u2066-\u2069\ufeff]/g, " ").replace(/\b[a-z][a-z0-9+.-]*:\/\/\S+/gi, "[url]").replace(/[^\s@<>()[\],;:"']+@[^\s@<>()[\],;:"']+\.[a-z]{2,}/gi, "[email]").replace(/[A-Za-z0-9_\-+/=.]{20,}/g, (run) => /[0-9]/.test(run) && /[A-Za-z]/.test(run) ? "[redacted]" : run).replace(/\s+/g, " ").trim();
128
+ if (!text3) return void 0;
129
+ return text3.length > SERVICE_MESSAGE_MAX_LENGTH ? `${text3.slice(0, SERVICE_MESSAGE_MAX_LENGTH - 1).trimEnd()}\u2026` : text3;
130
+ }
110
131
  function canonicalTopoloRequestId(value) {
111
132
  if (typeof value !== "string") return void 0;
112
133
  const candidate = value.trim();
@@ -131,7 +152,7 @@ var TopoloAuthError = class extends TopoloSdkError {
131
152
  retryAfter;
132
153
  details;
133
154
  constructor(message2, code = "auth_error", options = {}) {
134
- super(canonicalTopoloErrorCode(code) ?? "auth_error", message2);
155
+ super(canonicalTopoloServiceErrorCode(code) ?? "auth_error", message2);
135
156
  this.name = "TopoloAuthError";
136
157
  this.status = options.status ?? 401;
137
158
  this.body = options.body;
@@ -155,7 +176,7 @@ var TopoloPermissionError = class extends TopoloSdkError {
155
176
  details;
156
177
  constructor(message2, required, options = {}) {
157
178
  super(
158
- canonicalTopoloErrorCode(options.code) ?? serviceErrorCode(options.body, "permission_denied"),
179
+ canonicalTopoloServiceErrorCode(options.code) ?? serviceErrorCode(options.body, "permission_denied"),
159
180
  message2
160
181
  );
161
182
  this.name = "TopoloPermissionError";
@@ -173,36 +194,49 @@ var TopoloPermissionError = class extends TopoloSdkError {
173
194
  };
174
195
  function extractTopoloServiceErrorCode(body) {
175
196
  if (!body || typeof body !== "object" || Array.isArray(body)) return null;
176
- const record2 = body;
177
- const topLevel = canonicalTopoloErrorCode(record2.code);
197
+ const record3 = body;
198
+ const topLevel = canonicalTopoloServiceErrorCode(record3.code);
178
199
  if (topLevel) return topLevel;
179
- const nested = record2.error;
180
- const nestedString = canonicalTopoloErrorCode(nested);
200
+ const nested = record3.error;
201
+ const nestedString = canonicalTopoloServiceErrorCode(nested);
181
202
  if (nestedString) return nestedString;
182
203
  if (nested && typeof nested === "object" && !Array.isArray(nested)) {
183
204
  const code = nested.code;
184
- const nestedCode = canonicalTopoloErrorCode(code);
205
+ const nestedCode = canonicalTopoloServiceErrorCode(code);
185
206
  if (nestedCode) return nestedCode;
186
207
  }
187
208
  return null;
188
209
  }
210
+ function extractTopoloServiceErrorMessage(body) {
211
+ if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
212
+ const record3 = body;
213
+ const nested = record3.error;
214
+ const candidates = [];
215
+ if (nested && typeof nested === "object" && !Array.isArray(nested)) {
216
+ candidates.push(nested.message);
217
+ }
218
+ candidates.push(record3.message, record3.error_description);
219
+ if (typeof nested === "string" && !canonicalTopoloServiceErrorCode(nested)) candidates.push(nested);
220
+ for (const candidate of candidates) {
221
+ const message2 = sanitizeTopoloServiceMessage(candidate);
222
+ if (message2) return message2;
223
+ }
224
+ return void 0;
225
+ }
189
226
  function serviceErrorCode(body, fallback) {
190
227
  return extractTopoloServiceErrorCode(body) ?? fallback;
191
228
  }
192
229
  function extractTopoloServiceErrorDetails(body) {
193
230
  if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
194
- const record2 = body;
195
- let candidate = record2.details;
196
- const nested = record2.error;
231
+ const record3 = body;
232
+ let candidate = record3.details;
233
+ const nested = record3.error;
197
234
  if (nested && typeof nested === "object" && !Array.isArray(nested)) {
198
235
  const nestedRecord = nested;
199
236
  if (candidate === void 0) candidate = nestedRecord.details ?? nestedRecord;
200
237
  }
201
- if (candidate === void 0) candidate = record2;
202
- if (Array.isArray(candidate)) {
203
- const paths = canonicalTopoloValidationPaths(candidate);
204
- return paths.length ? { paths } : void 0;
205
- }
238
+ if (candidate === void 0) candidate = record3;
239
+ if (Array.isArray(candidate)) return validationIssueDetails(canonicalTopoloValidationIssues(candidate)) ?? void 0;
206
240
  if (!candidate || typeof candidate !== "object") return void 0;
207
241
  const source = candidate;
208
242
  const safe = {};
@@ -244,18 +278,59 @@ function extractTopoloServiceErrorDetails(body) {
244
278
  if (paths.length) safe[key] = paths;
245
279
  }
246
280
  }
281
+ const issues = [
282
+ ...Array.isArray(source.issues) ? canonicalTopoloValidationIssues(source.issues) : [],
283
+ ...flattenedValidationIssues(source.formErrors, source.fieldErrors)
284
+ ].slice(0, SERVICE_ISSUE_LIMIT);
285
+ Object.assign(safe, validationIssueDetails(issues));
247
286
  return Object.keys(safe).length ? safe : void 0;
248
287
  }
249
- function canonicalTopoloValidationPaths(issues) {
288
+ function validationIssueDetails(issues) {
289
+ if (!issues.length) return null;
250
290
  const paths = [];
251
- for (const issue of issues) {
252
- if (!issue || typeof issue !== "object" || Array.isArray(issue)) continue;
253
- const record2 = issue;
254
- const path = canonicalTopoloValidationPath(record2.field) ?? canonicalTopoloValidationPath(record2.path);
255
- if (path && !paths.includes(path)) paths.push(path);
256
- if (paths.length === 32) break;
291
+ for (const issue22 of issues) {
292
+ if (issue22.path && !paths.includes(issue22.path)) paths.push(issue22.path);
293
+ }
294
+ return paths.length ? { paths, issues } : { issues };
295
+ }
296
+ function issue(path, message2) {
297
+ const text3 = sanitizeTopoloServiceMessage(message2);
298
+ if (!path && !text3) return null;
299
+ return { ...path ? { path } : {}, ...text3 ? { message: text3 } : {} };
300
+ }
301
+ function canonicalTopoloValidationIssues(values) {
302
+ const issues = [];
303
+ for (const value of values) {
304
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
305
+ const record3 = value;
306
+ const path = canonicalTopoloValidationPath(record3.field) ?? canonicalTopoloValidationPath(record3.path);
307
+ if (!path) continue;
308
+ const projected = issue(path, record3.message);
309
+ if (projected) issues.push(projected);
310
+ if (issues.length === SERVICE_ISSUE_LIMIT) break;
311
+ }
312
+ return issues;
313
+ }
314
+ function flattenedValidationIssues(formErrors, fieldErrors) {
315
+ const issues = [];
316
+ if (Array.isArray(formErrors)) {
317
+ for (const message2 of formErrors) {
318
+ const projected = issue(void 0, message2);
319
+ if (projected) issues.push(projected);
320
+ }
321
+ }
322
+ if (fieldErrors && typeof fieldErrors === "object" && !Array.isArray(fieldErrors)) {
323
+ for (const [key, messages] of Object.entries(fieldErrors)) {
324
+ const path = canonicalTopoloValidationPath(key);
325
+ if (!path) continue;
326
+ const list = Array.isArray(messages) && messages.length ? messages : [void 0];
327
+ for (const message2 of list) {
328
+ const projected = issue(path, message2);
329
+ if (projected) issues.push(projected);
330
+ }
331
+ }
257
332
  }
258
- return paths;
333
+ return issues;
259
334
  }
260
335
  function canonicalTopoloValidationPath(value) {
261
336
  if (typeof value === "string") {
@@ -277,7 +352,9 @@ function canonicalTopoloValidationPath(value) {
277
352
  return PUBLIC_FIELD_PATH_PATTERN.test(path) ? path : void 0;
278
353
  }
279
354
  function publicTopoloErrorMessage(error) {
280
- const code = canonicalTopoloErrorCode(
355
+ const serviceMessage = typeof error === "string" ? void 0 : extractTopoloServiceErrorMessage(error.body);
356
+ if (serviceMessage) return serviceMessage;
357
+ const code = canonicalTopoloServiceErrorCode(
281
358
  typeof error === "string" ? error : error.code ?? extractTopoloServiceErrorCode(error.body)
282
359
  ) ?? "request_failed";
283
360
  if (/(?:organization|org).*(?:enroll|membership|member|admission)/.test(code)) {
@@ -294,7 +371,7 @@ function publicTopoloErrorMessage(error) {
294
371
  return `Topolo request failed (${code}).`;
295
372
  }
296
373
  function topoloErrorHint(error) {
297
- const code = canonicalTopoloErrorCode(
374
+ const code = canonicalTopoloServiceErrorCode(
298
375
  typeof error === "string" ? error : error.code ?? extractTopoloServiceErrorCode(error.body)
299
376
  );
300
377
  if (!code) return void 0;
@@ -360,8 +437,8 @@ function sensitive(key, value) {
360
437
  const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
361
438
  if (normalized === "capability") {
362
439
  if (typeof value !== "string") return value !== null && value !== void 0 && typeof value !== "boolean" && typeof value !== "number";
363
- const text2 = value.trim();
364
- return Boolean(text2) && !/^[a-z][a-z0-9_-]{0,31}$/.test(text2);
440
+ const text3 = value.trim();
441
+ return Boolean(text3) && !/^[a-z][a-z0-9_-]{0,31}$/.test(text3);
365
442
  }
366
443
  return ["authorization", "cookie", "setcookie", "proxyauthorization"].includes(normalized) || /(token|secret|password|privatekey|apikey)$/.test(normalized);
367
444
  }
@@ -645,7 +722,7 @@ function inspectSchema(schema, path, root, depth) {
645
722
  if (unsupportedKeyword) {
646
723
  return { path, code: "unsupported_schema_keyword", message: `Action schema uses unsupported keyword ${unsupportedKeyword}.` };
647
724
  }
648
- if (typeof schema.format === "string" && !["uri", "email", "date-time"].includes(schema.format)) {
725
+ if (typeof schema.format === "string" && !["uri", "email", "date-time", "uuid"].includes(schema.format)) {
649
726
  return { path, code: "unsupported_schema_format", message: `Action schema uses unsupported format ${schema.format}.` };
650
727
  }
651
728
  if (typeof schema.$ref === "string" && !resolveLocalReference(root, schema.$ref)) {
@@ -665,8 +742,8 @@ function inspectSchema(schema, path, root, depth) {
665
742
  );
666
743
  for (const [key, child] of children) {
667
744
  if (!isRecord2(child)) continue;
668
- const issue = inspectSchema(child, `${path}.${key}`, root, depth + 1);
669
- if (issue) return issue;
745
+ const issue22 = inspectSchema(child, `${path}.${key}`, root, depth + 1);
746
+ if (issue22) return issue22;
670
747
  }
671
748
  return null;
672
749
  }
@@ -782,6 +859,7 @@ function validateString(schema, value, path, errors) {
782
859
  if (schema.format === "uri" && !isUrl(value)) errors.push({ path, code: "format_uri", message: "Expected an absolute URL." });
783
860
  if (schema.format === "email" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) errors.push({ path, code: "format_email", message: "Expected an email address." });
784
861
  if (schema.format === "date-time" && !isDateTime(value)) errors.push({ path, code: "format_date_time", message: "Expected an ISO-8601 date-time." });
862
+ if (schema.format === "uuid" && (value.length !== 36 || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value))) errors.push({ path, code: "format_uuid", message: "Expected a hyphenated UUID." });
785
863
  }
786
864
  function validateNumber(schema, value, path, errors) {
787
865
  if (typeof schema.minimum === "number" && value < schema.minimum) errors.push({ path, code: "minimum", message: `Expected a value of at least ${schema.minimum}.` });
@@ -1839,14 +1917,14 @@ var TopoloClient = class _TopoloClient {
1839
1917
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
1840
1918
  throw new TopoloSdkError("action_catalog_summary_unavailable", "Action catalog summary is unavailable.");
1841
1919
  }
1842
- const record2 = raw;
1843
- if (record2.algorithm !== "sha256-v1" || typeof record2.digest !== "string" || !/^[a-f0-9]{64}$/.test(record2.digest) || !Number.isSafeInteger(record2.actionCount) || Number(record2.actionCount) < 0) {
1920
+ const record3 = raw;
1921
+ if (record3.algorithm !== "sha256-v1" || typeof record3.digest !== "string" || !/^[a-f0-9]{64}$/.test(record3.digest) || !Number.isSafeInteger(record3.actionCount) || Number(record3.actionCount) < 0) {
1844
1922
  throw new TopoloSdkError("action_catalog_summary_unavailable", "Action catalog summary is invalid.");
1845
1923
  }
1846
1924
  return {
1847
1925
  algorithm: "sha256-v1",
1848
- digest: record2.digest,
1849
- actionCount: Number(record2.actionCount)
1926
+ digest: record3.digest,
1927
+ actionCount: Number(record3.actionCount)
1850
1928
  };
1851
1929
  }
1852
1930
  /** @deprecated Prefer searchActions() so callers retain the continuation cursor. */
@@ -1982,7 +2060,7 @@ var TopoloClient = class _TopoloClient {
1982
2060
  input: selectedInput,
1983
2061
  inputSource: input2 ? "provided" : example ? "example" : "empty",
1984
2062
  missingFields: [...new Set(
1985
- plan.validation.errors.filter((issue) => issue.code === "required").map((issue) => issue.path)
2063
+ plan.validation.errors.filter((issue22) => issue22.code === "required").map((issue22) => issue22.path)
1986
2064
  )],
1987
2065
  plan
1988
2066
  };
@@ -2047,7 +2125,7 @@ var TopoloClient = class _TopoloClient {
2047
2125
  if (!validation.valid) {
2048
2126
  throw new TopoloSdkError(
2049
2127
  "invalid_action_input",
2050
- `Topolo action "${entry.actionId}" input is invalid: ${validation.errors.map((issue) => `${issue.path} ${issue.message}`).join("; ")}`
2128
+ `Topolo action "${entry.actionId}" input is invalid: ${validation.errors.map((issue22) => `${issue22.path} ${issue22.message}`).join("; ")}`
2051
2129
  );
2052
2130
  }
2053
2131
  const resourceIssue = validateActionResource(entry, options.resource);
@@ -2564,10 +2642,10 @@ function extractRequired(body) {
2564
2642
  if (Array.isArray(maybe.required)) return canonicalTopoloRequired(maybe.required);
2565
2643
  for (const nested of [maybe.details, maybe.error]) {
2566
2644
  if (!nested || typeof nested !== "object" || Array.isArray(nested)) continue;
2567
- const record2 = nested;
2568
- if (Array.isArray(record2.required)) return canonicalTopoloRequired(record2.required);
2569
- if (record2.details && typeof record2.details === "object" && !Array.isArray(record2.details)) {
2570
- const required = record2.details.required;
2645
+ const record3 = nested;
2646
+ if (Array.isArray(record3.required)) return canonicalTopoloRequired(record3.required);
2647
+ if (record3.details && typeof record3.details === "object" && !Array.isArray(record3.details)) {
2648
+ const required = record3.details.required;
2571
2649
  if (Array.isArray(required)) return canonicalTopoloRequired(required);
2572
2650
  }
2573
2651
  }
@@ -2642,8 +2720,8 @@ function compactResultValue(value, depth, budget) {
2642
2720
  return value.length > ACTION_RESULT_MAX_ITEMS ? { count: value.length, items, omittedItems: value.length - ACTION_RESULT_MAX_ITEMS } : items;
2643
2721
  }
2644
2722
  if (typeof value === "object") {
2645
- const record2 = value;
2646
- const allKeys = Object.keys(record2);
2723
+ const record3 = value;
2724
+ const allKeys = Object.keys(record3);
2647
2725
  const ranked = allKeys.sort((left, right) => resultKeyPriority(left) - resultKeyPriority(right));
2648
2726
  const prioritized = ranked.filter((key) => resultKeyPriority(key) < 2);
2649
2727
  const ordinary = ranked.filter((key) => resultKeyPriority(key) === 2);
@@ -2652,7 +2730,7 @@ function compactResultValue(value, depth, budget) {
2652
2730
  ...prioritized,
2653
2731
  ...ordinary.slice(0, ordinaryLimit)
2654
2732
  ].slice(0, ACTION_RESULT_MAX_KEYS);
2655
- const compact = Object.fromEntries(keys.map((key) => [key, compactResultValue(record2[key], depth + 1, budget)]));
2733
+ const compact = Object.fromEntries(keys.map((key) => [key, compactResultValue(record3[key], depth + 1, budget)]));
2656
2734
  if (allKeys.length > keys.length) compact["_omittedKeys"] = allKeys.length - keys.length;
2657
2735
  return compact;
2658
2736
  }
@@ -3416,7 +3494,7 @@ function createTopolo(options) {
3416
3494
  };
3417
3495
  }
3418
3496
 
3419
- // node_modules/.pnpm/@topolo+sdk@0.21.4/node_modules/@topolo/sdk/dist/node/credential-store.js
3497
+ // node_modules/.pnpm/@topolo+sdk@0.23.1/node_modules/@topolo/sdk/dist/node/credential-store.js
3420
3498
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, chmod as chmod2, rm as rm2, stat } from "fs/promises";
3421
3499
  import { existsSync as existsSync2 } from "fs";
3422
3500
  import { homedir as homedir2, platform } from "os";
@@ -3483,6 +3561,9 @@ var PUBLIC_ERROR_CODES2 = /* @__PURE__ */ new Set([
3483
3561
  "unknown_service"
3484
3562
  ]);
3485
3563
  var ERROR_CODE_PATTERN2 = /^[a-z][a-z0-9_]{0,63}$/;
3564
+ var SERVICE_ERROR_CODE_PATTERN2 = /^[a-z][a-z0-9]*(?:_[a-z][a-z0-9]*){1,9}$/;
3565
+ var SERVICE_MESSAGE_MAX_LENGTH2 = 300;
3566
+ var SERVICE_ISSUE_LIMIT2 = 32;
3486
3567
  var TOPOLO_REQUEST_ID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
3487
3568
  var CLOUDFLARE_RAY_ID_PATTERN2 = /^[0-9a-f]{16}-[A-Z]{3}$/;
3488
3569
  var PERMISSION_PATTERN2 = /^[a-z][a-z0-9_-]{0,63}(?:\.[a-z][a-z0-9_-]{0,63})*:(?:[a-z][a-z0-9_-]{0,63}|\*)$/;
@@ -3507,6 +3588,24 @@ function canonicalTopoloErrorCode2(value) {
3507
3588
  if (!ERROR_CODE_PATTERN2.test(candidate) || !PUBLIC_ERROR_CODES2.has(candidate)) return null;
3508
3589
  return candidate;
3509
3590
  }
3591
+ function canonicalTopoloServiceErrorCode2(value) {
3592
+ const platform22 = canonicalTopoloErrorCode2(value);
3593
+ if (platform22) return platform22;
3594
+ if (typeof value !== "string") return null;
3595
+ const candidate = value.trim();
3596
+ if (candidate.length > 64 || !SERVICE_ERROR_CODE_PATTERN2.test(candidate)) return null;
3597
+ const wordLike = candidate.split("_").every((segment) => {
3598
+ const digits = segment.replace(/[a-z]/g, "").length;
3599
+ return digits === 0 || digits < segment.length - digits && segment.length <= 8;
3600
+ });
3601
+ return wordLike ? candidate : null;
3602
+ }
3603
+ function sanitizeTopoloServiceMessage2(value) {
3604
+ if (typeof value !== "string") return void 0;
3605
+ const text2 = value.replace(/[\u0000-\u001f\u007f-\u009f\u200b-\u200f\u2028-\u202e\u2066-\u2069\ufeff]/g, " ").replace(/\b[a-z][a-z0-9+.-]*:\/\/\S+/gi, "[url]").replace(/[^\s@<>()[\],;:"']+@[^\s@<>()[\],;:"']+\.[a-z]{2,}/gi, "[email]").replace(/[A-Za-z0-9_\-+/=.]{20,}/g, (run) => /[0-9]/.test(run) && /[A-Za-z]/.test(run) ? "[redacted]" : run).replace(/\s+/g, " ").trim();
3606
+ if (!text2) return void 0;
3607
+ return text2.length > SERVICE_MESSAGE_MAX_LENGTH2 ? `${text2.slice(0, SERVICE_MESSAGE_MAX_LENGTH2 - 1).trimEnd()}\u2026` : text2;
3608
+ }
3510
3609
  function canonicalTopoloRequestId2(value) {
3511
3610
  if (typeof value !== "string") return void 0;
3512
3611
  const candidate = value.trim();
@@ -3531,7 +3630,7 @@ var TopoloAuthError2 = class extends TopoloSdkError2 {
3531
3630
  retryAfter;
3532
3631
  details;
3533
3632
  constructor(message2, code = "auth_error", options = {}) {
3534
- super(canonicalTopoloErrorCode2(code) ?? "auth_error", message2);
3633
+ super(canonicalTopoloServiceErrorCode2(code) ?? "auth_error", message2);
3535
3634
  this.name = "TopoloAuthError";
3536
3635
  this.status = options.status ?? 401;
3537
3636
  this.body = options.body;
@@ -3547,14 +3646,14 @@ var TopoloAuthError2 = class extends TopoloSdkError2 {
3547
3646
  function extractTopoloServiceErrorCode2(body) {
3548
3647
  if (!body || typeof body !== "object" || Array.isArray(body)) return null;
3549
3648
  const record2 = body;
3550
- const topLevel = canonicalTopoloErrorCode2(record2.code);
3649
+ const topLevel = canonicalTopoloServiceErrorCode2(record2.code);
3551
3650
  if (topLevel) return topLevel;
3552
3651
  const nested = record2.error;
3553
- const nestedString = canonicalTopoloErrorCode2(nested);
3652
+ const nestedString = canonicalTopoloServiceErrorCode2(nested);
3554
3653
  if (nestedString) return nestedString;
3555
3654
  if (nested && typeof nested === "object" && !Array.isArray(nested)) {
3556
3655
  const code = nested.code;
3557
- const nestedCode = canonicalTopoloErrorCode2(code);
3656
+ const nestedCode = canonicalTopoloServiceErrorCode2(code);
3558
3657
  if (nestedCode) return nestedCode;
3559
3658
  }
3560
3659
  return null;
@@ -3572,10 +3671,7 @@ function extractTopoloServiceErrorDetails2(body) {
3572
3671
  if (candidate === void 0) candidate = nestedRecord.details ?? nestedRecord;
3573
3672
  }
3574
3673
  if (candidate === void 0) candidate = record2;
3575
- if (Array.isArray(candidate)) {
3576
- const paths = canonicalTopoloValidationPaths2(candidate);
3577
- return paths.length ? { paths } : void 0;
3578
- }
3674
+ if (Array.isArray(candidate)) return validationIssueDetails2(canonicalTopoloValidationIssues2(candidate)) ?? void 0;
3579
3675
  if (!candidate || typeof candidate !== "object") return void 0;
3580
3676
  const source = candidate;
3581
3677
  const safe = {};
@@ -3617,18 +3713,59 @@ function extractTopoloServiceErrorDetails2(body) {
3617
3713
  if (paths.length) safe[key] = paths;
3618
3714
  }
3619
3715
  }
3716
+ const issues = [
3717
+ ...Array.isArray(source.issues) ? canonicalTopoloValidationIssues2(source.issues) : [],
3718
+ ...flattenedValidationIssues2(source.formErrors, source.fieldErrors)
3719
+ ].slice(0, SERVICE_ISSUE_LIMIT2);
3720
+ Object.assign(safe, validationIssueDetails2(issues));
3620
3721
  return Object.keys(safe).length ? safe : void 0;
3621
3722
  }
3622
- function canonicalTopoloValidationPaths2(issues) {
3723
+ function validationIssueDetails2(issues) {
3724
+ if (!issues.length) return null;
3623
3725
  const paths = [];
3624
- for (const issue of issues) {
3625
- if (!issue || typeof issue !== "object" || Array.isArray(issue)) continue;
3626
- const record2 = issue;
3726
+ for (const issue22 of issues) {
3727
+ if (issue22.path && !paths.includes(issue22.path)) paths.push(issue22.path);
3728
+ }
3729
+ return paths.length ? { paths, issues } : { issues };
3730
+ }
3731
+ function issue2(path, message2) {
3732
+ const text2 = sanitizeTopoloServiceMessage2(message2);
3733
+ if (!path && !text2) return null;
3734
+ return { ...path ? { path } : {}, ...text2 ? { message: text2 } : {} };
3735
+ }
3736
+ function canonicalTopoloValidationIssues2(values) {
3737
+ const issues = [];
3738
+ for (const value of values) {
3739
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
3740
+ const record2 = value;
3627
3741
  const path = canonicalTopoloValidationPath2(record2.field) ?? canonicalTopoloValidationPath2(record2.path);
3628
- if (path && !paths.includes(path)) paths.push(path);
3629
- if (paths.length === 32) break;
3742
+ if (!path) continue;
3743
+ const projected = issue2(path, record2.message);
3744
+ if (projected) issues.push(projected);
3745
+ if (issues.length === SERVICE_ISSUE_LIMIT2) break;
3630
3746
  }
3631
- return paths;
3747
+ return issues;
3748
+ }
3749
+ function flattenedValidationIssues2(formErrors, fieldErrors) {
3750
+ const issues = [];
3751
+ if (Array.isArray(formErrors)) {
3752
+ for (const message2 of formErrors) {
3753
+ const projected = issue2(void 0, message2);
3754
+ if (projected) issues.push(projected);
3755
+ }
3756
+ }
3757
+ if (fieldErrors && typeof fieldErrors === "object" && !Array.isArray(fieldErrors)) {
3758
+ for (const [key, messages] of Object.entries(fieldErrors)) {
3759
+ const path = canonicalTopoloValidationPath2(key);
3760
+ if (!path) continue;
3761
+ const list = Array.isArray(messages) && messages.length ? messages : [void 0];
3762
+ for (const message2 of list) {
3763
+ const projected = issue2(path, message2);
3764
+ if (projected) issues.push(projected);
3765
+ }
3766
+ }
3767
+ }
3768
+ return issues;
3632
3769
  }
3633
3770
  function canonicalTopoloValidationPath2(value) {
3634
3771
  if (typeof value === "string") {
@@ -4462,6 +4599,10 @@ function fail2(err, mode) {
4462
4599
  `);
4463
4600
  if (payload.status) process.stderr.write(`HTTP: ${payload.status}
4464
4601
  `);
4602
+ for (const issue3 of validationIssues(payload.details)) {
4603
+ process.stderr.write(`Issue: ${[issue3.path, issue3.message].filter(Boolean).join(": ")}
4604
+ `);
4605
+ }
4465
4606
  if (payload.requestId) process.stderr.write(`Request: ${payload.requestId}
4466
4607
  `);
4467
4608
  if (payload.hint) process.stderr.write(`Hint: ${payload.hint}
@@ -4469,6 +4610,10 @@ function fail2(err, mode) {
4469
4610
  }
4470
4611
  process.exit(payload.exitCode);
4471
4612
  }
4613
+ function validationIssues(details) {
4614
+ const issues = details?.issues;
4615
+ return Array.isArray(issues) ? issues : [];
4616
+ }
4472
4617
  function normalizeError(err) {
4473
4618
  if (err instanceof Error) {
4474
4619
  const code = err.code;
@@ -4486,9 +4631,9 @@ function normalizeError(err) {
4486
4631
  path: err.path,
4487
4632
  requestId: err.requestId,
4488
4633
  retryAfter: err.retryAfter,
4489
- details: extractTopoloServiceErrorDetails(
4490
- suppliedDetails === void 0 ? body : { details: suppliedDetails }
4491
- ),
4634
+ // SDK HTTP errors already carry sanitized details; projecting them again
4635
+ // would drop form-level issues that have no field path.
4636
+ details: err instanceof TopoloHttpError || err instanceof TopoloAuthError || err instanceof TopoloPermissionError ? err.details : extractTopoloServiceErrorDetails(suppliedDetails === void 0 ? body : { details: suppliedDetails }),
4492
4637
  hint: topoloErrorHint({ code, body }) ?? (code ? fallbackHintMap[code] : void 0),
4493
4638
  exitCode: status === 401 || code === "auth_error" || code === "confirmation_required" ? 2 : status === 403 || code === "permission_denied" ? 3 : code === "immutable_drift" ? 4 : 1
4494
4639
  };
@@ -4833,7 +4978,7 @@ function stderrDebugLogger(event) {
4833
4978
  return;
4834
4979
  }
4835
4980
  const statusPart = event.status ? ` ${event.status}` : "";
4836
- const errorCode = canonicalTopoloErrorCode(event.error) ?? "request_failed";
4981
+ const errorCode = canonicalTopoloServiceErrorCode(event.error) ?? "request_failed";
4837
4982
  process.stderr.write(
4838
4983
  `[topolo] \u2717${statusPart} ${event.method} ${event.service} ${event.path} (${event.durationMs}ms, req=${requestId}, code=${errorCode})
4839
4984
  `
@@ -5087,7 +5232,7 @@ Approval required. ${opened ? "Opening" : "Open"} ${approval.verificationUrl}` :
5087
5232
  const topolo = await requiredClient();
5088
5233
  const result = await topolo.client.validateActionInput(action, parseJsonObject(opts.data || "{}"));
5089
5234
  const output = detailRequested(opts.detail, command) ? result : compactActionValidation(result.action.actionId, result.validation);
5090
- emit(output, mode, () => result.validation.valid ? `Valid input for ${result.action.actionId}.` : result.validation.errors.map((issue) => `${issue.path}: ${issue.message}`).join("\n"));
5235
+ emit(output, mode, () => result.validation.valid ? `Valid input for ${result.action.actionId}.` : result.validation.errors.map((issue3) => `${issue3.path}: ${issue3.message}`).join("\n"));
5091
5236
  } catch (err) {
5092
5237
  fail2(err, mode);
5093
5238
  }
@@ -5367,7 +5512,7 @@ function formatActionPlan(plan) {
5367
5512
  ];
5368
5513
  if (plan.request) lines.push(`Request: ${plan.request.method} ${plan.request.apiBaseUrl || plan.request.service}${plan.request.path}`);
5369
5514
  if (plan.request?.resource) lines.push(`Resource: ${plan.request.resource.resourceType}:${plan.request.resource.resourceId}`);
5370
- if (plan.validation.errors.length) lines.push("", ...plan.validation.errors.map((issue) => `${issue.path}: ${issue.message}`));
5515
+ if (plan.validation.errors.length) lines.push("", ...plan.validation.errors.map((issue3) => `${issue3.path}: ${issue3.message}`));
5371
5516
  if (plan.verification.length) lines.push("", "Verify:", ...plan.verification.map((step) => `- ${step}`));
5372
5517
  if (plan.rollbackActionId) lines.push("", `Rollback: ${plan.rollbackActionId}`);
5373
5518
  return lines.join("\n");
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@topolo/cli",
3
3
  "packageManager": "pnpm@10.17.1",
4
- "version": "0.12.11",
5
- "topoloPublishedVersion": "0.12.11",
4
+ "version": "0.12.13",
5
+ "topoloPublishedVersion": "0.12.13",
6
6
  "description": "Public command-line interface for Topolo customer organizations, developers, and agents.",
7
7
  "license": "UNLICENSED",
8
8
  "author": "Topolo",
@@ -28,8 +28,8 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@modelcontextprotocol/sdk": "^1.0.0",
31
- "@topolo/mcp": "0.14.4",
32
- "@topolo/sdk": "0.21.4",
31
+ "@topolo/mcp": "0.14.6",
32
+ "@topolo/sdk": "0.23.1",
33
33
  "commander": "^12.1.0"
34
34
  },
35
35
  "devDependencies": {
@@ -289,6 +289,8 @@ In JSON mode, branch on `error.code`, not message text:
289
289
  - exit 3 / permission denied: surface the missing permission or target denial; do not retry or widen access.
290
290
  - `invalid_action_input`: correct only the reported schema paths.
291
291
  - `confirmation_required`: plan, obtain approval, then retry once with confirmation.
292
+ - a service-published refusal code (for example `outreach_requires_mailbox_sender`): apply that action's published recovery for the code; `error.message` is the service's sanitized explanation and `error.details.issues` lists field-level problems.
293
+ - `http_error`: the service returned no structured code; use `status` and `requestId`.
292
294
  - unknown action/service: repeat bounded discovery; never substitute a guessed identifier.
293
295
  - missing docs/schema/examples/verification/recovery: stop the affected operation and report the missing publication contract.
294
296