@topolo/cli 0.12.11 → 0.12.12

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.22.0/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 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();
128
+ if (!text2) return void 0;
129
+ return text2.length > SERVICE_MESSAGE_MAX_LENGTH ? `${text2.slice(0, SERVICE_MESSAGE_MAX_LENGTH - 1).trimEnd()}\u2026` : text2;
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";
@@ -174,18 +195,34 @@ var TopoloPermissionError = class extends TopoloSdkError {
174
195
  function extractTopoloServiceErrorCode(body) {
175
196
  if (!body || typeof body !== "object" || Array.isArray(body)) return null;
176
197
  const record2 = body;
177
- const topLevel = canonicalTopoloErrorCode(record2.code);
198
+ const topLevel = canonicalTopoloServiceErrorCode(record2.code);
178
199
  if (topLevel) return topLevel;
179
200
  const nested = record2.error;
180
- const nestedString = canonicalTopoloErrorCode(nested);
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 record2 = body;
213
+ const nested = record2.error;
214
+ const candidates = [];
215
+ if (nested && typeof nested === "object" && !Array.isArray(nested)) {
216
+ candidates.push(nested.message);
217
+ }
218
+ candidates.push(record2.message, record2.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
  }
@@ -199,10 +236,7 @@ function extractTopoloServiceErrorDetails(body) {
199
236
  if (candidate === void 0) candidate = nestedRecord.details ?? nestedRecord;
200
237
  }
201
238
  if (candidate === void 0) candidate = record2;
202
- if (Array.isArray(candidate)) {
203
- const paths = canonicalTopoloValidationPaths(candidate);
204
- return paths.length ? { paths } : void 0;
205
- }
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;
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 text2 = sanitizeTopoloServiceMessage(message2);
298
+ if (!path && !text2) return null;
299
+ return { ...path ? { path } : {}, ...text2 ? { message: text2 } : {} };
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 record2 = value;
254
306
  const path = canonicalTopoloValidationPath(record2.field) ?? canonicalTopoloValidationPath(record2.path);
255
- if (path && !paths.includes(path)) paths.push(path);
256
- if (paths.length === 32) break;
307
+ if (!path) continue;
308
+ const projected = issue(path, record2.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;
@@ -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
  }
@@ -1982,7 +2059,7 @@ var TopoloClient = class _TopoloClient {
1982
2059
  input: selectedInput,
1983
2060
  inputSource: input2 ? "provided" : example ? "example" : "empty",
1984
2061
  missingFields: [...new Set(
1985
- plan.validation.errors.filter((issue) => issue.code === "required").map((issue) => issue.path)
2062
+ plan.validation.errors.filter((issue22) => issue22.code === "required").map((issue22) => issue22.path)
1986
2063
  )],
1987
2064
  plan
1988
2065
  };
@@ -2047,7 +2124,7 @@ var TopoloClient = class _TopoloClient {
2047
2124
  if (!validation.valid) {
2048
2125
  throw new TopoloSdkError(
2049
2126
  "invalid_action_input",
2050
- `Topolo action "${entry.actionId}" input is invalid: ${validation.errors.map((issue) => `${issue.path} ${issue.message}`).join("; ")}`
2127
+ `Topolo action "${entry.actionId}" input is invalid: ${validation.errors.map((issue22) => `${issue22.path} ${issue22.message}`).join("; ")}`
2051
2128
  );
2052
2129
  }
2053
2130
  const resourceIssue = validateActionResource(entry, options.resource);
@@ -3416,7 +3493,7 @@ function createTopolo(options) {
3416
3493
  };
3417
3494
  }
3418
3495
 
3419
- // node_modules/.pnpm/@topolo+sdk@0.21.4/node_modules/@topolo/sdk/dist/node/credential-store.js
3496
+ // node_modules/.pnpm/@topolo+sdk@0.22.0/node_modules/@topolo/sdk/dist/node/credential-store.js
3420
3497
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, chmod as chmod2, rm as rm2, stat } from "fs/promises";
3421
3498
  import { existsSync as existsSync2 } from "fs";
3422
3499
  import { homedir as homedir2, platform } from "os";
@@ -3483,6 +3560,9 @@ var PUBLIC_ERROR_CODES2 = /* @__PURE__ */ new Set([
3483
3560
  "unknown_service"
3484
3561
  ]);
3485
3562
  var ERROR_CODE_PATTERN2 = /^[a-z][a-z0-9_]{0,63}$/;
3563
+ var SERVICE_ERROR_CODE_PATTERN2 = /^[a-z][a-z0-9]*(?:_[a-z][a-z0-9]*){1,9}$/;
3564
+ var SERVICE_MESSAGE_MAX_LENGTH2 = 300;
3565
+ var SERVICE_ISSUE_LIMIT2 = 32;
3486
3566
  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
3567
  var CLOUDFLARE_RAY_ID_PATTERN2 = /^[0-9a-f]{16}-[A-Z]{3}$/;
3488
3568
  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 +3587,24 @@ function canonicalTopoloErrorCode2(value) {
3507
3587
  if (!ERROR_CODE_PATTERN2.test(candidate) || !PUBLIC_ERROR_CODES2.has(candidate)) return null;
3508
3588
  return candidate;
3509
3589
  }
3590
+ function canonicalTopoloServiceErrorCode2(value) {
3591
+ const platform22 = canonicalTopoloErrorCode2(value);
3592
+ if (platform22) return platform22;
3593
+ if (typeof value !== "string") return null;
3594
+ const candidate = value.trim();
3595
+ if (candidate.length > 64 || !SERVICE_ERROR_CODE_PATTERN2.test(candidate)) return null;
3596
+ const wordLike = candidate.split("_").every((segment) => {
3597
+ const digits = segment.replace(/[a-z]/g, "").length;
3598
+ return digits === 0 || digits < segment.length - digits && segment.length <= 8;
3599
+ });
3600
+ return wordLike ? candidate : null;
3601
+ }
3602
+ function sanitizeTopoloServiceMessage2(value) {
3603
+ if (typeof value !== "string") return void 0;
3604
+ 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();
3605
+ if (!text2) return void 0;
3606
+ return text2.length > SERVICE_MESSAGE_MAX_LENGTH2 ? `${text2.slice(0, SERVICE_MESSAGE_MAX_LENGTH2 - 1).trimEnd()}\u2026` : text2;
3607
+ }
3510
3608
  function canonicalTopoloRequestId2(value) {
3511
3609
  if (typeof value !== "string") return void 0;
3512
3610
  const candidate = value.trim();
@@ -3531,7 +3629,7 @@ var TopoloAuthError2 = class extends TopoloSdkError2 {
3531
3629
  retryAfter;
3532
3630
  details;
3533
3631
  constructor(message2, code = "auth_error", options = {}) {
3534
- super(canonicalTopoloErrorCode2(code) ?? "auth_error", message2);
3632
+ super(canonicalTopoloServiceErrorCode2(code) ?? "auth_error", message2);
3535
3633
  this.name = "TopoloAuthError";
3536
3634
  this.status = options.status ?? 401;
3537
3635
  this.body = options.body;
@@ -3547,14 +3645,14 @@ var TopoloAuthError2 = class extends TopoloSdkError2 {
3547
3645
  function extractTopoloServiceErrorCode2(body) {
3548
3646
  if (!body || typeof body !== "object" || Array.isArray(body)) return null;
3549
3647
  const record2 = body;
3550
- const topLevel = canonicalTopoloErrorCode2(record2.code);
3648
+ const topLevel = canonicalTopoloServiceErrorCode2(record2.code);
3551
3649
  if (topLevel) return topLevel;
3552
3650
  const nested = record2.error;
3553
- const nestedString = canonicalTopoloErrorCode2(nested);
3651
+ const nestedString = canonicalTopoloServiceErrorCode2(nested);
3554
3652
  if (nestedString) return nestedString;
3555
3653
  if (nested && typeof nested === "object" && !Array.isArray(nested)) {
3556
3654
  const code = nested.code;
3557
- const nestedCode = canonicalTopoloErrorCode2(code);
3655
+ const nestedCode = canonicalTopoloServiceErrorCode2(code);
3558
3656
  if (nestedCode) return nestedCode;
3559
3657
  }
3560
3658
  return null;
@@ -3572,10 +3670,7 @@ function extractTopoloServiceErrorDetails2(body) {
3572
3670
  if (candidate === void 0) candidate = nestedRecord.details ?? nestedRecord;
3573
3671
  }
3574
3672
  if (candidate === void 0) candidate = record2;
3575
- if (Array.isArray(candidate)) {
3576
- const paths = canonicalTopoloValidationPaths2(candidate);
3577
- return paths.length ? { paths } : void 0;
3578
- }
3673
+ if (Array.isArray(candidate)) return validationIssueDetails2(canonicalTopoloValidationIssues2(candidate)) ?? void 0;
3579
3674
  if (!candidate || typeof candidate !== "object") return void 0;
3580
3675
  const source = candidate;
3581
3676
  const safe = {};
@@ -3617,18 +3712,59 @@ function extractTopoloServiceErrorDetails2(body) {
3617
3712
  if (paths.length) safe[key] = paths;
3618
3713
  }
3619
3714
  }
3715
+ const issues = [
3716
+ ...Array.isArray(source.issues) ? canonicalTopoloValidationIssues2(source.issues) : [],
3717
+ ...flattenedValidationIssues2(source.formErrors, source.fieldErrors)
3718
+ ].slice(0, SERVICE_ISSUE_LIMIT2);
3719
+ Object.assign(safe, validationIssueDetails2(issues));
3620
3720
  return Object.keys(safe).length ? safe : void 0;
3621
3721
  }
3622
- function canonicalTopoloValidationPaths2(issues) {
3722
+ function validationIssueDetails2(issues) {
3723
+ if (!issues.length) return null;
3623
3724
  const paths = [];
3624
- for (const issue of issues) {
3625
- if (!issue || typeof issue !== "object" || Array.isArray(issue)) continue;
3626
- const record2 = issue;
3725
+ for (const issue22 of issues) {
3726
+ if (issue22.path && !paths.includes(issue22.path)) paths.push(issue22.path);
3727
+ }
3728
+ return paths.length ? { paths, issues } : { issues };
3729
+ }
3730
+ function issue2(path, message2) {
3731
+ const text2 = sanitizeTopoloServiceMessage2(message2);
3732
+ if (!path && !text2) return null;
3733
+ return { ...path ? { path } : {}, ...text2 ? { message: text2 } : {} };
3734
+ }
3735
+ function canonicalTopoloValidationIssues2(values) {
3736
+ const issues = [];
3737
+ for (const value of values) {
3738
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
3739
+ const record2 = value;
3627
3740
  const path = canonicalTopoloValidationPath2(record2.field) ?? canonicalTopoloValidationPath2(record2.path);
3628
- if (path && !paths.includes(path)) paths.push(path);
3629
- if (paths.length === 32) break;
3741
+ if (!path) continue;
3742
+ const projected = issue2(path, record2.message);
3743
+ if (projected) issues.push(projected);
3744
+ if (issues.length === SERVICE_ISSUE_LIMIT2) break;
3745
+ }
3746
+ return issues;
3747
+ }
3748
+ function flattenedValidationIssues2(formErrors, fieldErrors) {
3749
+ const issues = [];
3750
+ if (Array.isArray(formErrors)) {
3751
+ for (const message2 of formErrors) {
3752
+ const projected = issue2(void 0, message2);
3753
+ if (projected) issues.push(projected);
3754
+ }
3755
+ }
3756
+ if (fieldErrors && typeof fieldErrors === "object" && !Array.isArray(fieldErrors)) {
3757
+ for (const [key, messages] of Object.entries(fieldErrors)) {
3758
+ const path = canonicalTopoloValidationPath2(key);
3759
+ if (!path) continue;
3760
+ const list = Array.isArray(messages) && messages.length ? messages : [void 0];
3761
+ for (const message2 of list) {
3762
+ const projected = issue2(path, message2);
3763
+ if (projected) issues.push(projected);
3764
+ }
3765
+ }
3630
3766
  }
3631
- return paths;
3767
+ return issues;
3632
3768
  }
3633
3769
  function canonicalTopoloValidationPath2(value) {
3634
3770
  if (typeof value === "string") {
@@ -4462,6 +4598,10 @@ function fail2(err, mode) {
4462
4598
  `);
4463
4599
  if (payload.status) process.stderr.write(`HTTP: ${payload.status}
4464
4600
  `);
4601
+ for (const issue3 of validationIssues(payload.details)) {
4602
+ process.stderr.write(`Issue: ${[issue3.path, issue3.message].filter(Boolean).join(": ")}
4603
+ `);
4604
+ }
4465
4605
  if (payload.requestId) process.stderr.write(`Request: ${payload.requestId}
4466
4606
  `);
4467
4607
  if (payload.hint) process.stderr.write(`Hint: ${payload.hint}
@@ -4469,6 +4609,10 @@ function fail2(err, mode) {
4469
4609
  }
4470
4610
  process.exit(payload.exitCode);
4471
4611
  }
4612
+ function validationIssues(details) {
4613
+ const issues = details?.issues;
4614
+ return Array.isArray(issues) ? issues : [];
4615
+ }
4472
4616
  function normalizeError(err) {
4473
4617
  if (err instanceof Error) {
4474
4618
  const code = err.code;
@@ -4486,9 +4630,9 @@ function normalizeError(err) {
4486
4630
  path: err.path,
4487
4631
  requestId: err.requestId,
4488
4632
  retryAfter: err.retryAfter,
4489
- details: extractTopoloServiceErrorDetails(
4490
- suppliedDetails === void 0 ? body : { details: suppliedDetails }
4491
- ),
4633
+ // SDK HTTP errors already carry sanitized details; projecting them again
4634
+ // would drop form-level issues that have no field path.
4635
+ details: err instanceof TopoloHttpError || err instanceof TopoloAuthError || err instanceof TopoloPermissionError ? err.details : extractTopoloServiceErrorDetails(suppliedDetails === void 0 ? body : { details: suppliedDetails }),
4492
4636
  hint: topoloErrorHint({ code, body }) ?? (code ? fallbackHintMap[code] : void 0),
4493
4637
  exitCode: status === 401 || code === "auth_error" || code === "confirmation_required" ? 2 : status === 403 || code === "permission_denied" ? 3 : code === "immutable_drift" ? 4 : 1
4494
4638
  };
@@ -4833,7 +4977,7 @@ function stderrDebugLogger(event) {
4833
4977
  return;
4834
4978
  }
4835
4979
  const statusPart = event.status ? ` ${event.status}` : "";
4836
- const errorCode = canonicalTopoloErrorCode(event.error) ?? "request_failed";
4980
+ const errorCode = canonicalTopoloServiceErrorCode(event.error) ?? "request_failed";
4837
4981
  process.stderr.write(
4838
4982
  `[topolo] \u2717${statusPart} ${event.method} ${event.service} ${event.path} (${event.durationMs}ms, req=${requestId}, code=${errorCode})
4839
4983
  `
@@ -5087,7 +5231,7 @@ Approval required. ${opened ? "Opening" : "Open"} ${approval.verificationUrl}` :
5087
5231
  const topolo = await requiredClient();
5088
5232
  const result = await topolo.client.validateActionInput(action, parseJsonObject(opts.data || "{}"));
5089
5233
  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"));
5234
+ emit(output, mode, () => result.validation.valid ? `Valid input for ${result.action.actionId}.` : result.validation.errors.map((issue3) => `${issue3.path}: ${issue3.message}`).join("\n"));
5091
5235
  } catch (err) {
5092
5236
  fail2(err, mode);
5093
5237
  }
@@ -5367,7 +5511,7 @@ function formatActionPlan(plan) {
5367
5511
  ];
5368
5512
  if (plan.request) lines.push(`Request: ${plan.request.method} ${plan.request.apiBaseUrl || plan.request.service}${plan.request.path}`);
5369
5513
  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}`));
5514
+ if (plan.validation.errors.length) lines.push("", ...plan.validation.errors.map((issue3) => `${issue3.path}: ${issue3.message}`));
5371
5515
  if (plan.verification.length) lines.push("", "Verify:", ...plan.verification.map((step) => `- ${step}`));
5372
5516
  if (plan.rollbackActionId) lines.push("", `Rollback: ${plan.rollbackActionId}`);
5373
5517
  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.12",
5
+ "topoloPublishedVersion": "0.12.12",
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.5",
32
+ "@topolo/sdk": "0.22.0",
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