@topolo/cli 0.12.10 → 0.12.11

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.21.4/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) {
@@ -401,6 +349,136 @@ var TopoloHttpError = class extends TopoloSdkError {
401
349
  );
402
350
  }
403
351
  };
352
+ var ACTION_SECRET_PREFIX = "topolo-secret:";
353
+ function isActionSecretReference(value) {
354
+ return typeof value === "string" && value.startsWith(ACTION_SECRET_PREFIX);
355
+ }
356
+ function isRecord(value) {
357
+ return typeof value === "object" && value !== null && !Array.isArray(value);
358
+ }
359
+ function sensitive(key, value) {
360
+ const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
361
+ if (normalized === "capability") {
362
+ if (typeof value !== "string") return value !== null && value !== void 0 && typeof value !== "boolean" && typeof value !== "number";
363
+ const text2 = value.trim();
364
+ return Boolean(text2) && !/^[a-z][a-z0-9_-]{0,31}$/.test(text2);
365
+ }
366
+ return ["authorization", "cookie", "setcookie", "proxyauthorization"].includes(normalized) || /(token|secret|password|privatekey|apikey)$/.test(normalized);
367
+ }
368
+ function maskActionSecrets(value, secrets) {
369
+ let masked = value;
370
+ for (const [secret, reference] of secrets) {
371
+ const encoded = encodeURIComponent(secret);
372
+ const variants = [.../* @__PURE__ */ new Set([secret, encoded])].map((item) => item.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
373
+ masked = masked.replace(new RegExp(variants.join("|"), "g"), (match) => match === secret ? reference : encodeURIComponent(reference));
374
+ }
375
+ return masked;
376
+ }
377
+ async function resolveActionSecrets(value, store, secrets) {
378
+ if (isActionSecretReference(value)) {
379
+ if (!store) throw new TopoloSdkError("action_secret_store_required", "Protected action references require a secret store.");
380
+ const resolved = await store.load(value);
381
+ if (typeof resolved !== "string" || !resolved) throw new TopoloSdkError("action_secret_unsupported_type", "Protected action handoff requires a nonempty string credential.");
382
+ secrets.set(resolved, value);
383
+ return resolved;
384
+ }
385
+ if (Array.isArray(value)) return Promise.all(value.map((item) => resolveActionSecrets(item, store, secrets)));
386
+ if (!isRecord(value)) return value;
387
+ return Object.fromEntries(await Promise.all(Object.entries(value).map(async ([key, item]) => [key, await resolveActionSecrets(item, store, secrets)])));
388
+ }
389
+ async function protectActionResult(value, store) {
390
+ const secrets = /* @__PURE__ */ new Map();
391
+ async function protect(item, key = "") {
392
+ if (isActionSecretReference(item)) return item;
393
+ if (sensitive(key, item)) {
394
+ const reference = store ? await store.save(item) : "[REDACTED]";
395
+ if (typeof item === "string" && item) secrets.set(item, reference);
396
+ return reference;
397
+ }
398
+ if (Array.isArray(item)) return Promise.all(item.map((child) => protect(child)));
399
+ if (!isRecord(item)) return item;
400
+ return Object.fromEntries(await Promise.all(Object.entries(item).map(async ([name, child]) => [name, await protect(child, name)])));
401
+ }
402
+ const protectedValue = await protect(value);
403
+ async function protectEmbedded(item) {
404
+ if (typeof item === "string" && !isActionSecretReference(item) && maskActionSecrets(item, secrets) !== item) {
405
+ return store ? store.save(item) : maskActionSecrets(item, secrets);
406
+ }
407
+ if (Array.isArray(item)) return Promise.all(item.map(protectEmbedded));
408
+ if (!isRecord(item)) return item;
409
+ return Object.fromEntries(await Promise.all(Object.entries(item).map(async ([key, child]) => [key, await protectEmbedded(child)])));
410
+ }
411
+ return protectEmbedded(protectedValue);
412
+ }
413
+ function maskActionSecretValue(value, secrets) {
414
+ if (value instanceof Error) return maskActionSecretError(value, secrets);
415
+ if (typeof value === "string") return maskActionSecrets(value, secrets);
416
+ if (Array.isArray(value)) return value.map((item) => maskActionSecretValue(item, secrets));
417
+ if (!isRecord(value)) return value;
418
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, maskActionSecretValue(item, secrets)]));
419
+ }
420
+ function maskActionSecretError(error, secrets) {
421
+ if (!(error instanceof Error)) return maskActionSecretValue(error, secrets);
422
+ const masked = Object.create(Object.getPrototypeOf(error));
423
+ for (const key of Object.getOwnPropertyNames(error)) {
424
+ const descriptor = Object.getOwnPropertyDescriptor(error, key);
425
+ if ("value" in descriptor) descriptor.value = maskActionSecretValue(descriptor.value, secrets);
426
+ Object.defineProperty(masked, key, descriptor);
427
+ }
428
+ return masked;
429
+ }
430
+ var encoder = new TextEncoder();
431
+ function encodeBase64Url(value) {
432
+ const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
433
+ let binary = "";
434
+ for (const byte of bytes) binary += String.fromCharCode(byte);
435
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
436
+ }
437
+ function encodeJson(value) {
438
+ return encodeBase64Url(encoder.encode(JSON.stringify(value)));
439
+ }
440
+ async function sha256Base64Url(value) {
441
+ return encodeBase64Url(await crypto.subtle.digest("SHA-256", encoder.encode(value)));
442
+ }
443
+ function publicJwk(privateJwk) {
444
+ const { crv, kty, x, y } = privateJwk;
445
+ if (kty !== "EC" || crv !== "P-256" || !x || !y || !privateJwk.d) {
446
+ throw new TypeError("A private P-256 JSON Web Key is required for DPoP signing.");
447
+ }
448
+ return { crv, kty, x, y };
449
+ }
450
+ function canonicalTargetUrl(value) {
451
+ const url = new URL(value);
452
+ return `${url.origin}${url.pathname}`;
453
+ }
454
+ async function calculateDpopJkt(privateJwk) {
455
+ const jwk = publicJwk(privateJwk);
456
+ return sha256Base64Url(JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y }));
457
+ }
458
+ async function createDpopProof(input2) {
459
+ const jwk = publicJwk(input2.privateJwk);
460
+ const key = await crypto.subtle.importKey(
461
+ "jwk",
462
+ input2.privateJwk,
463
+ { name: "ECDSA", namedCurve: "P-256" },
464
+ false,
465
+ ["sign"]
466
+ );
467
+ const header = encodeJson({ alg: "ES256", typ: "dpop+jwt", jwk });
468
+ const claims = encodeJson({
469
+ jti: crypto.randomUUID(),
470
+ iat: Math.floor(Date.now() / 1e3),
471
+ htm: input2.method.toUpperCase(),
472
+ htu: canonicalTargetUrl(input2.targetUrl),
473
+ ath: await sha256Base64Url(input2.accessToken)
474
+ });
475
+ const signature = await crypto.subtle.sign(
476
+ { name: "ECDSA", hash: "SHA-256" },
477
+ key,
478
+ encoder.encode(`${header}.${claims}`)
479
+ );
480
+ return `${header}.${claims}.${encodeBase64Url(signature)}`;
481
+ }
404
482
  function hostnameHasSuffix(hostname, suffix) {
405
483
  return hostname === suffix || hostname.endsWith(`.${suffix}`);
406
484
  }
