@topolo/cli 0.12.10 → 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.2/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);
@@ -28,58 +28,6 @@ function generateRequestId() {
28
28
  }
29
29
  return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
30
30
  }
31
- var encoder = new TextEncoder();
32
- function encodeBase64Url(value) {
33
- const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
34
- let binary = "";
35
- for (const byte of bytes) binary += String.fromCharCode(byte);
36
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
37
- }
38
- function encodeJson(value) {
39
- return encodeBase64Url(encoder.encode(JSON.stringify(value)));
40
- }
41
- async function sha256Base64Url(value) {
42
- return encodeBase64Url(await crypto.subtle.digest("SHA-256", encoder.encode(value)));
43
- }
44
- function publicJwk(privateJwk) {
45
- const { crv, kty, x, y } = privateJwk;
46
- if (kty !== "EC" || crv !== "P-256" || !x || !y || !privateJwk.d) {
47
- throw new TypeError("A private P-256 JSON Web Key is required for DPoP signing.");
48
- }
49
- return { crv, kty, x, y };
50
- }
51
- function canonicalTargetUrl(value) {
52
- const url = new URL(value);
53
- return `${url.origin}${url.pathname}`;
54
- }
55
- async function calculateDpopJkt(privateJwk) {
56
- const jwk = publicJwk(privateJwk);
57
- return sha256Base64Url(JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y }));
58
- }
59
- async function createDpopProof(input2) {
60
- const jwk = publicJwk(input2.privateJwk);
61
- const key = await crypto.subtle.importKey(
62
- "jwk",
63
- input2.privateJwk,
64
- { name: "ECDSA", namedCurve: "P-256" },
65
- false,
66
- ["sign"]
67
- );
68
- const header = encodeJson({ alg: "ES256", typ: "dpop+jwt", jwk });
69
- const claims = encodeJson({
70
- jti: crypto.randomUUID(),
71
- iat: Math.floor(Date.now() / 1e3),
72
- htm: input2.method.toUpperCase(),
73
- htu: canonicalTargetUrl(input2.targetUrl),
74
- ath: await sha256Base64Url(input2.accessToken)
75
- });
76
- const signature = await crypto.subtle.sign(
77
- { name: "ECDSA", hash: "SHA-256" },
78
- key,
79
- encoder.encode(`${header}.${claims}`)
80
- );
81
- return `${header}.${claims}.${encodeBase64Url(signature)}`;
82
- }
83
31
  var TopoloSdkError = class extends Error {
84
32
  code;
85
33
  constructor(code, message2) {
@@ -135,6 +83,9 @@ var PUBLIC_ERROR_CODES = /* @__PURE__ */ new Set([
135
83
  "unknown_service"
136
84
  ]);
137
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;
138
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;
139
90
  var CLOUDFLARE_RAY_ID_PATTERN = /^[0-9a-f]{16}-[A-Z]{3}$/;
140
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}|\*)$/;
@@ -159,6 +110,24 @@ function canonicalTopoloErrorCode(value) {
159
110
  if (!ERROR_CODE_PATTERN.test(candidate) || !PUBLIC_ERROR_CODES.has(candidate)) return null;
160
111
  return candidate;
161
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
+ }
162
131
  function canonicalTopoloRequestId(value) {
163
132
  if (typeof value !== "string") return void 0;
164
133
  const candidate = value.trim();
@@ -183,7 +152,7 @@ var TopoloAuthError = class extends TopoloSdkError {
183
152
  retryAfter;
184
153
  details;
185
154
  constructor(message2, code = "auth_error", options = {}) {
186
- super(canonicalTopoloErrorCode(code) ?? "auth_error", message2);
155
+ super(canonicalTopoloServiceErrorCode(code) ?? "auth_error", message2);
187
156
  this.name = "TopoloAuthError";
188
157
  this.status = options.status ?? 401;
189
158
  this.body = options.body;
@@ -207,7 +176,7 @@ var TopoloPermissionError = class extends TopoloSdkError {
207
176
  details;
208
177
  constructor(message2, required, options = {}) {
209
178
  super(
210
- canonicalTopoloErrorCode(options.code) ?? serviceErrorCode(options.body, "permission_denied"),
179
+ canonicalTopoloServiceErrorCode(options.code) ?? serviceErrorCode(options.body, "permission_denied"),
211
180
  message2
212
181
  );
213
182
  this.name = "TopoloPermissionError";
@@ -226,18 +195,34 @@ var TopoloPermissionError = class extends TopoloSdkError {
226
195
  function extractTopoloServiceErrorCode(body) {
227
196
  if (!body || typeof body !== "object" || Array.isArray(body)) return null;
228
197
  const record2 = body;
229
- const topLevel = canonicalTopoloErrorCode(record2.code);
198
+ const topLevel = canonicalTopoloServiceErrorCode(record2.code);
230
199
  if (topLevel) return topLevel;
231
200
  const nested = record2.error;
232
- const nestedString = canonicalTopoloErrorCode(nested);
201
+ const nestedString = canonicalTopoloServiceErrorCode(nested);
233
202
  if (nestedString) return nestedString;
234
203
  if (nested && typeof nested === "object" && !Array.isArray(nested)) {
235
204
  const code = nested.code;
236
- const nestedCode = canonicalTopoloErrorCode(code);
205
+ const nestedCode = canonicalTopoloServiceErrorCode(code);
237
206
  if (nestedCode) return nestedCode;
238
207
  }
239
208
  return null;
240
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
+ }
241
226
  function serviceErrorCode(body, fallback) {
242
227
  return extractTopoloServiceErrorCode(body) ?? fallback;
243
228
  }
@@ -251,10 +236,7 @@ function extractTopoloServiceErrorDetails(body) {
251
236
  if (candidate === void 0) candidate = nestedRecord.details ?? nestedRecord;
252
237
  }
253
238
  if (candidate === void 0) candidate = record2;
254
- if (Array.isArray(candidate)) {
255
- const paths = canonicalTopoloValidationPaths(candidate);
256
- return paths.length ? { paths } : void 0;
257
- }
239
+ if (Array.isArray(candidate)) return validationIssueDetails(canonicalTopoloValidationIssues(candidate)) ?? void 0;
258
240
  if (!candidate || typeof candidate !== "object") return void 0;
259
241
  const source = candidate;
260
242
  const safe = {};
@@ -296,18 +278,59 @@ function extractTopoloServiceErrorDetails(body) {
296
278
  if (paths.length) safe[key] = paths;
297
279
  }
298
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));
299
286
  return Object.keys(safe).length ? safe : void 0;
300
287
  }
301
- function canonicalTopoloValidationPaths(issues) {
288
+ function validationIssueDetails(issues) {
289
+ if (!issues.length) return null;
302
290
  const paths = [];
303
- for (const issue of issues) {
304
- if (!issue || typeof issue !== "object" || Array.isArray(issue)) continue;
305
- 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;
306
306
  const path = canonicalTopoloValidationPath(record2.field) ?? canonicalTopoloValidationPath(record2.path);
307
- if (path && !paths.includes(path)) paths.push(path);
308
- 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;
309
311
  }
310
- return paths;
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
+ }
332
+ }
333
+ return issues;
311
334
  }
312
335
  function canonicalTopoloValidationPath(value) {
313
336
  if (typeof value === "string") {
@@ -329,7 +352,9 @@ function canonicalTopoloValidationPath(value) {
329
352
  return PUBLIC_FIELD_PATH_PATTERN.test(path) ? path : void 0;
330
353
  }
331
354
  function publicTopoloErrorMessage(error) {
332
- const code = canonicalTopoloErrorCode(
355
+ const serviceMessage = typeof error === "string" ? void 0 : extractTopoloServiceErrorMessage(error.body);
356
+ if (serviceMessage) return serviceMessage;
357
+ const code = canonicalTopoloServiceErrorCode(
333
358
  typeof error === "string" ? error : error.code ?? extractTopoloServiceErrorCode(error.body)
334
359
  ) ?? "request_failed";
335
360
  if (/(?:organization|org).*(?:enroll|membership|member|admission)/.test(code)) {
@@ -346,7 +371,7 @@ function publicTopoloErrorMessage(error) {
346
371
  return `Topolo request failed (${code}).`;
347
372
  }
348
373
  function topoloErrorHint(error) {
349
- const code = canonicalTopoloErrorCode(
374
+ const code = canonicalTopoloServiceErrorCode(
350
375
  typeof error === "string" ? error : error.code ?? extractTopoloServiceErrorCode(error.body)
351
376
  );
352
377
  if (!code) return void 0;
@@ -401,6 +426,136 @@ var TopoloHttpError = class extends TopoloSdkError {
401
426
  );
402
427
  }
403
428
  };
429
+ var ACTION_SECRET_PREFIX = "topolo-secret:";
430
+ function isActionSecretReference(value) {
431
+ return typeof value === "string" && value.startsWith(ACTION_SECRET_PREFIX);
432
+ }
433
+ function isRecord(value) {
434
+ return typeof value === "object" && value !== null && !Array.isArray(value);
435
+ }
436
+ function sensitive(key, value) {
437
+ const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
438
+ if (normalized === "capability") {
439
+ if (typeof value !== "string") return value !== null && value !== void 0 && typeof value !== "boolean" && typeof value !== "number";
440
+ const text2 = value.trim();
441
+ return Boolean(text2) && !/^[a-z][a-z0-9_-]{0,31}$/.test(text2);
442
+ }
443
+ return ["authorization", "cookie", "setcookie", "proxyauthorization"].includes(normalized) || /(token|secret|password|privatekey|apikey)$/.test(normalized);
444
+ }
445
+ function maskActionSecrets(value, secrets) {
446
+ let masked = value;
447
+ for (const [secret, reference] of secrets) {
448
+ const encoded = encodeURIComponent(secret);
449
+ const variants = [.../* @__PURE__ */ new Set([secret, encoded])].map((item) => item.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
450
+ masked = masked.replace(new RegExp(variants.join("|"), "g"), (match) => match === secret ? reference : encodeURIComponent(reference));
451
+ }
452
+ return masked;
453
+ }
454
+ async function resolveActionSecrets(value, store, secrets) {
455
+ if (isActionSecretReference(value)) {
456
+ if (!store) throw new TopoloSdkError("action_secret_store_required", "Protected action references require a secret store.");
457
+ const resolved = await store.load(value);
458
+ if (typeof resolved !== "string" || !resolved) throw new TopoloSdkError("action_secret_unsupported_type", "Protected action handoff requires a nonempty string credential.");
459
+ secrets.set(resolved, value);
460
+ return resolved;
461
+ }
462
+ if (Array.isArray(value)) return Promise.all(value.map((item) => resolveActionSecrets(item, store, secrets)));
463
+ if (!isRecord(value)) return value;
464
+ return Object.fromEntries(await Promise.all(Object.entries(value).map(async ([key, item]) => [key, await resolveActionSecrets(item, store, secrets)])));
465
+ }
466
+ async function protectActionResult(value, store) {
467
+ const secrets = /* @__PURE__ */ new Map();
468
+ async function protect(item, key = "") {
469
+ if (isActionSecretReference(item)) return item;
470
+ if (sensitive(key, item)) {
471
+ const reference = store ? await store.save(item) : "[REDACTED]";
472
+ if (typeof item === "string" && item) secrets.set(item, reference);
473
+ return reference;
474
+ }
475
+ if (Array.isArray(item)) return Promise.all(item.map((child) => protect(child)));
476
+ if (!isRecord(item)) return item;
477
+ return Object.fromEntries(await Promise.all(Object.entries(item).map(async ([name, child]) => [name, await protect(child, name)])));
478
+ }
479
+ const protectedValue = await protect(value);
480
+ async function protectEmbedded(item) {
481
+ if (typeof item === "string" && !isActionSecretReference(item) && maskActionSecrets(item, secrets) !== item) {
482
+ return store ? store.save(item) : maskActionSecrets(item, secrets);
483
+ }
484
+ if (Array.isArray(item)) return Promise.all(item.map(protectEmbedded));
485
+ if (!isRecord(item)) return item;
486
+ return Object.fromEntries(await Promise.all(Object.entries(item).map(async ([key, child]) => [key, await protectEmbedded(child)])));
487
+ }
488
+ return protectEmbedded(protectedValue);
489
+ }
490
+ function maskActionSecretValue(value, secrets) {
491
+ if (value instanceof Error) return maskActionSecretError(value, secrets);
492
+ if (typeof value === "string") return maskActionSecrets(value, secrets);
493
+ if (Array.isArray(value)) return value.map((item) => maskActionSecretValue(item, secrets));
494
+ if (!isRecord(value)) return value;
495
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, maskActionSecretValue(item, secrets)]));
496
+ }
497
+ function maskActionSecretError(error, secrets) {
498
+ if (!(error instanceof Error)) return maskActionSecretValue(error, secrets);
499
+ const masked = Object.create(Object.getPrototypeOf(error));
500
+ for (const key of Object.getOwnPropertyNames(error)) {
501
+ const descriptor = Object.getOwnPropertyDescriptor(error, key);
502
+ if ("value" in descriptor) descriptor.value = maskActionSecretValue(descriptor.value, secrets);
503
+ Object.defineProperty(masked, key, descriptor);
504
+ }
505
+ return masked;
506
+ }
507
+ var encoder = new TextEncoder();
508
+ function encodeBase64Url(value) {
509
+ const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
510
+ let binary = "";
511
+ for (const byte of bytes) binary += String.fromCharCode(byte);
512
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
513
+ }
514
+ function encodeJson(value) {
515
+ return encodeBase64Url(encoder.encode(JSON.stringify(value)));
516
+ }
517
+ async function sha256Base64Url(value) {
518
+ return encodeBase64Url(await crypto.subtle.digest("SHA-256", encoder.encode(value)));
519
+ }
520
+ function publicJwk(privateJwk) {
521
+ const { crv, kty, x, y } = privateJwk;
522
+ if (kty !== "EC" || crv !== "P-256" || !x || !y || !privateJwk.d) {
523
+ throw new TypeError("A private P-256 JSON Web Key is required for DPoP signing.");
524
+ }
525
+ return { crv, kty, x, y };
526
+ }
527
+ function canonicalTargetUrl(value) {
528
+ const url = new URL(value);
529
+ return `${url.origin}${url.pathname}`;
530
+ }
531
+ async function calculateDpopJkt(privateJwk) {
532
+ const jwk = publicJwk(privateJwk);
533
+ return sha256Base64Url(JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y }));
534
+ }
535
+ async function createDpopProof(input2) {
536
+ const jwk = publicJwk(input2.privateJwk);
537
+ const key = await crypto.subtle.importKey(
538
+ "jwk",
539
+ input2.privateJwk,
540
+ { name: "ECDSA", namedCurve: "P-256" },
541
+ false,
542
+ ["sign"]
543
+ );
544
+ const header = encodeJson({ alg: "ES256", typ: "dpop+jwt", jwk });
545
+ const claims = encodeJson({
546
+ jti: crypto.randomUUID(),
547
+ iat: Math.floor(Date.now() / 1e3),
548
+ htm: input2.method.toUpperCase(),
549
+ htu: canonicalTargetUrl(input2.targetUrl),
550
+ ath: await sha256Base64Url(input2.accessToken)
551
+ });
552
+ const signature = await crypto.subtle.sign(
553
+ { name: "ECDSA", hash: "SHA-256" },
554
+ key,
555
+ encoder.encode(`${header}.${claims}`)
556
+ );
557
+ return `${header}.${claims}.${encodeBase64Url(signature)}`;
558
+ }
404
559
  function hostnameHasSuffix(hostname, suffix) {
405
560
  return hostname === suffix || hostname.endsWith(`.${suffix}`);
406
561
  }
@@ -574,9 +729,9 @@ function inspectSchema(schema, path, root, depth) {
574
729
  return { path, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
575
730
  }
576
731
  const children = [];
577
- if (isRecord(schema.properties)) children.push(...Object.entries(schema.properties));
578
- if (isRecord(schema.$defs)) children.push(...Object.entries(schema.$defs));
579
- if (isRecord(schema.definitions)) children.push(...Object.entries(schema.definitions));
732
+ if (isRecord2(schema.properties)) children.push(...Object.entries(schema.properties));
733
+ if (isRecord2(schema.$defs)) children.push(...Object.entries(schema.$defs));
734
+ if (isRecord2(schema.definitions)) children.push(...Object.entries(schema.definitions));
580
735
  children.push(
581
736
  ["additionalProperties", schema.additionalProperties],
582
737
  ["propertyNames", schema.propertyNames],
@@ -586,9 +741,9 @@ function inspectSchema(schema, path, root, depth) {
586
741
  ...arraySchemaEntries("oneOf", schema.oneOf)
587
742
  );
588
743
  for (const [key, child] of children) {
589
- if (!isRecord(child)) continue;
590
- const issue = inspectSchema(child, `${path}.${key}`, root, depth + 1);
591
- if (issue) return issue;
744
+ if (!isRecord2(child)) continue;
745
+ const issue22 = inspectSchema(child, `${path}.${key}`, root, depth + 1);
746
+ if (issue22) return issue22;
592
747
  }
593
748
  return null;
594
749
  }
@@ -610,7 +765,7 @@ function validateNode(schema, value, path, root, errors, depth) {
610
765
  }
611
766
  if (Array.isArray(schema.allOf)) {
612
767
  for (const candidate of schema.allOf) {
613
- if (isRecord(candidate)) validateNode(candidate, value, path, root, errors, depth + 1);
768
+ if (isRecord2(candidate)) validateNode(candidate, value, path, root, errors, depth + 1);
614
769
  }
615
770
  }
616
771
  if (Array.isArray(schema.anyOf) && !schema.anyOf.some((candidate) => candidateMatches(candidate, value, root, depth))) {
@@ -640,7 +795,7 @@ function validateNode(schema, value, path, root, errors, depth) {
640
795
  if (typeof value === "string") validateString(schema, value, path, errors);
641
796
  if (typeof value === "number") validateNumber(schema, value, path, errors);
642
797
  if (Array.isArray(value)) validateArray(schema, value, path, root, errors, depth);
643
- if (isRecord(value)) validateObject(schema, value, path, root, errors, depth);
798
+ if (isRecord2(value)) validateObject(schema, value, path, root, errors, depth);
644
799
  }
645
800
  function validateObject(schema, value, path, root, errors, depth) {
646
801
  if (typeof schema.minProperties === "number" && Object.keys(value).length < schema.minProperties) {
@@ -649,25 +804,25 @@ function validateObject(schema, value, path, root, errors, depth) {
649
804
  if (typeof schema.maxProperties === "number" && Object.keys(value).length > schema.maxProperties) {
650
805
  errors.push({ path, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
651
806
  }
652
- const properties = isRecord(schema.properties) ? schema.properties : {};
807
+ const properties = isRecord2(schema.properties) ? schema.properties : {};
653
808
  const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
654
809
  for (const key of required) {
655
810
  if (!Object.prototype.hasOwnProperty.call(value, key)) {
656
811
  errors.push({ path: `${path}.${key}`, code: "required", message: "Required property is missing." });
657
812
  }
658
813
  }
659
- if (isRecord(schema.propertyNames)) {
814
+ if (isRecord2(schema.propertyNames)) {
660
815
  for (const key of Object.keys(value)) {
661
816
  validateNode(schema.propertyNames, key, `${path}.${key}`, root, errors, depth + 1);
662
817
  }
663
818
  }
664
819
  for (const [key, child] of Object.entries(value)) {
665
820
  const childSchema = properties[key];
666
- if (isRecord(childSchema)) {
821
+ if (isRecord2(childSchema)) {
667
822
  validateNode(childSchema, child, `${path}.${key}`, root, errors, depth + 1);
668
823
  } else if (schema.additionalProperties === false) {
669
824
  errors.push({ path: `${path}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
670
- } else if (isRecord(schema.additionalProperties)) {
825
+ } else if (isRecord2(schema.additionalProperties)) {
671
826
  validateNode(schema.additionalProperties, child, `${path}.${key}`, root, errors, depth + 1);
672
827
  }
673
828
  }
@@ -683,7 +838,7 @@ function validateArray(schema, value, path, root, errors, depth) {
683
838
  const unique = new Set(value.map((item) => JSON.stringify(item)));
684
839
  if (unique.size !== value.length) errors.push({ path, code: "unique_items", message: "Expected every item to be unique." });
685
840
  }
686
- if (isRecord(schema.items)) {
841
+ if (isRecord2(schema.items)) {
687
842
  value.forEach((item, index) => validateNode(schema.items, item, `${path}[${index}]`, root, errors, depth + 1));
688
843
  }
689
844
  }
@@ -721,7 +876,7 @@ function validateNumber(schema, value, path, errors) {
721
876
  }
722
877
  }
723
878
  function candidateMatches(candidate, value, root, depth) {
724
- if (!isRecord(candidate)) return false;
879
+ if (!isRecord2(candidate)) return false;
725
880
  const issues = [];
726
881
  validateNode(candidate, value, "$", root, issues, depth + 1);
727
882
  return issues.length === 0;
@@ -735,24 +890,24 @@ function resolveLocalReference(root, reference) {
735
890
  current = current[Number(token)];
736
891
  continue;
737
892
  }
738
- if (!isRecord(current) || !Object.prototype.hasOwnProperty.call(current, token)) return null;
893
+ if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, token)) return null;
739
894
  current = current[token];
740
895
  }
741
- return isRecord(current) ? current : null;
896
+ return isRecord2(current) ? current : null;
742
897
  }
743
898
  function matchesType(type, value) {
744
899
  if (type === void 0) return true;
745
900
  if (Array.isArray(type)) return type.some((candidate) => matchesType(candidate, value));
746
901
  if (type === "null") return value === null;
747
902
  if (type === "array") return Array.isArray(value);
748
- if (type === "object") return isRecord(value);
903
+ if (type === "object") return isRecord2(value);
749
904
  if (type === "integer") return typeof value === "number" && Number.isInteger(value);
750
905
  return typeof value === type;
751
906
  }
752
907
  function sameJsonValue(left, right) {
753
908
  return JSON.stringify(left) === JSON.stringify(right);
754
909
  }
755
- function isRecord(value) {
910
+ function isRecord2(value) {
756
911
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
757
912
  }
758
913
  function isUrl(value) {
@@ -987,7 +1142,7 @@ function fail(path, message2) {
987
1142
  throw new ActorEnvelopeValidationError(path, message2);
988
1143
  }
989
1144
  function parseTopoloActionUploadContract(value) {
990
- if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
1145
+ if (!isRecord3(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
991
1146
  throw new TopoloSdkError("upload_contract_unavailable", "This action does not publish a supported upload contract.");
992
1147
  }
993
1148
  const contentTypeField = requiredField(value.contentTypeField, "contentTypeField");
@@ -1066,17 +1221,17 @@ function prepareTopoloUrlSessionUploadInput(contractValue, byteLength, input2, o
1066
1221
  };
1067
1222
  }
1068
1223
  function topoloUploadResponseValue(response, path) {
1069
- return path.split(".").reduce((value, segment) => isRecord2(value) ? value[segment] : void 0, response);
1224
+ return path.split(".").reduce((value, segment) => isRecord3(value) ? value[segment] : void 0, response);
1070
1225
  }
1071
1226
  function redactTopoloUrlSessionResult(response, uploadUrlPath) {
1072
1227
  const redacted = cloneUploadValue(response);
1073
1228
  let target = redacted;
1074
1229
  const segments = uploadUrlPath.split(".");
1075
1230
  for (const segment of segments.slice(0, -1)) {
1076
- target = isRecord2(target) ? target[segment] : void 0;
1231
+ target = isRecord3(target) ? target[segment] : void 0;
1077
1232
  }
1078
1233
  const finalSegment = segments.at(-1);
1079
- if (finalSegment && isRecord2(target) && finalSegment in target) target[finalSegment] = "[REDACTED]";
1234
+ if (finalSegment && isRecord3(target) && finalSegment in target) target[finalSegment] = "[REDACTED]";
1080
1235
  redactUploadHeaders(redacted);
1081
1236
  return redacted;
1082
1237
  }
@@ -1100,7 +1255,7 @@ function mapUploadInput(contract, contentBase64, input2, options) {
1100
1255
  }
1101
1256
  function cloneUploadValue(value) {
1102
1257
  if (Array.isArray(value)) return value.map(cloneUploadValue);
1103
- if (!isRecord2(value)) return value;
1258
+ if (!isRecord3(value)) return value;
1104
1259
  return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, cloneUploadValue(item)]));
1105
1260
  }
1106
1261
  function redactUploadHeaders(value) {
@@ -1108,9 +1263,9 @@ function redactUploadHeaders(value) {
1108
1263
  value.forEach(redactUploadHeaders);
1109
1264
  return;
1110
1265
  }
1111
- if (!isRecord2(value)) return;
1266
+ if (!isRecord3(value)) return;
1112
1267
  for (const [key, item] of Object.entries(value)) {
1113
- if (key.toLowerCase() === "headers" && isRecord2(item)) {
1268
+ if (key.toLowerCase() === "headers" && isRecord3(item)) {
1114
1269
  for (const header of Object.keys(item)) {
1115
1270
  if (["authorization", "cookie", "set-cookie", "proxy-authorization", "x-api-key"].includes(header.toLowerCase())) {
1116
1271
  item[header] = "[REDACTED]";
@@ -1139,7 +1294,7 @@ function decodedBase64Size(contentBase64) {
1139
1294
  return contentBase64.length / 4 * 3 - padding;
1140
1295
  }
1141
1296
  function parseCompleteContract(value) {
1142
- if (!isRecord2(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
1297
+ if (!isRecord3(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
1143
1298
  return {
1144
1299
  actionId: requiredPath(value.actionId, "complete.actionId"),
1145
1300
  inputField: requiredField(value.inputField, "complete.inputField"),
@@ -1177,7 +1332,7 @@ function encodeTopoloActionBase64(bytes) {
1177
1332
  }
1178
1333
  return btoa(binary);
1179
1334
  }
1180
- function isRecord2(value) {
1335
+ function isRecord3(value) {
1181
1336
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1182
1337
  }
1183
1338
  var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
@@ -1188,6 +1343,8 @@ var TOPOLO_RESOURCE_ID_HEADER = "X-Topolo-Resource-ID";
1188
1343
  var POSITIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
1189
1344
  var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
1190
1345
  var TopoloClient = class _TopoloClient {
1346
+ actionSecretStore;
1347
+ actionSecrets = /* @__PURE__ */ new Map();
1191
1348
  credential;
1192
1349
  agent;
1193
1350
  actorContext;
@@ -1217,6 +1374,7 @@ var TopoloClient = class _TopoloClient {
1217
1374
  _TopoloClient.appIdCache.clear();
1218
1375
  }
1219
1376
  constructor(options) {
1377
+ this.actionSecretStore = options.actionSecretStore;
1220
1378
  if (!options.credential) throw new TopoloAuthError("credential is required");
1221
1379
  if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
1222
1380
  this.credential = options.credential;
@@ -1238,6 +1396,7 @@ var TopoloClient = class _TopoloClient {
1238
1396
  }
1239
1397
  emit(event) {
1240
1398
  if (!this.debug) return;
1399
+ event = maskActionSecretValue(event, this.actionSecrets);
1241
1400
  try {
1242
1401
  this.debug(event);
1243
1402
  } catch {
@@ -1623,7 +1782,7 @@ var TopoloClient = class _TopoloClient {
1623
1782
  }
1624
1783
  const entries = [];
1625
1784
  for (const item of raw) {
1626
- if (!isRecord3(item)) continue;
1785
+ if (!isRecord4(item)) continue;
1627
1786
  const s = item;
1628
1787
  const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
1629
1788
  if (!appId) continue;
@@ -1698,8 +1857,8 @@ var TopoloClient = class _TopoloClient {
1698
1857
  "app_topolo_auth.service_api_key_resources.list",
1699
1858
  { appId: service.appId }
1700
1859
  );
1701
- const payload = isRecord3(response) && isRecord3(response.data) ? response.data : response;
1702
- if (!isRecord3(payload) || !Array.isArray(payload.resources)) {
1860
+ const payload = isRecord4(response) && isRecord4(response.data) ? response.data : response;
1861
+ if (!isRecord4(payload) || !Array.isArray(payload.resources)) {
1703
1862
  throw new TopoloSdkError("resource_catalog_unavailable", "TopoloAuth did not return bindable resources.");
1704
1863
  }
1705
1864
  return {
@@ -1880,24 +2039,27 @@ var TopoloClient = class _TopoloClient {
1880
2039
  }
1881
2040
  async validateActionInput(action, input2 = {}) {
1882
2041
  const entry = await this.getAction(action);
1883
- return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input2) };
2042
+ const resolved = await resolveActionSecrets(input2, this.actionSecretStore, this.actionSecrets);
2043
+ return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, resolved) };
1884
2044
  }
1885
2045
  async planAction(action, input2 = {}, options = {}) {
1886
2046
  const entry = await this.getAction(action);
1887
- return this.planResolvedAction(entry, input2, options);
2047
+ const resolved = await resolveActionSecrets(input2, this.actionSecretStore, this.actionSecrets);
2048
+ return this.planResolvedAction(entry, resolved, options);
1888
2049
  }
1889
2050
  async prepareAction(action, input2, options = {}) {
1890
2051
  const entry = await this.getAction(action);
1891
2052
  const example = (entry.agentContract?.examples ?? []).find((candidate) => Boolean(candidate?.input) && typeof candidate.input === "object" && !Array.isArray(candidate.input) && validateTopoloActionInput(entry.inputSchema, candidate.input).valid) ?? null;
1892
2053
  const selectedInput = input2 ?? example?.input ?? {};
1893
- const plan = this.planResolvedAction(entry, selectedInput, options);
2054
+ const resolved = await resolveActionSecrets(selectedInput, this.actionSecretStore, this.actionSecrets);
2055
+ const plan = this.planResolvedAction(entry, resolved, options);
1894
2056
  return {
1895
2057
  action: entry,
1896
2058
  example,
1897
2059
  input: selectedInput,
1898
2060
  inputSource: input2 ? "provided" : example ? "example" : "empty",
1899
2061
  missingFields: [...new Set(
1900
- 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)
1901
2063
  )],
1902
2064
  plan
1903
2065
  };
@@ -1927,7 +2089,7 @@ var TopoloClient = class _TopoloClient {
1927
2089
  confirmationRequired,
1928
2090
  confirmationProvided: options.confirm === true,
1929
2091
  executable: validation.valid && agentAccess !== "off" && (!confirmationRequired || options.confirm === true),
1930
- request,
2092
+ request: maskActionSecretValue(request, this.actionSecrets),
1931
2093
  verification: stringArray(entry.agentContract?.verify),
1932
2094
  rollbackActionId: pickActionString(entry.agentContract?.rollbackActionId),
1933
2095
  nextActionIds: stringArray(entry.agentContract?.nextActionIds)
@@ -1957,11 +2119,12 @@ var TopoloClient = class _TopoloClient {
1957
2119
  `Topolo action "${entry.actionId}" requires { confirm: true } before invocation.`
1958
2120
  );
1959
2121
  }
2122
+ input2 = await resolveActionSecrets(input2, this.actionSecretStore, this.actionSecrets);
1960
2123
  const validation = validateTopoloActionInput(entry.inputSchema, input2);
1961
2124
  if (!validation.valid) {
1962
2125
  throw new TopoloSdkError(
1963
2126
  "invalid_action_input",
1964
- `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("; ")}`
1965
2128
  );
1966
2129
  }
1967
2130
  const resourceIssue = validateActionResource(entry, options.resource);
@@ -1987,22 +2150,31 @@ var TopoloClient = class _TopoloClient {
1987
2150
  }
1988
2151
  }
1989
2152
  const service = entry.appSlug || entry.appId;
1990
- const result = await this.request({
1991
- service,
1992
- ...entry.apiBaseUrl ? { baseUrl: entry.apiBaseUrl } : {},
1993
- method: entry.method,
1994
- path: mapped.path,
1995
- ...mapped.query ? { query: mapped.query } : {},
1996
- ...mapped.body !== void 0 ? { body: mapped.body } : {},
1997
- ...options.resource ? { resource: options.resource } : {},
1998
- ...resolvedActorEnvelope ? { actorEnvelope: resolvedActorEnvelope } : {},
1999
- headers: {
2000
- [TOPOLO_ACTION_ID_HEADER]: entry.actionId,
2001
- [TOPOLO_ACTION_NAME_HEADER]: entry.name
2002
- },
2003
- confirm: options.confirm === true || entry.readOnly && !entry.destructive && !entry.requiresConfirmation
2004
- });
2005
- return preserveSensitiveResult ? result : redactActionResult(result);
2153
+ let result;
2154
+ try {
2155
+ result = await this.request({
2156
+ service,
2157
+ ...entry.apiBaseUrl ? { baseUrl: entry.apiBaseUrl } : {},
2158
+ method: entry.method,
2159
+ path: mapped.path,
2160
+ ...mapped.query ? { query: mapped.query } : {},
2161
+ ...mapped.body !== void 0 ? { body: mapped.body } : {},
2162
+ ...options.resource ? { resource: options.resource } : {},
2163
+ ...resolvedActorEnvelope ? { actorEnvelope: resolvedActorEnvelope } : {},
2164
+ headers: {
2165
+ [TOPOLO_ACTION_ID_HEADER]: entry.actionId,
2166
+ [TOPOLO_ACTION_NAME_HEADER]: entry.name
2167
+ },
2168
+ confirm: options.confirm === true || entry.readOnly && !entry.destructive && !entry.requiresConfirmation
2169
+ });
2170
+ } catch (error) {
2171
+ throw maskActionSecretError(error, this.actionSecrets);
2172
+ }
2173
+ return preserveSensitiveResult ? result : await protectActionResult(result, this.actionSecretStore);
2174
+ }
2175
+ async releaseActionSecret(reference) {
2176
+ if (!this.actionSecretStore) throw new TopoloSdkError("action_secret_store_required", "Protected action references require a secret store.");
2177
+ await this.actionSecretStore.delete(reference);
2006
2178
  }
2007
2179
  async buildVerifiedAgentActorEnvelope(actionId, input2, resource) {
2008
2180
  if (!this.actorContext) return void 0;
@@ -2149,7 +2321,7 @@ function resolveConfiguredAppId(slug, appIds) {
2149
2321
  return null;
2150
2322
  }
2151
2323
  function normalizeApiKeyResourceMetadata(input2) {
2152
- if (!isRecord3(input2)) return null;
2324
+ if (!isRecord4(input2)) return null;
2153
2325
  const resourceType = readString(input2.resourceType ?? input2.resource_type);
2154
2326
  const label = readString(input2.label);
2155
2327
  const emptyLabel = readString(input2.emptyLabel ?? input2.empty_label);
@@ -2183,7 +2355,7 @@ function normalizeApiKeyResourceMetadataList(input2) {
2183
2355
  return resources;
2184
2356
  }
2185
2357
  function normalizeBindableResource(input2) {
2186
- if (!isRecord3(input2)) return null;
2358
+ if (!isRecord4(input2)) return null;
2187
2359
  const resourceType = readString(input2.resourceType ?? input2.resource_type);
2188
2360
  const resourceId = readString(input2.resourceId ?? input2.resource_id);
2189
2361
  if (!resourceType || !resourceId) return null;
@@ -2194,13 +2366,13 @@ function normalizeBindableResource(input2) {
2194
2366
  slug: readOptionalString(input2.slug),
2195
2367
  status: readOptionalString(input2.status),
2196
2368
  organizationId: readOptionalString(input2.organizationId ?? input2.organization_id),
2197
- metadata: isRecord3(input2.metadata) ? input2.metadata : {}
2369
+ metadata: isRecord4(input2.metadata) ? input2.metadata : {}
2198
2370
  };
2199
2371
  }
2200
2372
  function normalizeResourceBindings(input2) {
2201
2373
  if (!Array.isArray(input2)) return [];
2202
2374
  return input2.flatMap((binding) => {
2203
- if (!isRecord3(binding)) return [];
2375
+ if (!isRecord4(binding)) return [];
2204
2376
  const resourceType = readString(binding.resourceType ?? binding.resource_type);
2205
2377
  const resourceId = readString(binding.resourceId ?? binding.resource_id);
2206
2378
  return resourceType && resourceId ? [{ resourceType, resourceId }] : [];
@@ -2213,30 +2385,9 @@ function readOptionalString(value) {
2213
2385
  const normalized = readString(value);
2214
2386
  return normalized || null;
2215
2387
  }
2216
- function isRecord3(value) {
2388
+ function isRecord4(value) {
2217
2389
  return typeof value === "object" && value !== null && !Array.isArray(value);
2218
2390
  }
2219
- function redactActionResult(value) {
2220
- if (Array.isArray(value)) return value.map(redactActionResult);
2221
- if (!isRecord3(value)) return value;
2222
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [
2223
- key,
2224
- isSensitiveActionResultKey(key, item) ? "[REDACTED]" : redactActionResult(item)
2225
- ]));
2226
- }
2227
- function isSensitiveActionResultKey(key, value) {
2228
- const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
2229
- if (normalized === "capability") return isCredentialShaped(value);
2230
- return ["authorization", "cookie", "setcookie", "proxyauthorization"].includes(normalized) || /(token|secret|password|privatekey|apikey)$/.test(normalized);
2231
- }
2232
- var PLAIN_ENUM_VALUE = /^[a-z][a-z0-9_-]{0,31}$/;
2233
- function isCredentialShaped(value) {
2234
- if (typeof value !== "string") return value !== null && value !== void 0 && typeof value !== "boolean" && typeof value !== "number";
2235
- const trimmed = value.trim();
2236
- if (!trimmed) return false;
2237
- if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) return true;
2238
- return !PLAIN_ENUM_VALUE.test(trimmed);
2239
- }
2240
2391
  function resolveConfiguredServiceUrl(slug, serviceUrls) {
2241
2392
  for (const serviceKey of serviceKeyCandidates2(slug)) {
2242
2393
  const configured = serviceUrls?.[serviceKey];
@@ -2306,14 +2457,14 @@ function catalogSearchQuery(options) {
2306
2457
  return query;
2307
2458
  }
2308
2459
  function normalizeCatalogPage(value) {
2309
- if (!isRecord3(value)) return { nextCursor: null, hasMore: false };
2460
+ if (!isRecord4(value)) return { nextCursor: null, hasMore: false };
2310
2461
  return {
2311
2462
  nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
2312
2463
  hasMore: value.hasMore === true
2313
2464
  };
2314
2465
  }
2315
2466
  function normalizeServiceCatalogEntry(item) {
2316
- if (!isRecord3(item)) return null;
2467
+ if (!isRecord4(item)) return null;
2317
2468
  const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
2318
2469
  if (!appId) return null;
2319
2470
  const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
@@ -3342,11 +3493,13 @@ function createTopolo(options) {
3342
3493
  };
3343
3494
  }
3344
3495
 
3345
- // node_modules/.pnpm/@topolo+sdk@0.21.2/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
3346
3497
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, chmod as chmod2, rm as rm2, stat } from "fs/promises";
3347
3498
  import { existsSync as existsSync2 } from "fs";
3348
3499
  import { homedir as homedir2, platform } from "os";
3349
- import { dirname as dirname2, join as join2 } from "path";
3500
+ import { dirname as dirname2, join as join3 } from "path";
3501
+ import { createHash, randomUUID } from "crypto";
3502
+ import { join as join2 } from "path";
3350
3503
  import { spawnSync } from "child_process";
3351
3504
  import { mkdir, readFile, readdir, rename, rm, writeFile, chmod } from "fs/promises";
3352
3505
  import { existsSync } from "fs";
@@ -3407,6 +3560,9 @@ var PUBLIC_ERROR_CODES2 = /* @__PURE__ */ new Set([
3407
3560
  "unknown_service"
3408
3561
  ]);
3409
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;
3410
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;
3411
3567
  var CLOUDFLARE_RAY_ID_PATTERN2 = /^[0-9a-f]{16}-[A-Z]{3}$/;
3412
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}|\*)$/;
@@ -3431,6 +3587,24 @@ function canonicalTopoloErrorCode2(value) {
3431
3587
  if (!ERROR_CODE_PATTERN2.test(candidate) || !PUBLIC_ERROR_CODES2.has(candidate)) return null;
3432
3588
  return candidate;
3433
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
+ }
3434
3608
  function canonicalTopoloRequestId2(value) {
3435
3609
  if (typeof value !== "string") return void 0;
3436
3610
  const candidate = value.trim();
@@ -3455,7 +3629,7 @@ var TopoloAuthError2 = class extends TopoloSdkError2 {
3455
3629
  retryAfter;
3456
3630
  details;
3457
3631
  constructor(message2, code = "auth_error", options = {}) {
3458
- super(canonicalTopoloErrorCode2(code) ?? "auth_error", message2);
3632
+ super(canonicalTopoloServiceErrorCode2(code) ?? "auth_error", message2);
3459
3633
  this.name = "TopoloAuthError";
3460
3634
  this.status = options.status ?? 401;
3461
3635
  this.body = options.body;
@@ -3471,14 +3645,14 @@ var TopoloAuthError2 = class extends TopoloSdkError2 {
3471
3645
  function extractTopoloServiceErrorCode2(body) {
3472
3646
  if (!body || typeof body !== "object" || Array.isArray(body)) return null;
3473
3647
  const record2 = body;
3474
- const topLevel = canonicalTopoloErrorCode2(record2.code);
3648
+ const topLevel = canonicalTopoloServiceErrorCode2(record2.code);
3475
3649
  if (topLevel) return topLevel;
3476
3650
  const nested = record2.error;
3477
- const nestedString = canonicalTopoloErrorCode2(nested);
3651
+ const nestedString = canonicalTopoloServiceErrorCode2(nested);
3478
3652
  if (nestedString) return nestedString;
3479
3653
  if (nested && typeof nested === "object" && !Array.isArray(nested)) {
3480
3654
  const code = nested.code;
3481
- const nestedCode = canonicalTopoloErrorCode2(code);
3655
+ const nestedCode = canonicalTopoloServiceErrorCode2(code);
3482
3656
  if (nestedCode) return nestedCode;
3483
3657
  }
3484
3658
  return null;
@@ -3496,10 +3670,7 @@ function extractTopoloServiceErrorDetails2(body) {
3496
3670
  if (candidate === void 0) candidate = nestedRecord.details ?? nestedRecord;
3497
3671
  }
3498
3672
  if (candidate === void 0) candidate = record2;
3499
- if (Array.isArray(candidate)) {
3500
- const paths = canonicalTopoloValidationPaths2(candidate);
3501
- return paths.length ? { paths } : void 0;
3502
- }
3673
+ if (Array.isArray(candidate)) return validationIssueDetails2(canonicalTopoloValidationIssues2(candidate)) ?? void 0;
3503
3674
  if (!candidate || typeof candidate !== "object") return void 0;
3504
3675
  const source = candidate;
3505
3676
  const safe = {};
@@ -3541,18 +3712,59 @@ function extractTopoloServiceErrorDetails2(body) {
3541
3712
  if (paths.length) safe[key] = paths;
3542
3713
  }
3543
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));
3544
3720
  return Object.keys(safe).length ? safe : void 0;
3545
3721
  }
3546
- function canonicalTopoloValidationPaths2(issues) {
3722
+ function validationIssueDetails2(issues) {
3723
+ if (!issues.length) return null;
3547
3724
  const paths = [];
3548
- for (const issue of issues) {
3549
- if (!issue || typeof issue !== "object" || Array.isArray(issue)) continue;
3550
- 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;
3551
3740
  const path = canonicalTopoloValidationPath2(record2.field) ?? canonicalTopoloValidationPath2(record2.path);
3552
- if (path && !paths.includes(path)) paths.push(path);
3553
- 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;
3554
3745
  }
3555
- return paths;
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
+ }
3766
+ }
3767
+ return issues;
3556
3768
  }
3557
3769
  function canonicalTopoloValidationPath2(value) {
3558
3770
  if (typeof value === "string") {
@@ -3698,67 +3910,7 @@ var TopoloOAuth2 = class {
3698
3910
  return parsed;
3699
3911
  }
3700
3912
  };
3701
- var TOPOLO_RUNTIME_ENVIRONMENTS = ["production", "staging", "development", "local"];
3702
- var TOPOLO_ENVIRONMENT_SERVICE_URLS = {
3703
- production: {
3704
- auth: "https://auth.topolo.app",
3705
- developers: "https://developers.topolo.app"
3706
- },
3707
- staging: {
3708
- auth: "https://auth.stg.topolo.us",
3709
- developers: "https://developers.stg.topolo.us"
3710
- },
3711
- development: {
3712
- auth: "https://auth.topolo.dev",
3713
- developers: "https://developers.topolo.dev"
3714
- },
3715
- local: {}
3716
- };
3717
- function hostnameHasSuffix2(hostname, suffix) {
3718
- return hostname === suffix || hostname.endsWith(`.${suffix}`);
3719
- }
3720
- function workersDevAccountMatches2(hostname, account) {
3721
- if (!hostname.endsWith(".workers.dev")) return false;
3722
- const labels = hostname.slice(0, -".workers.dev".length).split(".");
3723
- return labels.length >= 2 && labels.at(-1) === account;
3724
- }
3725
- function originMatchesEnvironment2(url, environment) {
3726
- const hostname = url.hostname.toLowerCase();
3727
- if (environment === "local") {
3728
- return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "[::1]";
3729
- }
3730
- if (url.protocol !== "https:") return false;
3731
- if (environment === "production") {
3732
- return hostnameHasSuffix2(hostname, "topolo.app") || hostnameHasSuffix2(hostname, "topolo.io") || hostname === "api.ol0.me" || workersDevAccountMatches2(hostname, "topolo");
3733
- }
3734
- if (environment === "staging") {
3735
- return hostnameHasSuffix2(hostname, "stg.topolo.us") || workersDevAccountMatches2(hostname, "topolo-staging");
3736
- }
3737
- return hostnameHasSuffix2(hostname, "topolo.dev") || workersDevAccountMatches2(hostname, "topolo-development");
3738
- }
3739
- function assertTopoloServiceUrlEnvironment2(service, value, environment) {
3740
- let url;
3741
- try {
3742
- url = new URL(value);
3743
- } catch {
3744
- throw new TopoloSdkError2("invalid_service_url", `Invalid URL configured for Topolo service "${service}".`);
3745
- }
3746
- if (url.protocol !== "https:" && url.protocol !== "http:" || url.username || url.password || !originMatchesEnvironment2(url, environment)) {
3747
- throw new TopoloSdkError2(
3748
- "environment_mismatch",
3749
- `Refusing Topolo service "${service}": ${url.origin} does not belong to ${environment}.`
3750
- );
3751
- }
3752
- return url.origin;
3753
- }
3754
- function topoloServiceOrigins2(serviceUrls, environment) {
3755
- return Object.fromEntries(
3756
- Object.entries(serviceUrls ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([service, value]) => [
3757
- service,
3758
- assertTopoloServiceUrlEnvironment2(service, value, environment)
3759
- ])
3760
- );
3761
- }
3913
+ var ACTION_SECRET_PREFIX2 = "topolo-secret:";
3762
3914
  var KEYCHAIN_SERVICE = "topolo-external-agent-installation";
3763
3915
  var MACOS_KEYCHAIN_CHUNK_SIZE = 96;
3764
3916
  var MACOS_KEYCHAIN_CHUNK_PREFIX = "topolo-keychain-chunks:v1:";
@@ -3801,6 +3953,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3801
3953
  const platform22 = options.platform ?? hostPlatform();
3802
3954
  const spawn3 = options.spawn ?? ((command, args, spawnOptions) => spawnSync(command, args, spawnOptions));
3803
3955
  const root = options.root ?? installationStorePath();
3956
+ const service = options.service ?? KEYCHAIN_SERVICE;
3804
3957
  if (platform22 === "darwin") {
3805
3958
  const write = (account, value) => runOrThrow(spawn3("/usr/bin/expect", ["-c", MACOS_KEYCHAIN_WRITE_SCRIPT], {
3806
3959
  input: `${value}
@@ -3809,7 +3962,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3809
3962
  env: {
3810
3963
  ...process.env,
3811
3964
  TOPOLO_KEYCHAIN_ACCOUNT: account,
3812
- TOPOLO_KEYCHAIN_SERVICE: KEYCHAIN_SERVICE
3965
+ TOPOLO_KEYCHAIN_SERVICE: service
3813
3966
  }
3814
3967
  }), "macOS Keychain write");
3815
3968
  const read = (account) => {
@@ -3818,7 +3971,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3818
3971
  "-a",
3819
3972
  account,
3820
3973
  "-s",
3821
- KEYCHAIN_SERVICE,
3974
+ service,
3822
3975
  "-w"
3823
3976
  ], { encoding: "utf8" });
3824
3977
  if (result.status === 44) return null;
@@ -3830,7 +3983,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3830
3983
  "-a",
3831
3984
  account,
3832
3985
  "-s",
3833
- KEYCHAIN_SERVICE
3986
+ service
3834
3987
  ], { encoding: "utf8" });
3835
3988
  if (result.status !== 44) runOrThrow(result, "macOS Keychain delete");
3836
3989
  };
@@ -3877,9 +4030,9 @@ function createPlatformInstallationSecretBackend(options = {}) {
3877
4030
  async set(account, value) {
3878
4031
  runOrThrow(spawn3("secret-tool", [
3879
4032
  "store",
3880
- "--label=Topolo external agent installation",
4033
+ "--label=Topolo protected credential",
3881
4034
  "service",
3882
- KEYCHAIN_SERVICE,
4035
+ service,
3883
4036
  "account",
3884
4037
  account
3885
4038
  ], { input: value, encoding: "utf8" }), "Linux Secret Service write");
@@ -3888,7 +4041,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3888
4041
  const result = spawn3("secret-tool", [
3889
4042
  "lookup",
3890
4043
  "service",
3891
- KEYCHAIN_SERVICE,
4044
+ service,
3892
4045
  "account",
3893
4046
  account
3894
4047
  ], { encoding: "utf8" });
@@ -3899,7 +4052,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3899
4052
  const result = spawn3("secret-tool", [
3900
4053
  "clear",
3901
4054
  "service",
3902
- KEYCHAIN_SERVICE,
4055
+ service,
3903
4056
  "account",
3904
4057
  account
3905
4058
  ], { encoding: "utf8" });
@@ -3961,6 +4114,114 @@ async function deleteExternalAgentInstallation(environment, id, backend = create
3961
4114
  await backend.delete(accountName(environment, id));
3962
4115
  await rm(installationPath(environment, id), { force: true });
3963
4116
  }
4117
+ function createProtectedActionSecretStore(options) {
4118
+ const backend = options.backend ?? createPlatformInstallationSecretBackend({
4119
+ service: "topolo-action-handoff",
4120
+ root: join2(installationStorePath(), "action-handoff")
4121
+ });
4122
+ const now = options.now ?? Date.now;
4123
+ async function account(reference) {
4124
+ if (!/^topolo-secret:[0-9a-f-]{36}$/.test(reference)) throw new TopoloSdkError2("invalid_action_secret_reference", "Invalid protected action reference.");
4125
+ const identity = await options.identity();
4126
+ if (!identity.user.id) throw new TopoloSdkError2("action_secret_identity_required", "A verified identity is required for protected action handoff.");
4127
+ if (identity.kind === "api_key" && !identity.apiKey?.id) throw new TopoloSdkError2("action_secret_identity_required", "A verified API key identity is required for protected action handoff.");
4128
+ const scope = createHash("sha256").update(JSON.stringify([
4129
+ options.environment,
4130
+ identity.user.principalId ?? identity.user.id,
4131
+ identity.organization?.id ?? null,
4132
+ identity.apiKey?.id ?? null
4133
+ ])).digest("hex");
4134
+ return `${scope}-${reference.slice(ACTION_SECRET_PREFIX2.length)}`;
4135
+ }
4136
+ return {
4137
+ async save(value) {
4138
+ const reference = `${ACTION_SECRET_PREFIX2}${randomUUID()}`;
4139
+ await backend.set(await account(reference), JSON.stringify({ value, expiresAt: now() + 30 * 60 * 1e3 }));
4140
+ return reference;
4141
+ },
4142
+ async load(reference) {
4143
+ const key = await account(reference);
4144
+ const encoded = await backend.get(key);
4145
+ if (!encoded) throw new TopoloSdkError2("action_secret_unavailable", "Protected action reference is missing or belongs to another identity or environment.");
4146
+ let record2;
4147
+ try {
4148
+ record2 = JSON.parse(encoded);
4149
+ } catch {
4150
+ throw new TopoloSdkError2("action_secret_invalid", "Protected action reference is corrupt.");
4151
+ }
4152
+ if (!Number.isFinite(record2?.expiresAt) || !Object.hasOwn(record2, "value")) throw new TopoloSdkError2("action_secret_invalid", "Protected action reference is corrupt.");
4153
+ if (record2.expiresAt <= now()) {
4154
+ await backend.delete(key);
4155
+ throw new TopoloSdkError2("action_secret_expired", "Protected action reference expired. Repeat the originating action if its contract permits.");
4156
+ }
4157
+ return record2.value;
4158
+ },
4159
+ async delete(reference) {
4160
+ await backend.delete(await account(reference));
4161
+ }
4162
+ };
4163
+ }
4164
+ var TOPOLO_RUNTIME_ENVIRONMENTS = ["production", "staging", "development", "local"];
4165
+ var TOPOLO_ENVIRONMENT_SERVICE_URLS = {
4166
+ production: {
4167
+ auth: "https://auth.topolo.app",
4168
+ developers: "https://developers.topolo.app"
4169
+ },
4170
+ staging: {
4171
+ auth: "https://auth.stg.topolo.us",
4172
+ developers: "https://developers.stg.topolo.us"
4173
+ },
4174
+ development: {
4175
+ auth: "https://auth.topolo.dev",
4176
+ developers: "https://developers.topolo.dev"
4177
+ },
4178
+ local: {}
4179
+ };
4180
+ function hostnameHasSuffix2(hostname, suffix) {
4181
+ return hostname === suffix || hostname.endsWith(`.${suffix}`);
4182
+ }
4183
+ function workersDevAccountMatches2(hostname, account) {
4184
+ if (!hostname.endsWith(".workers.dev")) return false;
4185
+ const labels = hostname.slice(0, -".workers.dev".length).split(".");
4186
+ return labels.length >= 2 && labels.at(-1) === account;
4187
+ }
4188
+ function originMatchesEnvironment2(url, environment) {
4189
+ const hostname = url.hostname.toLowerCase();
4190
+ if (environment === "local") {
4191
+ return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "[::1]";
4192
+ }
4193
+ if (url.protocol !== "https:") return false;
4194
+ if (environment === "production") {
4195
+ return hostnameHasSuffix2(hostname, "topolo.app") || hostnameHasSuffix2(hostname, "topolo.io") || hostname === "api.ol0.me" || workersDevAccountMatches2(hostname, "topolo");
4196
+ }
4197
+ if (environment === "staging") {
4198
+ return hostnameHasSuffix2(hostname, "stg.topolo.us") || workersDevAccountMatches2(hostname, "topolo-staging");
4199
+ }
4200
+ return hostnameHasSuffix2(hostname, "topolo.dev") || workersDevAccountMatches2(hostname, "topolo-development");
4201
+ }
4202
+ function assertTopoloServiceUrlEnvironment2(service, value, environment) {
4203
+ let url;
4204
+ try {
4205
+ url = new URL(value);
4206
+ } catch {
4207
+ throw new TopoloSdkError2("invalid_service_url", `Invalid URL configured for Topolo service "${service}".`);
4208
+ }
4209
+ if (url.protocol !== "https:" && url.protocol !== "http:" || url.username || url.password || !originMatchesEnvironment2(url, environment)) {
4210
+ throw new TopoloSdkError2(
4211
+ "environment_mismatch",
4212
+ `Refusing Topolo service "${service}": ${url.origin} does not belong to ${environment}.`
4213
+ );
4214
+ }
4215
+ return url.origin;
4216
+ }
4217
+ function topoloServiceOrigins2(serviceUrls, environment) {
4218
+ return Object.fromEntries(
4219
+ Object.entries(serviceUrls ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([service, value]) => [
4220
+ service,
4221
+ assertTopoloServiceUrlEnvironment2(service, value, environment)
4222
+ ])
4223
+ );
4224
+ }
3964
4225
  var TOPOLO_ENVIRONMENTS = TOPOLO_RUNTIME_ENVIRONMENTS;
3965
4226
  function contextKey(contextType, organizationId) {
3966
4227
  return contextType === "organization" && organizationId ? `org:${organizationId}` : "personal";
@@ -3969,8 +4230,8 @@ var ENVIRONMENT_DEFAULT_SERVICE_URLS = TOPOLO_ENVIRONMENT_SERVICE_URLS;
3969
4230
  function configPath() {
3970
4231
  const override = process.env.TOPOLO_CONFIG_PATH;
3971
4232
  if (override) return override;
3972
- const base = process.env.XDG_CONFIG_HOME || (platform() === "win32" ? join2(homedir2(), "AppData", "Roaming") : join2(homedir2(), ".config"));
3973
- return join2(base, "topolo", "config.json");
4233
+ const base = process.env.XDG_CONFIG_HOME || (platform() === "win32" ? join3(homedir2(), "AppData", "Roaming") : join3(homedir2(), ".config"));
4234
+ return join3(base, "topolo", "config.json");
3974
4235
  }
3975
4236
  async function readConfig() {
3976
4237
  const path = configPath();
@@ -4337,6 +4598,10 @@ function fail2(err, mode) {
4337
4598
  `);
4338
4599
  if (payload.status) process.stderr.write(`HTTP: ${payload.status}
4339
4600
  `);
4601
+ for (const issue3 of validationIssues(payload.details)) {
4602
+ process.stderr.write(`Issue: ${[issue3.path, issue3.message].filter(Boolean).join(": ")}
4603
+ `);
4604
+ }
4340
4605
  if (payload.requestId) process.stderr.write(`Request: ${payload.requestId}
4341
4606
  `);
4342
4607
  if (payload.hint) process.stderr.write(`Hint: ${payload.hint}
@@ -4344,6 +4609,10 @@ function fail2(err, mode) {
4344
4609
  }
4345
4610
  process.exit(payload.exitCode);
4346
4611
  }
4612
+ function validationIssues(details) {
4613
+ const issues = details?.issues;
4614
+ return Array.isArray(issues) ? issues : [];
4615
+ }
4347
4616
  function normalizeError(err) {
4348
4617
  if (err instanceof Error) {
4349
4618
  const code = err.code;
@@ -4361,9 +4630,9 @@ function normalizeError(err) {
4361
4630
  path: err.path,
4362
4631
  requestId: err.requestId,
4363
4632
  retryAfter: err.retryAfter,
4364
- details: extractTopoloServiceErrorDetails(
4365
- suppliedDetails === void 0 ? body : { details: suppliedDetails }
4366
- ),
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 }),
4367
4636
  hint: topoloErrorHint({ code, body }) ?? (code ? fallbackHintMap[code] : void 0),
4368
4637
  exitCode: status === 401 || code === "auth_error" || code === "confirmation_required" ? 2 : status === 403 || code === "permission_denied" ? 3 : code === "immutable_drift" ? 4 : 1
4369
4638
  };
@@ -4373,14 +4642,14 @@ function normalizeError(err) {
4373
4642
 
4374
4643
  // src/version.ts
4375
4644
  import { readFileSync } from "fs";
4376
- import { dirname as dirname3, join as join3 } from "path";
4645
+ import { dirname as dirname3, join as join4 } from "path";
4377
4646
  import { fileURLToPath } from "url";
4378
4647
  function readCliVersion() {
4379
4648
  try {
4380
4649
  const here = dirname3(fileURLToPath(import.meta.url));
4381
4650
  const candidates = [
4382
- join3(here, "package.json"),
4383
- join3(here, "..", "package.json")
4651
+ join4(here, "package.json"),
4652
+ join4(here, "..", "package.json")
4384
4653
  ];
4385
4654
  for (const path of candidates) {
4386
4655
  try {
@@ -4667,9 +4936,10 @@ async function buildClient(options) {
4667
4936
  if (!credential) return null;
4668
4937
  const config = await readConfig();
4669
4938
  const profile = resolvedProfileConfig(config, environment);
4670
- return createTopolo({
4939
+ const topolo = createTopolo({
4671
4940
  credential,
4672
4941
  environment,
4942
+ actionSecretStore: createProtectedActionSecretStore({ environment, identity: () => topolo.client.introspect() }),
4673
4943
  actorContext: { channel: "cli" },
4674
4944
  agent: {
4675
4945
  clientName: "topolo-cli",
@@ -4683,6 +4953,7 @@ async function buildClient(options) {
4683
4953
  } : {},
4684
4954
  ...isDebugEnabled() ? { debug: stderrDebugLogger } : {}
4685
4955
  });
4956
+ return topolo;
4686
4957
  }
4687
4958
  function isDebugEnabled() {
4688
4959
  const v = process.env.TOPOLO_DEBUG;
@@ -4706,7 +4977,7 @@ function stderrDebugLogger(event) {
4706
4977
  return;
4707
4978
  }
4708
4979
  const statusPart = event.status ? ` ${event.status}` : "";
4709
- const errorCode = canonicalTopoloErrorCode(event.error) ?? "request_failed";
4980
+ const errorCode = canonicalTopoloServiceErrorCode(event.error) ?? "request_failed";
4710
4981
  process.stderr.write(
4711
4982
  `[topolo] \u2717${statusPart} ${event.method} ${event.service} ${event.path} (${event.durationMs}ms, req=${requestId}, code=${errorCode})
4712
4983
  `
@@ -4878,6 +5149,18 @@ function registerActionsCommand(program2) {
4878
5149
  fail2(err, mode);
4879
5150
  }
4880
5151
  });
5152
+ actions.command("release-secret <reference>").description("Delete a local protected action reference for the current verified identity and environment").option("--confirm", "Acknowledge local reference deletion; does not revoke the upstream credential").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (reference, opts, command) => {
5153
+ const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
5154
+ try {
5155
+ if (opts.confirm !== true) throw new Error("Releasing a protected action reference requires --confirm.");
5156
+ const topolo = await buildClient();
5157
+ if (!topolo) throw new Error("No credential configured. Run `topolo auth login`.");
5158
+ await topolo.client.releaseActionSecret(reference);
5159
+ emit({ released: true }, mode, () => "Protected action reference released.");
5160
+ } catch (error) {
5161
+ fail2(error, mode);
5162
+ }
5163
+ });
4881
5164
  actions.command("get <action>").description("Describe one Topolo action by exact action id").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
4882
5165
  const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
4883
5166
  try {
@@ -4948,7 +5231,7 @@ Approval required. ${opened ? "Opening" : "Open"} ${approval.verificationUrl}` :
4948
5231
  const topolo = await requiredClient();
4949
5232
  const result = await topolo.client.validateActionInput(action, parseJsonObject(opts.data || "{}"));
4950
5233
  const output = detailRequested(opts.detail, command) ? result : compactActionValidation(result.action.actionId, result.validation);
4951
- 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"));
4952
5235
  } catch (err) {
4953
5236
  fail2(err, mode);
4954
5237
  }
@@ -5228,7 +5511,7 @@ function formatActionPlan(plan) {
5228
5511
  ];
5229
5512
  if (plan.request) lines.push(`Request: ${plan.request.method} ${plan.request.apiBaseUrl || plan.request.service}${plan.request.path}`);
5230
5513
  if (plan.request?.resource) lines.push(`Resource: ${plan.request.resource.resourceType}:${plan.request.resource.resourceId}`);
5231
- 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}`));
5232
5515
  if (plan.verification.length) lines.push("", "Verify:", ...plan.verification.map((step) => `- ${step}`));
5233
5516
  if (plan.rollbackActionId) lines.push("", `Rollback: ${plan.rollbackActionId}`);
5234
5517
  return lines.join("\n");
@@ -5523,7 +5806,7 @@ function normalizeList(values, fallback = []) {
5523
5806
 
5524
5807
  // src/commands/integrations.ts
5525
5808
  import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "fs/promises";
5526
- import { join as join4, relative } from "path";
5809
+ import { join as join5, relative } from "path";
5527
5810
  function registerIntegrationsCommand(program2) {
5528
5811
  const integrations = program2.command("integrations").description("Generate and validate Topolo integration handoffs.");
5529
5812
  integrations.command("prompt").description("Print an agent prompt for adding a Topolo integration.").argument("<integration>", "Integration prompt id, currently login-with-topolo").option("--framework <framework>", "auto, authjs, express, or generic", "auto").option("--json", "Emit JSON output").action((integration, options) => {
@@ -5666,7 +5949,7 @@ function detectFramework(packageJson, haystack) {
5666
5949
  }
5667
5950
  async function readPackageJson(repoRoot) {
5668
5951
  try {
5669
- return JSON.parse(await readFile3(join4(repoRoot, "package.json"), "utf8"));
5952
+ return JSON.parse(await readFile3(join5(repoRoot, "package.json"), "utf8"));
5670
5953
  } catch {
5671
5954
  return null;
5672
5955
  }
@@ -5678,7 +5961,7 @@ async function collectFiles(repoRoot) {
5678
5961
  if (result.length > 2e3) return;
5679
5962
  const entries = await readdir2(dir, { withFileTypes: true });
5680
5963
  for (const entry of entries) {
5681
- const fullPath = join4(dir, entry.name);
5964
+ const fullPath = join5(dir, entry.name);
5682
5965
  if (entry.isDirectory()) {
5683
5966
  if ([".git", "node_modules", "dist", "build", ".next", "coverage"].includes(entry.name)) continue;
5684
5967
  await walk(fullPath);
@@ -5709,7 +5992,7 @@ import { spawnSync as spawnSync3 } from "child_process";
5709
5992
  import { existsSync as existsSync5 } from "fs";
5710
5993
  import { readFile as readFile6, rm as rm3 } from "fs/promises";
5711
5994
  import { homedir as homedir4 } from "os";
5712
- import { dirname as dirname5, join as join6, resolve } from "path";
5995
+ import { dirname as dirname5, join as join7, resolve } from "path";
5713
5996
  import { fileURLToPath as fileURLToPath2 } from "url";
5714
5997
  import { createRequire } from "module";
5715
5998
 
@@ -5718,7 +6001,7 @@ import { spawnSync as spawnSync2 } from "child_process";
5718
6001
  import { existsSync as existsSync3 } from "fs";
5719
6002
  import { readFile as readFile4 } from "fs/promises";
5720
6003
  import { homedir as homedir3 } from "os";
5721
- import { isAbsolute, join as join5 } from "path";
6004
+ import { isAbsolute, join as join6 } from "path";
5722
6005
  var SUPPORTED_AGENT_IDS = [
5723
6006
  "claude",
5724
6007
  "claude-desktop",
@@ -5776,23 +6059,23 @@ function stableNodeCommand() {
5776
6059
  function agentSkillPath(agent, homeDir = homedir3(), projectDir) {
5777
6060
  switch (agent) {
5778
6061
  case "claude":
5779
- return projectDir ? join5(projectDir, ".claude", "skills", "topolo", "SKILL.md") : join5(homeDir, ".claude", "skills", "topolo", "SKILL.md");
6062
+ return projectDir ? join6(projectDir, ".claude", "skills", "topolo", "SKILL.md") : join6(homeDir, ".claude", "skills", "topolo", "SKILL.md");
5780
6063
  case "claude-desktop":
5781
- return join5(homeDir, ".claude", "skills", "topolo", "SKILL.md");
6064
+ return join6(homeDir, ".claude", "skills", "topolo", "SKILL.md");
5782
6065
  case "codex":
5783
- return join5(homeDir, ".codex", "skills", "topolo", "SKILL.md");
6066
+ return join6(homeDir, ".codex", "skills", "topolo", "SKILL.md");
5784
6067
  case "cursor":
5785
- return join5(projectDir ?? homeDir, ".cursor", "rules", "topolo.mdc");
6068
+ return join6(projectDir ?? homeDir, ".cursor", "rules", "topolo.mdc");
5786
6069
  case "grok":
5787
- return join5(projectDir ?? homeDir, ".grok", "skills", "topolo", "SKILL.md");
6070
+ return join6(projectDir ?? homeDir, ".grok", "skills", "topolo", "SKILL.md");
5788
6071
  case "gemini":
5789
- return join5(projectDir ?? homeDir, ".gemini", "skills", "topolo", "SKILL.md");
6072
+ return join6(projectDir ?? homeDir, ".gemini", "skills", "topolo", "SKILL.md");
5790
6073
  case "copilot":
5791
- return projectDir ? join5(projectDir, ".github", "skills", "topolo", "SKILL.md") : join5(homeDir, ".copilot", "skills", "topolo", "SKILL.md");
6074
+ return projectDir ? join6(projectDir, ".github", "skills", "topolo", "SKILL.md") : join6(homeDir, ".copilot", "skills", "topolo", "SKILL.md");
5792
6075
  case "windsurf":
5793
- return projectDir ? join5(projectDir, ".windsurf", "rules", "topolo.md") : void 0;
6076
+ return projectDir ? join6(projectDir, ".windsurf", "rules", "topolo.md") : void 0;
5794
6077
  case "cline":
5795
- return join5(projectDir ?? homeDir, ".cline", "skills", "topolo", "SKILL.md");
6078
+ return join6(projectDir ?? homeDir, ".cline", "skills", "topolo", "SKILL.md");
5796
6079
  case "goose":
5797
6080
  case "amazon-q":
5798
6081
  return void 0;
@@ -5801,7 +6084,7 @@ function agentSkillPath(agent, homeDir = homedir3(), projectDir) {
5801
6084
  function agentConfigPaths(agent, homeDir = homedir3(), projectDir) {
5802
6085
  switch (agent) {
5803
6086
  case "claude":
5804
- return [{ path: join5(homeDir, ".claude.json"), format: "json", rootKey: "mcpServers" }];
6087
+ return [{ path: join6(homeDir, ".claude.json"), format: "json", rootKey: "mcpServers" }];
5805
6088
  case "claude-desktop":
5806
6089
  return [{
5807
6090
  path: claudeDesktopConfigPath(homeDir),
@@ -5809,31 +6092,31 @@ function agentConfigPaths(agent, homeDir = homedir3(), projectDir) {
5809
6092
  rootKey: "mcpServers"
5810
6093
  }];
5811
6094
  case "codex":
5812
- return [{ path: join5(homeDir, ".codex", "config.toml"), format: "toml" }];
6095
+ return [{ path: join6(homeDir, ".codex", "config.toml"), format: "toml" }];
5813
6096
  case "cursor":
5814
6097
  return [{
5815
- path: projectDir ? join5(projectDir, ".cursor", "mcp.json") : join5(homeDir, ".cursor", "mcp.json"),
6098
+ path: projectDir ? join6(projectDir, ".cursor", "mcp.json") : join6(homeDir, ".cursor", "mcp.json"),
5816
6099
  format: "json",
5817
6100
  rootKey: "mcpServers"
5818
6101
  }];
5819
6102
  case "grok":
5820
6103
  return [{
5821
- path: projectDir ? join5(projectDir, ".grok", "config.toml") : join5(homeDir, ".grok", "config.toml"),
6104
+ path: projectDir ? join6(projectDir, ".grok", "config.toml") : join6(homeDir, ".grok", "config.toml"),
5822
6105
  format: "toml"
5823
6106
  }];
5824
6107
  case "gemini":
5825
6108
  return [{
5826
- path: projectDir ? join5(projectDir, ".gemini", "settings.json") : join5(homeDir, ".gemini", "settings.json"),
6109
+ path: projectDir ? join6(projectDir, ".gemini", "settings.json") : join6(homeDir, ".gemini", "settings.json"),
5827
6110
  format: "json",
5828
6111
  rootKey: "mcpServers"
5829
6112
  }];
5830
6113
  case "copilot":
5831
6114
  return projectDir ? [
5832
- { path: join5(projectDir, ".mcp.json"), format: "json", rootKey: "mcpServers" },
5833
- { path: join5(projectDir, ".vscode", "mcp.json"), format: "json", rootKey: "servers" }
6115
+ { path: join6(projectDir, ".mcp.json"), format: "json", rootKey: "mcpServers" },
6116
+ { path: join6(projectDir, ".vscode", "mcp.json"), format: "json", rootKey: "servers" }
5834
6117
  ] : [
5835
6118
  {
5836
- path: join5(homeDir, ".copilot", "mcp-config.json"),
6119
+ path: join6(homeDir, ".copilot", "mcp-config.json"),
5837
6120
  format: "json",
5838
6121
  rootKey: "mcpServers"
5839
6122
  },
@@ -5841,24 +6124,24 @@ function agentConfigPaths(agent, homeDir = homedir3(), projectDir) {
5841
6124
  ];
5842
6125
  case "windsurf":
5843
6126
  return [{
5844
- path: join5(homeDir, ".codeium", "windsurf", "mcp_config.json"),
6127
+ path: join6(homeDir, ".codeium", "windsurf", "mcp_config.json"),
5845
6128
  format: "json",
5846
6129
  rootKey: "mcpServers"
5847
6130
  }];
5848
6131
  case "cline":
5849
6132
  return [{
5850
- path: projectDir ? join5(projectDir, ".cline", "mcp.json") : join5(homeDir, ".cline", "mcp.json"),
6133
+ path: projectDir ? join6(projectDir, ".cline", "mcp.json") : join6(homeDir, ".cline", "mcp.json"),
5851
6134
  format: "json",
5852
6135
  rootKey: "mcpServers"
5853
6136
  }];
5854
6137
  case "goose":
5855
- return [{ path: join5(homeDir, ".config", "goose", "config.yaml"), format: "yaml" }];
6138
+ return [{ path: join6(homeDir, ".config", "goose", "config.yaml"), format: "yaml" }];
5856
6139
  case "amazon-q":
5857
6140
  if (projectDir) {
5858
- return [{ path: join5(projectDir, ".amazonq", "mcp.json"), format: "json", rootKey: "mcpServers" }];
6141
+ return [{ path: join6(projectDir, ".amazonq", "mcp.json"), format: "json", rootKey: "mcpServers" }];
5859
6142
  }
5860
6143
  return [{
5861
- path: existsSync3(join5(homeDir, ".aws", "amazonq", "mcp.json")) ? join5(homeDir, ".aws", "amazonq", "mcp.json") : join5(homeDir, ".aws", "amazonq", "default.json"),
6144
+ path: existsSync3(join6(homeDir, ".aws", "amazonq", "mcp.json")) ? join6(homeDir, ".aws", "amazonq", "mcp.json") : join6(homeDir, ".aws", "amazonq", "default.json"),
5862
6145
  format: "json",
5863
6146
  rootKey: "mcpServers"
5864
6147
  }];
@@ -5894,14 +6177,14 @@ function agentConfigExists(agent, homeDir = homedir3(), projectDir) {
5894
6177
  return agentConfigPaths(agent, homeDir, projectDir).some((config) => existsSync3(config.path));
5895
6178
  }
5896
6179
  function claudeDesktopConfigPath(homeDir) {
5897
- if (process.platform === "darwin") return join5(homeDir, "Library", "Application Support", "Claude", "claude_desktop_config.json");
5898
- if (process.platform === "win32") return join5(process.env.APPDATA ?? join5(homeDir, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
5899
- return join5(process.env.XDG_CONFIG_HOME ?? join5(homeDir, ".config"), "Claude", "claude_desktop_config.json");
6180
+ if (process.platform === "darwin") return join6(homeDir, "Library", "Application Support", "Claude", "claude_desktop_config.json");
6181
+ if (process.platform === "win32") return join6(process.env.APPDATA ?? join6(homeDir, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
6182
+ return join6(process.env.XDG_CONFIG_HOME ?? join6(homeDir, ".config"), "Claude", "claude_desktop_config.json");
5900
6183
  }
5901
6184
  function vscodeUserConfigPath(homeDir) {
5902
- if (process.platform === "darwin") return join5(homeDir, "Library", "Application Support", "Code", "User", "mcp.json");
5903
- if (process.platform === "win32") return join5(process.env.APPDATA ?? join5(homeDir, "AppData", "Roaming"), "Code", "User", "mcp.json");
5904
- return join5(process.env.XDG_CONFIG_HOME ?? join5(homeDir, ".config"), "Code", "User", "mcp.json");
6185
+ if (process.platform === "darwin") return join6(homeDir, "Library", "Application Support", "Code", "User", "mcp.json");
6186
+ if (process.platform === "win32") return join6(process.env.APPDATA ?? join6(homeDir, "AppData", "Roaming"), "Code", "User", "mcp.json");
6187
+ return join6(process.env.XDG_CONFIG_HOME ?? join6(homeDir, ".config"), "Code", "User", "mcp.json");
5905
6188
  }
5906
6189
  async function readAgentRegistrations(agent, homeDir = homedir3(), projectDir) {
5907
6190
  const registrations = [];
@@ -6359,7 +6642,7 @@ function skillsSourceDir() {
6359
6642
  const distCandidate = resolve(here, "../skills");
6360
6643
  const srcCandidate = resolve(here, "../../skills");
6361
6644
  for (const candidate of [distCandidate, srcCandidate]) {
6362
- if (existsSync5(join6(candidate, "topolo", "SKILL.md"))) return candidate;
6645
+ if (existsSync5(join7(candidate, "topolo", "SKILL.md"))) return candidate;
6363
6646
  }
6364
6647
  return distCandidate;
6365
6648
  }
@@ -6402,7 +6685,7 @@ function resolveMcpRegistration(options) {
6402
6685
  return { name, ...context ? { context } : {}, environment, ...installationId ? { installationId } : {} };
6403
6686
  }
6404
6687
  async function installForAgent(agent, sourceDir, mcp, opts) {
6405
- const source = await readFile6(join6(sourceDir, "topolo", "SKILL.md"), "utf8");
6688
+ const source = await readFile6(join7(sourceDir, "topolo", "SKILL.md"), "utf8");
6406
6689
  const skill = await installAgentSkill(agent, source, opts.homeDir, opts.projectDir);
6407
6690
  if (!opts.withMcp) {
6408
6691
  return { agent, skillPath: skill.path, skillInstalled: skill.installed, mcp: "skipped" };
@@ -6498,7 +6781,7 @@ function registerSkillsCommand(program2) {
6498
6781
  }
6499
6782
  async function installAgentSurfaces(opts) {
6500
6783
  const sourceDir = skillsSourceDir();
6501
- if (!existsSync5(join6(sourceDir, "topolo", "SKILL.md"))) {
6784
+ if (!existsSync5(join7(sourceDir, "topolo", "SKILL.md"))) {
6502
6785
  throw new Error("Bundled skill file not found. Reinstall the CLI: npm install -g @topolo/cli");
6503
6786
  }
6504
6787
  const mcp = resolveMcpCommand();
@@ -6539,7 +6822,7 @@ function assertAgentSetupSucceeded(results) {
6539
6822
  }
6540
6823
  async function agentSurfaceStatus(options = {}) {
6541
6824
  const homeDir = options.homeDir ?? homedir4();
6542
- const sourceSkill = await readFile6(join6(skillsSourceDir(), "topolo", "SKILL.md"), "utf8");
6825
+ const sourceSkill = await readFile6(join7(skillsSourceDir(), "topolo", "SKILL.md"), "utf8");
6543
6826
  const agents = resolveTargets({
6544
6827
  agent: options.agent,
6545
6828
  all: options.all,