@@ -574,9 +652,9 @@ function inspectSchema(schema, path, root, depth) {
574
652
  return { path, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
575
653
  }
576
654
  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));
655
+ if (isRecord2(schema.properties)) children.push(...Object.entries(schema.properties));
656
+ if (isRecord2(schema.$defs)) children.push(...Object.entries(schema.$defs));
657
+ if (isRecord2(schema.definitions)) children.push(...Object.entries(schema.definitions));
580
658
  children.push(
581
659
  ["additionalProperties", schema.additionalProperties],
582
660
  ["propertyNames", schema.propertyNames],
@@ -586,7 +664,7 @@ function inspectSchema(schema, path, root, depth) {
586
664
  ...arraySchemaEntries("oneOf", schema.oneOf)
587
665
  );
588
666
  for (const [key, child] of children) {
589
- if (!isRecord(child)) continue;
667
+ if (!isRecord2(child)) continue;
590
668
  const issue = inspectSchema(child, `${path}.${key}`, root, depth + 1);
591
669
  if (issue) return issue;
592
670
  }
@@ -610,7 +688,7 @@ function validateNode(schema, value, path, root, errors, depth) {
610
688
  }
611
689
  if (Array.isArray(schema.allOf)) {
612
690
  for (const candidate of schema.allOf) {
613
- if (isRecord(candidate)) validateNode(candidate, value, path, root, errors, depth + 1);
691
+ if (isRecord2(candidate)) validateNode(candidate, value, path, root, errors, depth + 1);
614
692
  }
615
693
  }
616
694
  if (Array.isArray(schema.anyOf) && !schema.anyOf.some((candidate) => candidateMatches(candidate, value, root, depth))) {
@@ -640,7 +718,7 @@ function validateNode(schema, value, path, root, errors, depth) {
640
718
  if (typeof value === "string") validateString(schema, value, path, errors);
641
719
  if (typeof value === "number") validateNumber(schema, value, path, errors);
642
720
  if (Array.isArray(value)) validateArray(schema, value, path, root, errors, depth);
643
- if (isRecord(value)) validateObject(schema, value, path, root, errors, depth);
721
+ if (isRecord2(value)) validateObject(schema, value, path, root, errors, depth);
644
722
  }
645
723
  function validateObject(schema, value, path, root, errors, depth) {
646
724
  if (typeof schema.minProperties === "number" && Object.keys(value).length < schema.minProperties) {
@@ -649,25 +727,25 @@ function validateObject(schema, value, path, root, errors, depth) {
649
727
  if (typeof schema.maxProperties === "number" && Object.keys(value).length > schema.maxProperties) {
650
728
  errors.push({ path, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
651
729
  }
652
- const properties = isRecord(schema.properties) ? schema.properties : {};
730
+ const properties = isRecord2(schema.properties) ? schema.properties : {};
653
731
  const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
654
732
  for (const key of required) {
655
733
  if (!Object.prototype.hasOwnProperty.call(value, key)) {
656
734
  errors.push({ path: `${path}.${key}`, code: "required", message: "Required property is missing." });
657
735
  }
658
736
  }
659
- if (isRecord(schema.propertyNames)) {
737
+ if (isRecord2(schema.propertyNames)) {
660
738
  for (const key of Object.keys(value)) {
661
739
  validateNode(schema.propertyNames, key, `${path}.${key}`, root, errors, depth + 1);
662
740
  }
663
741
  }
664
742
  for (const [key, child] of Object.entries(value)) {
665
743
  const childSchema = properties[key];
666
- if (isRecord(childSchema)) {
744
+ if (isRecord2(childSchema)) {
667
745
  validateNode(childSchema, child, `${path}.${key}`, root, errors, depth + 1);
668
746
  } else if (schema.additionalProperties === false) {
669
747
  errors.push({ path: `${path}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
670
- } else if (isRecord(schema.additionalProperties)) {
748
+ } else if (isRecord2(schema.additionalProperties)) {
671
749
  validateNode(schema.additionalProperties, child, `${path}.${key}`, root, errors, depth + 1);
672
750
  }
673
751
  }
@@ -683,7 +761,7 @@ function validateArray(schema, value, path, root, errors, depth) {
683
761
  const unique = new Set(value.map((item) => JSON.stringify(item)));
684
762
  if (unique.size !== value.length) errors.push({ path, code: "unique_items", message: "Expected every item to be unique." });
685
763
  }
686
- if (isRecord(schema.items)) {
764
+ if (isRecord2(schema.items)) {
687
765
  value.forEach((item, index) => validateNode(schema.items, item, `${path}[${index}]`, root, errors, depth + 1));
688
766
  }
689
767
  }
@@ -721,7 +799,7 @@ function validateNumber(schema, value, path, errors) {
721
799
  }
722
800
  }
723
801
  function candidateMatches(candidate, value, root, depth) {
724
- if (!isRecord(candidate)) return false;
802
+ if (!isRecord2(candidate)) return false;
725
803
  const issues = [];
726
804
  validateNode(candidate, value, "$", root, issues, depth + 1);
727
805
  return issues.length === 0;
@@ -735,24 +813,24 @@ function resolveLocalReference(root, reference) {
735
813
  current = current[Number(token)];
736
814
  continue;
737
815
  }
738
- if (!isRecord(current) || !Object.prototype.hasOwnProperty.call(current, token)) return null;
816
+ if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, token)) return null;
739
817
  current = current[token];
740
818
  }
741
- return isRecord(current) ? current : null;
819
+ return isRecord2(current) ? current : null;
742
820
  }
743
821
  function matchesType(type, value) {
744
822
  if (type === void 0) return true;
745
823
  if (Array.isArray(type)) return type.some((candidate) => matchesType(candidate, value));
746
824
  if (type === "null") return value === null;
747
825
  if (type === "array") return Array.isArray(value);
748
- if (type === "object") return isRecord(value);
826
+ if (type === "object") return isRecord2(value);
749
827
  if (type === "integer") return typeof value === "number" && Number.isInteger(value);
750
828
  return typeof value === type;
751
829
  }
752
830
  function sameJsonValue(left, right) {
753
831
  return JSON.stringify(left) === JSON.stringify(right);
754
832
  }
755
- function isRecord(value) {
833
+ function isRecord2(value) {
756
834
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
757
835
  }
758
836
  function isUrl(value) {
@@ -987,7 +1065,7 @@ function fail(path, message2) {
987
1065
  throw new ActorEnvelopeValidationError(path, message2);
988
1066
  }
989
1067
  function parseTopoloActionUploadContract(value) {
990
- if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
1068
+ if (!isRecord3(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
991
1069
  throw new TopoloSdkError("upload_contract_unavailable", "This action does not publish a supported upload contract.");
992
1070
  }
993
1071
  const contentTypeField = requiredField(value.contentTypeField, "contentTypeField");
@@ -1066,17 +1144,17 @@ function prepareTopoloUrlSessionUploadInput(contractValue, byteLength, input2, o
1066
1144
  };
1067
1145
  }
1068
1146
  function topoloUploadResponseValue(response, path) {
1069
- return path.split(".").reduce((value, segment) => isRecord2(value) ? value[segment] : void 0, response);
1147
+ return path.split(".").reduce((value, segment) => isRecord3(value) ? value[segment] : void 0, response);
1070
1148
  }
1071
1149
  function redactTopoloUrlSessionResult(response, uploadUrlPath) {
1072
1150
  const redacted = cloneUploadValue(response);
1073
1151
  let target = redacted;
1074
1152
  const segments = uploadUrlPath.split(".");
1075
1153
  for (const segment of segments.slice(0, -1)) {
1076
- target = isRecord2(target) ? target[segment] : void 0;
1154
+ target = isRecord3(target) ? target[segment] : void 0;
1077
1155
  }
1078
1156
  const finalSegment = segments.at(-1);
1079
- if (finalSegment && isRecord2(target) && finalSegment in target) target[finalSegment] = "[REDACTED]";
1157
+ if (finalSegment && isRecord3(target) && finalSegment in target) target[finalSegment] = "[REDACTED]";
1080
1158
  redactUploadHeaders(redacted);
1081
1159
  return redacted;
1082
1160
  }
@@ -1100,7 +1178,7 @@ function mapUploadInput(contract, contentBase64, input2, options) {
1100
1178
  }
1101
1179
  function cloneUploadValue(value) {
1102
1180
  if (Array.isArray(value)) return value.map(cloneUploadValue);
1103
- if (!isRecord2(value)) return value;
1181
+ if (!isRecord3(value)) return value;
1104
1182
  return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, cloneUploadValue(item)]));
1105
1183
  }
1106
1184
  function redactUploadHeaders(value) {
@@ -1108,9 +1186,9 @@ function redactUploadHeaders(value) {
1108
1186
  value.forEach(redactUploadHeaders);
1109
1187
  return;
1110
1188
  }
1111
- if (!isRecord2(value)) return;
1189
+ if (!isRecord3(value)) return;
1112
1190
  for (const [key, item] of Object.entries(value)) {
1113
- if (key.toLowerCase() === "headers" && isRecord2(item)) {
1191
+ if (key.toLowerCase() === "headers" && isRecord3(item)) {
1114
1192
  for (const header of Object.keys(item)) {
1115
1193
  if (["authorization", "cookie", "set-cookie", "proxy-authorization", "x-api-key"].includes(header.toLowerCase())) {
1116
1194
  item[header] = "[REDACTED]";
@@ -1139,7 +1217,7 @@ function decodedBase64Size(contentBase64) {
1139
1217
  return contentBase64.length / 4 * 3 - padding;
1140
1218
  }
1141
1219
  function parseCompleteContract(value) {
1142
- if (!isRecord2(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
1220
+ if (!isRecord3(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
1143
1221
  return {
1144
1222
  actionId: requiredPath(value.actionId, "complete.actionId"),
1145
1223
  inputField: requiredField(value.inputField, "complete.inputField"),
@@ -1177,7 +1255,7 @@ function encodeTopoloActionBase64(bytes) {
1177
1255
  }
1178
1256
  return btoa(binary);
1179
1257
  }
1180
- function isRecord2(value) {
1258
+ function isRecord3(value) {
1181
1259
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1182
1260
  }
1183
1261
  var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
@@ -1188,6 +1266,8 @@ var TOPOLO_RESOURCE_ID_HEADER = "X-Topolo-Resource-ID";
1188
1266
  var POSITIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
1189
1267
  var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
1190
1268
  var TopoloClient = class _TopoloClient {
1269
+ actionSecretStore;
1270
+ actionSecrets = /* @__PURE__ */ new Map();
1191
1271
  credential;
1192
1272
  agent;
1193
1273
  actorContext;
@@ -1217,6 +1297,7 @@ var TopoloClient = class _TopoloClient {
1217
1297
  _TopoloClient.appIdCache.clear();
1218
1298
  }
1219
1299
  constructor(options) {
1300
+ this.actionSecretStore = options.actionSecretStore;
1220
1301
  if (!options.credential) throw new TopoloAuthError("credential is required");
1221
1302
  if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
1222
1303
  this.credential = options.credential;
@@ -1238,6 +1319,7 @@ var TopoloClient = class _TopoloClient {
1238
1319
  }
1239
1320
  emit(event) {
1240
1321
  if (!this.debug) return;
1322
+ event = maskActionSecretValue(event, this.actionSecrets);
1241
1323
  try {
1242
1324
  this.debug(event);
1243
1325
  } catch {
@@ -1623,7 +1705,7 @@ var TopoloClient = class _TopoloClient {
1623
1705
  }
1624
1706
  const entries = [];
1625
1707
  for (const item of raw) {
1626
- if (!isRecord3(item)) continue;
1708
+ if (!isRecord4(item)) continue;
1627
1709
  const s = item;
1628
1710
  const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
1629
1711
  if (!appId) continue;
@@ -1698,8 +1780,8 @@ var TopoloClient = class _TopoloClient {
1698
1780
  "app_topolo_auth.service_api_key_resources.list",
1699
1781
  { appId: service.appId }
1700
1782
  );
1701
- const payload = isRecord3(response) && isRecord3(response.data) ? response.data : response;
1702
- if (!isRecord3(payload) || !Array.isArray(payload.resources)) {
1783
+ const payload = isRecord4(response) && isRecord4(response.data) ? response.data : response;
1784
+ if (!isRecord4(payload) || !Array.isArray(payload.resources)) {
1703
1785
  throw new TopoloSdkError("resource_catalog_unavailable", "TopoloAuth did not return bindable resources.");
1704
1786
  }
1705
1787
  return {
@@ -1880,17 +1962,20 @@ var TopoloClient = class _TopoloClient {
1880
1962
  }
1881
1963
  async validateActionInput(action, input2 = {}) {
1882
1964
  const entry = await this.getAction(action);
1883
- return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input2) };
1965
+ const resolved = await resolveActionSecrets(input2, this.actionSecretStore, this.actionSecrets);
1966
+ return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, resolved) };
1884
1967
  }
1885
1968
  async planAction(action, input2 = {}, options = {}) {
1886
1969
  const entry = await this.getAction(action);
1887
- return this.planResolvedAction(entry, input2, options);
1970
+ const resolved = await resolveActionSecrets(input2, this.actionSecretStore, this.actionSecrets);
1971
+ return this.planResolvedAction(entry, resolved, options);
1888
1972
  }
1889
1973
  async prepareAction(action, input2, options = {}) {
1890
1974
  const entry = await this.getAction(action);
1891
1975
  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
1976
  const selectedInput = input2 ?? example?.input ?? {};
1893
- const plan = this.planResolvedAction(entry, selectedInput, options);
1977
+ const resolved = await resolveActionSecrets(selectedInput, this.actionSecretStore, this.actionSecrets);
1978
+ const plan = this.planResolvedAction(entry, resolved, options);
1894
1979
  return {
1895
1980
  action: entry,
1896
1981
  example,
@@ -1927,7 +2012,7 @@ var TopoloClient = class _TopoloClient {
1927
2012
  confirmationRequired,
1928
2013
  confirmationProvided: options.confirm === true,
1929
2014
  executable: validation.valid && agentAccess !== "off" && (!confirmationRequired || options.confirm === true),
1930
- request,
2015
+ request: maskActionSecretValue(request, this.actionSecrets),
1931
2016
  verification: stringArray(entry.agentContract?.verify),
1932
2017
  rollbackActionId: pickActionString(entry.agentContract?.rollbackActionId),
1933
2018
  nextActionIds: stringArray(entry.agentContract?.nextActionIds)
@@ -1957,6 +2042,7 @@ var TopoloClient = class _TopoloClient {
1957
2042
  `Topolo action "${entry.actionId}" requires { confirm: true } before invocation.`
1958
2043
  );
1959
2044
  }
2045
+ input2 = await resolveActionSecrets(input2, this.actionSecretStore, this.actionSecrets);
1960
2046
  const validation = validateTopoloActionInput(entry.inputSchema, input2);
1961
2047
  if (!validation.valid) {
1962
2048
  throw new TopoloSdkError(
@@ -1987,22 +2073,31 @@ var TopoloClient = class _TopoloClient {
1987
2073
  }
1988
2074
  }
1989
2075
  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);
2076
+ let result;
2077
+ try {
2078
+ result = await this.request({
2079
+ service,
2080
+ ...entry.apiBaseUrl ? { baseUrl: entry.apiBaseUrl } : {},
2081
+ method: entry.method,
2082
+ path: mapped.path,
2083
+ ...mapped.query ? { query: mapped.query } : {},
2084
+ ...mapped.body !== void 0 ? { body: mapped.body } : {},
2085
+ ...options.resource ? { resource: options.resource } : {},
2086
+ ...resolvedActorEnvelope ? { actorEnvelope: resolvedActorEnvelope } : {},
2087
+ headers: {
2088
+ [TOPOLO_ACTION_ID_HEADER]: entry.actionId,
2089
+ [TOPOLO_ACTION_NAME_HEADER]: entry.name
2090
+ },
2091
+ confirm: options.confirm === true || entry.readOnly && !entry.destructive && !entry.requiresConfirmation
2092
+ });
2093
+ } catch (error) {
2094
+ throw maskActionSecretError(error, this.actionSecrets);
2095
+ }
2096
+ return preserveSensitiveResult ? result : await protectActionResult(result, this.actionSecretStore);
2097
+ }
2098
+ async releaseActionSecret(reference) {
2099
+ if (!this.actionSecretStore) throw new TopoloSdkError("action_secret_store_required", "Protected action references require a secret store.");
2100
+ await this.actionSecretStore.delete(reference);
2006
2101
  }
2007
2102
  async buildVerifiedAgentActorEnvelope(actionId, input2, resource) {
2008
2103
  if (!this.actorContext) return void 0;
@@ -2149,7 +2244,7 @@ function resolveConfiguredAppId(slug, appIds) {
2149
2244
  return null;
2150
2245
  }
2151
2246
  function normalizeApiKeyResourceMetadata(input2) {
2152
- if (!isRecord3(input2)) return null;
2247
+ if (!isRecord4(input2)) return null;
2153
2248
  const resourceType = readString(input2.resourceType ?? input2.resource_type);
2154
2249
  const label = readString(input2.label);
2155
2250
  const emptyLabel = readString(input2.emptyLabel ?? input2.empty_label);
@@ -2183,7 +2278,7 @@ function normalizeApiKeyResourceMetadataList(input2) {
2183
2278
  return resources;
2184
2279
  }
2185
2280
  function normalizeBindableResource(input2) {
2186
- if (!isRecord3(input2)) return null;
2281
+ if (!isRecord4(input2)) return null;
2187
2282
  const resourceType = readString(input2.resourceType ?? input2.resource_type);
2188
2283
  const resourceId = readString(input2.resourceId ?? input2.resource_id);
2189
2284
  if (!resourceType || !resourceId) return null;
@@ -2194,13 +2289,13 @@ function normalizeBindableResource(input2) {
2194
2289
  slug: readOptionalString(input2.slug),
2195
2290
  status: readOptionalString(input2.status),
2196
2291
  organizationId: readOptionalString(input2.organizationId ?? input2.organization_id),
2197
- metadata: isRecord3(input2.metadata) ? input2.metadata : {}
2292
+ metadata: isRecord4(input2.metadata) ? input2.metadata : {}
2198
2293
  };
2199
2294
  }
2200
2295
  function normalizeResourceBindings(input2) {
2201
2296
  if (!Array.isArray(input2)) return [];
2202
2297
  return input2.flatMap((binding) => {
2203
- if (!isRecord3(binding)) return [];
2298
+ if (!isRecord4(binding)) return [];
2204
2299
  const resourceType = readString(binding.resourceType ?? binding.resource_type);
2205
2300
  const resourceId = readString(binding.resourceId ?? binding.resource_id);
2206
2301
  return resourceType && resourceId ? [{ resourceType, resourceId }] : [];
@@ -2213,30 +2308,9 @@ function readOptionalString(value) {
2213
2308
  const normalized = readString(value);
2214
2309
  return normalized || null;
2215
2310
  }
2216
- function isRecord3(value) {
2311
+ function isRecord4(value) {
2217
2312
  return typeof value === "object" && value !== null && !Array.isArray(value);
2218
2313
  }
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
2314
  function resolveConfiguredServiceUrl(slug, serviceUrls) {
2241
2315
  for (const serviceKey of serviceKeyCandidates2(slug)) {
2242
2316
  const configured = serviceUrls?.[serviceKey];
@@ -2306,14 +2380,14 @@ function catalogSearchQuery(options) {
2306
2380
  return query;
2307
2381
  }
2308
2382
  function normalizeCatalogPage(value) {
2309
- if (!isRecord3(value)) return { nextCursor: null, hasMore: false };
2383
+ if (!isRecord4(value)) return { nextCursor: null, hasMore: false };
2310
2384
  return {
2311
2385
  nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
2312
2386
  hasMore: value.hasMore === true
2313
2387
  };
2314
2388
  }
2315
2389
  function normalizeServiceCatalogEntry(item) {
2316
- if (!isRecord3(item)) return null;
2390
+ if (!isRecord4(item)) return null;
2317
2391
  const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
2318
2392
  if (!appId) return null;
2319
2393
  const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
@@ -3342,11 +3416,13 @@ function createTopolo(options) {
3342
3416
  };
3343
3417
  }
3344
3418
 
3345
- // node_modules/.pnpm/@topolo+sdk@0.21.2/node_modules/@topolo/sdk/dist/node/credential-store.js
3419
+ // node_modules/.pnpm/@topolo+sdk@0.21.4/node_modules/@topolo/sdk/dist/node/credential-store.js
3346
3420
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, chmod as chmod2, rm as rm2, stat } from "fs/promises";
3347
3421
  import { existsSync as existsSync2 } from "fs";
3348
3422
  import { homedir as homedir2, platform } from "os";
3349
- import { dirname as dirname2, join as join2 } from "path";
3423
+ import { dirname as dirname2, join as join3 } from "path";
3424
+ import { createHash, randomUUID } from "crypto";
3425
+ import { join as join2 } from "path";
3350
3426
  import { spawnSync } from "child_process";
3351
3427
  import { mkdir, readFile, readdir, rename, rm, writeFile, chmod } from "fs/promises";
3352
3428
  import { existsSync } from "fs";
@@ -3698,67 +3774,7 @@ var TopoloOAuth2 = class {
3698
3774
  return parsed;
3699
3775
  }
3700
3776
  };
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
- }
3777
+ var ACTION_SECRET_PREFIX2 = "topolo-secret:";
3762
3778
  var KEYCHAIN_SERVICE = "topolo-external-agent-installation";
3763
3779
  var MACOS_KEYCHAIN_CHUNK_SIZE = 96;
3764
3780
  var MACOS_KEYCHAIN_CHUNK_PREFIX = "topolo-keychain-chunks:v1:";
@@ -3801,6 +3817,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3801
3817
  const platform22 = options.platform ?? hostPlatform();
3802
3818
  const spawn3 = options.spawn ?? ((command, args, spawnOptions) => spawnSync(command, args, spawnOptions));
3803
3819
  const root = options.root ?? installationStorePath();
3820
+ const service = options.service ?? KEYCHAIN_SERVICE;
3804
3821
  if (platform22 === "darwin") {
3805
3822
  const write = (account, value) => runOrThrow(spawn3("/usr/bin/expect", ["-c", MACOS_KEYCHAIN_WRITE_SCRIPT], {
3806
3823
  input: `${value}
@@ -3809,7 +3826,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3809
3826
  env: {
3810
3827
  ...process.env,
3811
3828
  TOPOLO_KEYCHAIN_ACCOUNT: account,
3812
- TOPOLO_KEYCHAIN_SERVICE: KEYCHAIN_SERVICE
3829
+ TOPOLO_KEYCHAIN_SERVICE: service
3813
3830
  }
3814
3831
  }), "macOS Keychain write");
3815
3832
  const read = (account) => {
@@ -3818,7 +3835,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3818
3835
  "-a",
3819
3836
  account,
3820
3837
  "-s",
3821
- KEYCHAIN_SERVICE,
3838
+ service,
3822
3839
  "-w"
3823
3840
  ], { encoding: "utf8" });
3824
3841
  if (result.status === 44) return null;
@@ -3830,7 +3847,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3830
3847
  "-a",
3831
3848
  account,
3832
3849
  "-s",
3833
- KEYCHAIN_SERVICE
3850
+ service
3834
3851
  ], { encoding: "utf8" });
3835
3852
  if (result.status !== 44) runOrThrow(result, "macOS Keychain delete");
3836
3853
  };
@@ -3877,9 +3894,9 @@ function createPlatformInstallationSecretBackend(options = {}) {
3877
3894
  async set(account, value) {
3878
3895
  runOrThrow(spawn3("secret-tool", [
3879
3896
  "store",
3880
- "--label=Topolo external agent installation",
3897
+ "--label=Topolo protected credential",
3881
3898
  "service",
3882
- KEYCHAIN_SERVICE,
3899
+ service,
3883
3900
  "account",
3884
3901
  account
3885
3902
  ], { input: value, encoding: "utf8" }), "Linux Secret Service write");
@@ -3888,7 +3905,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3888
3905
  const result = spawn3("secret-tool", [
3889
3906
  "lookup",
3890
3907
  "service",
3891
- KEYCHAIN_SERVICE,
3908
+ service,
3892
3909
  "account",
3893
3910
  account
3894
3911
  ], { encoding: "utf8" });
@@ -3899,7 +3916,7 @@ function createPlatformInstallationSecretBackend(options = {}) {
3899
3916
  const result = spawn3("secret-tool", [
3900
3917
  "clear",
3901
3918
  "service",
3902
- KEYCHAIN_SERVICE,
3919
+ service,
3903
3920
  "account",
3904
3921
  account
3905
3922
  ], { encoding: "utf8" });
@@ -3961,6 +3978,114 @@ async function deleteExternalAgentInstallation(environment, id, backend = create
3961
3978
  await backend.delete(accountName(environment, id));
3962
3979
  await rm(installationPath(environment, id), { force: true });
3963
3980
  }
3981
+ function createProtectedActionSecretStore(options) {
3982
+ const backend = options.backend ?? createPlatformInstallationSecretBackend({
3983
+ service: "topolo-action-handoff",
3984
+ root: join2(installationStorePath(), "action-handoff")
3985
+ });
3986
+ const now = options.now ?? Date.now;
3987
+ async function account(reference) {
3988
+ if (!/^topolo-secret:[0-9a-f-]{36}$/.test(reference)) throw new TopoloSdkError2("invalid_action_secret_reference", "Invalid protected action reference.");
3989
+ const identity = await options.identity();
3990
+ if (!identity.user.id) throw new TopoloSdkError2("action_secret_identity_required", "A verified identity is required for protected action handoff.");
3991
+ 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.");
3992
+ const scope = createHash("sha256").update(JSON.stringify([
3993
+ options.environment,
3994
+ identity.user.principalId ?? identity.user.id,
3995
+ identity.organization?.id ?? null,
3996
+ identity.apiKey?.id ?? null
3997
+ ])).digest("hex");
3998
+ return `${scope}-${reference.slice(ACTION_SECRET_PREFIX2.length)}`;
3999
+ }
4000
+ return {
4001
+ async save(value) {
4002
+ const reference = `${ACTION_SECRET_PREFIX2}${randomUUID()}`;
4003
+ await backend.set(await account(reference), JSON.stringify({ value, expiresAt: now() + 30 * 60 * 1e3 }));
4004
+ return reference;
4005
+ },
4006
+ async load(reference) {
4007
+ const key = await account(reference);
4008
+ const encoded = await backend.get(key);
4009
+ if (!encoded) throw new TopoloSdkError2("action_secret_unavailable", "Protected action reference is missing or belongs to another identity or environment.");
4010
+ let record2;
4011
+ try {
4012
+ record2 = JSON.parse(encoded);
4013
+ } catch {
4014
+ throw new TopoloSdkError2("action_secret_invalid", "Protected action reference is corrupt.");
4015
+ }
4016
+ if (!Number.isFinite(record2?.expiresAt) || !Object.hasOwn(record2, "value")) throw new TopoloSdkError2("action_secret_invalid", "Protected action reference is corrupt.");
4017
+ if (record2.expiresAt <= now()) {
4018
+ await backend.delete(key);
4019
+ throw new TopoloSdkError2("action_secret_expired", "Protected action reference expired. Repeat the originating action if its contract permits.");
4020
+ }
4021
+ return record2.value;
4022
+ },
4023
+ async delete(reference) {
4024
+ await backend.delete(await account(reference));
4025
+ }
4026
+ };
4027
+ }
4028
+ var TOPOLO_RUNTIME_ENVIRONMENTS = ["production", "staging", "development", "local"];
4029
+ var TOPOLO_ENVIRONMENT_SERVICE_URLS = {
4030
+ production: {
4031
+ auth: "https://auth.topolo.app",
4032
+ developers: "https://developers.topolo.app"
4033
+ },
4034
+ staging: {
4035
+ auth: "https://auth.stg.topolo.us",
4036
+ developers: "https://developers.stg.topolo.us"
4037
+ },
4038
+ development: {
4039
+ auth: "https://auth.topolo.dev",
4040
+ developers: "https://developers.topolo.dev"
4041
+ },
4042
+ local: {}
4043
+ };
4044
+ function hostnameHasSuffix2(hostname, suffix) {
4045
+ return hostname === suffix || hostname.endsWith(`.${suffix}`);
4046
+ }
4047
+ function workersDevAccountMatches2(hostname, account) {
4048
+ if (!hostname.endsWith(".workers.dev")) return false;
4049
+ const labels = hostname.slice(0, -".workers.dev".length).split(".");
4050
+ return labels.length >= 2 && labels.at(-1) === account;
4051
+ }
4052
+ function originMatchesEnvironment2(url, environment) {
4053
+ const hostname = url.hostname.toLowerCase();
4054
+ if (environment === "local") {
4055
+ return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "[::1]";
4056
+ }
4057
+ if (url.protocol !== "https:") return false;
4058
+ if (environment === "production") {
4059
+ return hostnameHasSuffix2(hostname, "topolo.app") || hostnameHasSuffix2(hostname, "topolo.io") || hostname === "api.ol0.me" || workersDevAccountMatches2(hostname, "topolo");
4060
+ }
4061
+ if (environment === "staging") {
4062
+ return hostnameHasSuffix2(hostname, "stg.topolo.us") || workersDevAccountMatches2(hostname, "topolo-staging");
4063
+ }
4064
+ return hostnameHasSuffix2(hostname, "topolo.dev") || workersDevAccountMatches2(hostname, "topolo-development");
4065
+ }
4066
+ function assertTopoloServiceUrlEnvironment2(service, value, environment) {
4067
+ let url;
4068
+ try {
4069
+ url = new URL(value);
4070
+ } catch {
4071
+ throw new TopoloSdkError2("invalid_service_url", `Invalid URL configured for Topolo service "${service}".`);
4072
+ }
4073
+ if (url.protocol !== "https:" && url.protocol !== "http:" || url.username || url.password || !originMatchesEnvironment2(url, environment)) {
4074
+ throw new TopoloSdkError2(
4075
+ "environment_mismatch",
4076
+ `Refusing Topolo service "${service}": ${url.origin} does not belong to ${environment}.`
4077
+ );
4078
+ }
4079
+ return url.origin;
4080
+ }
4081
+ function topoloServiceOrigins2(serviceUrls, environment) {
4082
+ return Object.fromEntries(
4083
+ Object.entries(serviceUrls ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([service, value]) => [
4084
+ service,
4085
+ assertTopoloServiceUrlEnvironment2(service, value, environment)
4086
+ ])
4087
+ );
4088
+ }
3964
4089
  var TOPOLO_ENVIRONMENTS = TOPOLO_RUNTIME_ENVIRONMENTS;
3965
4090
  function contextKey(contextType, organizationId) {
3966
4091
  return contextType === "organization" && organizationId ? `org:${organizationId}` : "personal";
@@ -3969,8 +4094,8 @@ var ENVIRONMENT_DEFAULT_SERVICE_URLS = TOPOLO_ENVIRONMENT_SERVICE_URLS;
3969
4094
  function configPath() {
3970
4095
  const override = process.env.TOPOLO_CONFIG_PATH;
3971
4096
  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");
4097
+ const base = process.env.XDG_CONFIG_HOME || (platform() === "win32" ? join3(homedir2(), "AppData", "Roaming") : join3(homedir2(), ".config"));
4098
+ return join3(base, "topolo", "config.json");
3974
4099
  }
3975
4100
  async function readConfig() {
3976
4101
  const path = configPath();
@@ -4373,14 +4498,14 @@ function normalizeError(err) {
4373
4498
 
4374
4499
  // src/version.ts
4375
4500
  import { readFileSync } from "fs";
4376
- import { dirname as dirname3, join as join3 } from "path";
4501
+ import { dirname as dirname3, join as join4 } from "path";
4377
4502
  import { fileURLToPath } from "url";
4378
4503
  function readCliVersion() {
4379
4504
  try {
4380
4505
  const here = dirname3(fileURLToPath(import.meta.url));
4381
4506
  const candidates = [
4382
- join3(here, "package.json"),
4383
- join3(here, "..", "package.json")
4507
+ join4(here, "package.json"),
4508
+ join4(here, "..", "package.json")
4384
4509
  ];
4385
4510
  for (const path of candidates) {
4386
4511
  try {
@@ -4667,9 +4792,10 @@ async function buildClient(options) {
4667
4792
  if (!credential) return null;
4668
4793
  const config = await readConfig();
4669
4794
  const profile = resolvedProfileConfig(config, environment);
4670
- return createTopolo({
4795
+ const topolo = createTopolo({
4671
4796
  credential,
4672
4797
  environment,
4798
+ actionSecretStore: createProtectedActionSecretStore({ environment, identity: () => topolo.client.introspect() }),
4673
4799
  actorContext: { channel: "cli" },
4674
4800
  agent: {
4675
4801
  clientName: "topolo-cli",
@@ -4683,6 +4809,7 @@ async function buildClient(options) {
4683
4809
  } : {},
4684
4810
  ...isDebugEnabled() ? { debug: stderrDebugLogger } : {}
4685
4811
  });
4812
+ return topolo;
4686
4813
  }
4687
4814
  function isDebugEnabled() {
4688
4815
  const v = process.env.TOPOLO_DEBUG;
@@ -4878,6 +5005,18 @@ function registerActionsCommand(program2) {
4878
5005
  fail2(err, mode);
4879
5006
  }
4880
5007
  });
5008
+ 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) => {
5009
+ const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
5010
+ try {
5011
+ if (opts.confirm !== true) throw new Error("Releasing a protected action reference requires --confirm.");
5012
+ const topolo = await buildClient();
5013
+ if (!topolo) throw new Error("No credential configured. Run `topolo auth login`.");
5014
+ await topolo.client.releaseActionSecret(reference);
5015
+ emit({ released: true }, mode, () => "Protected action reference released.");
5016
+ } catch (error) {
5017
+ fail2(error, mode);
5018
+ }
5019
+ });
4881
5020
  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
5021
  const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
4883
5022
  try {
@@ -5523,7 +5662,7 @@ function normalizeList(values, fallback = []) {
5523
5662
 
5524
5663
  // src/commands/integrations.ts
5525
5664
  import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "fs/promises";
5526
- import { join as join4, relative } from "path";
5665
+ import { join as join5, relative } from "path";
5527
5666
  function registerIntegrationsCommand(program2) {
5528
5667
  const integrations = program2.command("integrations").description("Generate and validate Topolo integration handoffs.");
5529
5668
  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 +5805,7 @@ function detectFramework(packageJson, haystack) {
5666
5805
  }
5667
5806
  async function readPackageJson(repoRoot) {
5668
5807
  try {
5669
- return JSON.parse(await readFile3(join4(repoRoot, "package.json"), "utf8"));
5808
+ return JSON.parse(await readFile3(join5(repoRoot, "package.json"), "utf8"));
5670
5809
  } catch {
5671
5810
  return null;
5672
5811
  }
@@ -5678,7 +5817,7 @@ async function collectFiles(repoRoot) {
5678
5817
  if (result.length > 2e3) return;
5679
5818
  const entries = await readdir2(dir, { withFileTypes: true });
5680
5819
  for (const entry of entries) {
5681
- const fullPath = join4(dir, entry.name);
5820
+ const fullPath = join5(dir, entry.name);
5682
5821
  if (entry.isDirectory()) {
5683
5822
  if ([".git", "node_modules", "dist", "build", ".next", "coverage"].includes(entry.name)) continue;
5684
5823
  await walk(fullPath);
@@ -5709,7 +5848,7 @@ import { spawnSync as spawnSync3 } from "child_process";
5709
5848
  import { existsSync as existsSync5 } from "fs";
5710
5849
  import { readFile as readFile6, rm as rm3 } from "fs/promises";
5711
5850
  import { homedir as homedir4 } from "os";
5712
- import { dirname as dirname5, join as join6, resolve } from "path";
5851
+ import { dirname as dirname5, join as join7, resolve } from "path";
5713
5852
  import { fileURLToPath as fileURLToPath2 } from "url";
5714
5853
  import { createRequire } from "module";
5715
5854
 
@@ -5718,7 +5857,7 @@ import { spawnSync as spawnSync2 } from "child_process";
5718
5857
  import { existsSync as existsSync3 } from "fs";
5719
5858
  import { readFile as readFile4 } from "fs/promises";
5720
5859
  import { homedir as homedir3 } from "os";
5721
- import { isAbsolute, join as join5 } from "path";
5860
+ import { isAbsolute, join as join6 } from "path";
5722
5861
  var SUPPORTED_AGENT_IDS = [
5723
5862
  "claude",
5724
5863
  "claude-desktop",
@@ -5776,23 +5915,23 @@ function stableNodeCommand() {
5776
5915
  function agentSkillPath(agent, homeDir = homedir3(), projectDir) {
5777
5916
  switch (agent) {
5778
5917
  case "claude":
5779
- return projectDir ? join5(projectDir, ".claude", "skills", "topolo", "SKILL.md") : join5(homeDir, ".claude", "skills", "topolo", "SKILL.md");
5918
+ return projectDir ? join6(projectDir, ".claude", "skills", "topolo", "SKILL.md") : join6(homeDir, ".claude", "skills", "topolo", "SKILL.md");
5780
5919
  case "claude-desktop":
5781
- return join5(homeDir, ".claude", "skills", "topolo", "SKILL.md");
5920
+ return join6(homeDir, ".claude", "skills", "topolo", "SKILL.md");
5782
5921
  case "codex":
5783
- return join5(homeDir, ".codex", "skills", "topolo", "SKILL.md");
5922
+ return join6(homeDir, ".codex", "skills", "topolo", "SKILL.md");
5784
5923
  case "cursor":
5785
- return join5(projectDir ?? homeDir, ".cursor", "rules", "topolo.mdc");
5924
+ return join6(projectDir ?? homeDir, ".cursor", "rules", "topolo.mdc");
5786
5925
  case "grok":
5787
- return join5(projectDir ?? homeDir, ".grok", "skills", "topolo", "SKILL.md");
5926
+ return join6(projectDir ?? homeDir, ".grok", "skills", "topolo", "SKILL.md");
5788
5927
  case "gemini":
5789
- return join5(projectDir ?? homeDir, ".gemini", "skills", "topolo", "SKILL.md");
5928
+ return join6(projectDir ?? homeDir, ".gemini", "skills", "topolo", "SKILL.md");
5790
5929
  case "copilot":
5791
- return projectDir ? join5(projectDir, ".github", "skills", "topolo", "SKILL.md") : join5(homeDir, ".copilot", "skills", "topolo", "SKILL.md");
5930
+ return projectDir ? join6(projectDir, ".github", "skills", "topolo", "SKILL.md") : join6(homeDir, ".copilot", "skills", "topolo", "SKILL.md");
5792
5931
  case "windsurf":
5793
- return projectDir ? join5(projectDir, ".windsurf", "rules", "topolo.md") : void 0;
5932
+ return projectDir ? join6(projectDir, ".windsurf", "rules", "topolo.md") : void 0;
5794
5933
  case "cline":
5795
- return join5(projectDir ?? homeDir, ".cline", "skills", "topolo", "SKILL.md");
5934
+ return join6(projectDir ?? homeDir, ".cline", "skills", "topolo", "SKILL.md");
5796
5935
  case "goose":
5797
5936
  case "amazon-q":
5798
5937
  return void 0;
@@ -5801,7 +5940,7 @@ function agentSkillPath(agent, homeDir = homedir3(), projectDir) {
5801
5940
  function agentConfigPaths(agent, homeDir = homedir3(), projectDir) {
5802
5941
  switch (agent) {
5803
5942
  case "claude":
5804
- return [{ path: join5(homeDir, ".claude.json"), format: "json", rootKey: "mcpServers" }];
5943
+ return [{ path: join6(homeDir, ".claude.json"), format: "json", rootKey: "mcpServers" }];
5805
5944
  case "claude-desktop":
5806
5945
  return [{
5807
5946
  path: claudeDesktopConfigPath(homeDir),
@@ -5809,31 +5948,31 @@ function agentConfigPaths(agent, homeDir = homedir3(), projectDir) {
5809
5948
  rootKey: "mcpServers"
5810
5949
  }];
5811
5950
  case "codex":
5812
- return [{ path: join5(homeDir, ".codex", "config.toml"), format: "toml" }];
5951
+ return [{ path: join6(homeDir, ".codex", "config.toml"), format: "toml" }];
5813
5952
  case "cursor":
5814
5953
  return [{
5815
- path: projectDir ? join5(projectDir, ".cursor", "mcp.json") : join5(homeDir, ".cursor", "mcp.json"),
5954
+ path: projectDir ? join6(projectDir, ".cursor", "mcp.json") : join6(homeDir, ".cursor", "mcp.json"),
5816
5955
  format: "json",
5817
5956
  rootKey: "mcpServers"
5818
5957
  }];
5819
5958
  case "grok":
5820
5959
  return [{
5821
- path: projectDir ? join5(projectDir, ".grok", "config.toml") : join5(homeDir, ".grok", "config.toml"),
5960
+ path: projectDir ? join6(projectDir, ".grok", "config.toml") : join6(homeDir, ".grok", "config.toml"),
5822
5961
  format: "toml"
5823
5962
  }];
5824
5963
  case "gemini":
5825
5964
  return [{
5826
- path: projectDir ? join5(projectDir, ".gemini", "settings.json") : join5(homeDir, ".gemini", "settings.json"),
5965
+ path: projectDir ? join6(projectDir, ".gemini", "settings.json") : join6(homeDir, ".gemini", "settings.json"),
5827
5966
  format: "json",
5828
5967
  rootKey: "mcpServers"
5829
5968
  }];
5830
5969
  case "copilot":
5831
5970
  return projectDir ? [
5832
- { path: join5(projectDir, ".mcp.json"), format: "json", rootKey: "mcpServers" },
5833
- { path: join5(projectDir, ".vscode", "mcp.json"), format: "json", rootKey: "servers" }
5971
+ { path: join6(projectDir, ".mcp.json"), format: "json", rootKey: "mcpServers" },
5972
+ { path: join6(projectDir, ".vscode", "mcp.json"), format: "json", rootKey: "servers" }
5834
5973
  ] : [
5835
5974
  {
5836
- path: join5(homeDir, ".copilot", "mcp-config.json"),
5975
+ path: join6(homeDir, ".copilot", "mcp-config.json"),
5837
5976
  format: "json",
5838
5977
  rootKey: "mcpServers"
5839
5978
  },
@@ -5841,24 +5980,24 @@ function agentConfigPaths(agent, homeDir = homedir3(), projectDir) {
5841
5980
  ];
5842
5981
  case "windsurf":
5843
5982
  return [{
5844
- path: join5(homeDir, ".codeium", "windsurf", "mcp_config.json"),
5983
+ path: join6(homeDir, ".codeium", "windsurf", "mcp_config.json"),
5845
5984
  format: "json",
5846
5985
  rootKey: "mcpServers"
5847
5986
  }];
5848
5987
  case "cline":
5849
5988
  return [{
5850
- path: projectDir ? join5(projectDir, ".cline", "mcp.json") : join5(homeDir, ".cline", "mcp.json"),
5989
+ path: projectDir ? join6(projectDir, ".cline", "mcp.json") : join6(homeDir, ".cline", "mcp.json"),
5851
5990
  format: "json",
5852
5991
  rootKey: "mcpServers"
5853
5992
  }];
5854
5993
  case "goose":
5855
- return [{ path: join5(homeDir, ".config", "goose", "config.yaml"), format: "yaml" }];
5994
+ return [{ path: join6(homeDir, ".config", "goose", "config.yaml"), format: "yaml" }];
5856
5995
  case "amazon-q":
5857
5996
  if (projectDir) {
5858
- return [{ path: join5(projectDir, ".amazonq", "mcp.json"), format: "json", rootKey: "mcpServers" }];
5997
+ return [{ path: join6(projectDir, ".amazonq", "mcp.json"), format: "json", rootKey: "mcpServers" }];
5859
5998
  }
5860
5999
  return [{
5861
- path: existsSync3(join5(homeDir, ".aws", "amazonq", "mcp.json")) ? join5(homeDir, ".aws", "amazonq", "mcp.json") : join5(homeDir, ".aws", "amazonq", "default.json"),
6000
+ path: existsSync3(join6(homeDir, ".aws", "amazonq", "mcp.json")) ? join6(homeDir, ".aws", "amazonq", "mcp.json") : join6(homeDir, ".aws", "amazonq", "default.json"),
5862
6001
  format: "json",
5863
6002
  rootKey: "mcpServers"
5864
6003
  }];
@@ -5894,14 +6033,14 @@ function agentConfigExists(agent, homeDir = homedir3(), projectDir) {
5894
6033
  return agentConfigPaths(agent, homeDir, projectDir).some((config) => existsSync3(config.path));
5895
6034
  }
5896
6035
  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");
6036
+ if (process.platform === "darwin") return join6(homeDir, "Library", "Application Support", "Claude", "claude_desktop_config.json");
6037
+ if (process.platform === "win32") return join6(process.env.APPDATA ?? join6(homeDir, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
6038
+ return join6(process.env.XDG_CONFIG_HOME ?? join6(homeDir, ".config"), "Claude", "claude_desktop_config.json");
5900
6039
  }
5901
6040
  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");
6041
+ if (process.platform === "darwin") return join6(homeDir, "Library", "Application Support", "Code", "User", "mcp.json");
6042
+ if (process.platform === "win32") return join6(process.env.APPDATA ?? join6(homeDir, "AppData", "Roaming"), "Code", "User", "mcp.json");
6043
+ return join6(process.env.XDG_CONFIG_HOME ?? join6(homeDir, ".config"), "Code", "User", "mcp.json");
5905
6044
  }
5906
6045
  async function readAgentRegistrations(agent, homeDir = homedir3(), projectDir) {
5907
6046
  const registrations = [];
@@ -6359,7 +6498,7 @@ function skillsSourceDir() {
6359
6498
  const distCandidate = resolve(here, "../skills");
6360
6499
  const srcCandidate = resolve(here, "../../skills");
6361
6500
  for (const candidate of [distCandidate, srcCandidate]) {
6362
- if (existsSync5(join6(candidate, "topolo", "SKILL.md"))) return candidate;
6501
+ if (existsSync5(join7(candidate, "topolo", "SKILL.md"))) return candidate;
6363
6502
  }
6364
6503
  return distCandidate;
6365
6504
  }
@@ -6402,7 +6541,7 @@ function resolveMcpRegistration(options) {
6402
6541
  return { name, ...context ? { context } : {}, environment, ...installationId ? { installationId } : {} };
6403
6542
  }
6404
6543
  async function installForAgent(agent, sourceDir, mcp, opts) {
6405
- const source = await readFile6(join6(sourceDir, "topolo", "SKILL.md"), "utf8");
6544
+ const source = await readFile6(join7(sourceDir, "topolo", "SKILL.md"), "utf8");
6406
6545
  const skill = await installAgentSkill(agent, source, opts.homeDir, opts.projectDir);
6407
6546
  if (!opts.withMcp) {
6408
6547
  return { agent, skillPath: skill.path, skillInstalled: skill.installed, mcp: "skipped" };
@@ -6498,7 +6637,7 @@ function registerSkillsCommand(program2) {
6498
6637
  }
6499
6638
  async function installAgentSurfaces(opts) {
6500
6639
  const sourceDir = skillsSourceDir();
6501
- if (!existsSync5(join6(sourceDir, "topolo", "SKILL.md"))) {
6640
+ if (!existsSync5(join7(sourceDir, "topolo", "SKILL.md"))) {
6502
6641
  throw new Error("Bundled skill file not found. Reinstall the CLI: npm install -g @topolo/cli");
6503
6642
  }
6504
6643
  const mcp = resolveMcpCommand();
@@ -6539,7 +6678,7 @@ function assertAgentSetupSucceeded(results) {
6539
6678
  }
6540
6679
  async function agentSurfaceStatus(options = {}) {
6541
6680
  const homeDir = options.homeDir ?? homedir4();
6542
- const sourceSkill = await readFile6(join6(skillsSourceDir(), "topolo", "SKILL.md"), "utf8");
6681
+ const sourceSkill = await readFile6(join7(skillsSourceDir(), "topolo", "SKILL.md"), "utf8");
6543
6682
  const agents = resolveTargets({
6544
6683
  agent: options.agent,
6545
6684
  all: options.all,
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.10",
5
- "topoloPublishedVersion": "0.12.10",
4
+ "version": "0.12.11",
5
+ "topoloPublishedVersion": "0.12.11",
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.2",
32
- "@topolo/sdk": "0.21.2",
31
+ "@topolo/mcp": "0.14.4",
32
+ "@topolo/sdk": "0.21.4",
33
33
  "commander": "^12.1.0"
34
34
  },
35
35
  "devDependencies": {
@@ -252,6 +252,24 @@ If verification fails:
252
252
 
253
253
  Never improvise a rollback.
254
254
 
255
+ ## Protected action credential handoff
256
+
257
+ Local CLI and stdio MCP replace returned action credentials with opaque
258
+ `topolo-secret:<id>` references backed by the OS protected credential store.
259
+ Pass the reference unchanged into the next published action input; the SDK
260
+ resolves nonempty string credentials internally before validation and execution;
261
+ non-string or empty credentials fail closed. Never extract the
262
+ underlying token or put it in a file, terminal command, message, or log.
263
+
264
+ References are bound to the verified principal, organization, API key identity
265
+ when present, and environment. They expire after 30 minutes; access to an
266
+ expired reference deletes its local entry. Delete references once the workflow
267
+ finishes using `topolo actions release-secret <reference> --confirm --json`,
268
+ or `topolo_release_action_secret` with `confirm: true` in stdio MCP.
269
+ Local deletion does not revoke the upstream credential; use its published
270
+ revocation action separately when required. Other SDK/remote hosts keep
271
+ redaction unless they explicitly provide a protected store.
272
+
255
273
  ## Generic public API escape hatch
256
274
 
257
275
  Only when public docs provide a complete contract: