@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 +381 -242
- package/package.json +4 -4
- package/skills/topolo/SKILL.md +18 -0
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.
|
|
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 (
|
|
578
|
-
if (
|
|
579
|
-
if (
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
816
|
+
if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, token)) return null;
|
|
739
817
|
current = current[token];
|
|
740
818
|
}
|
|
741
|
-
return
|
|
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
|
|
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
|
|
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 (!
|
|
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) =>
|
|
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 =
|
|
1154
|
+
target = isRecord3(target) ? target[segment] : void 0;
|
|
1077
1155
|
}
|
|
1078
1156
|
const finalSegment = segments.at(-1);
|
|
1079
|
-
if (finalSegment &&
|
|
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 (!
|
|
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 (!
|
|
1189
|
+
if (!isRecord3(value)) return;
|
|
1112
1190
|
for (const [key, item] of Object.entries(value)) {
|
|
1113
|
-
if (key.toLowerCase() === "headers" &&
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 =
|
|
1702
|
-
if (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
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 (!
|
|
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 (!
|
|
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:
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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.
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3897
|
+
"--label=Topolo protected credential",
|
|
3881
3898
|
"service",
|
|
3882
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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" ?
|
|
3973
|
-
return
|
|
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
|
|
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
|
-
|
|
4383
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
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 ?
|
|
5918
|
+
return projectDir ? join6(projectDir, ".claude", "skills", "topolo", "SKILL.md") : join6(homeDir, ".claude", "skills", "topolo", "SKILL.md");
|
|
5780
5919
|
case "claude-desktop":
|
|
5781
|
-
return
|
|
5920
|
+
return join6(homeDir, ".claude", "skills", "topolo", "SKILL.md");
|
|
5782
5921
|
case "codex":
|
|
5783
|
-
return
|
|
5922
|
+
return join6(homeDir, ".codex", "skills", "topolo", "SKILL.md");
|
|
5784
5923
|
case "cursor":
|
|
5785
|
-
return
|
|
5924
|
+
return join6(projectDir ?? homeDir, ".cursor", "rules", "topolo.mdc");
|
|
5786
5925
|
case "grok":
|
|
5787
|
-
return
|
|
5926
|
+
return join6(projectDir ?? homeDir, ".grok", "skills", "topolo", "SKILL.md");
|
|
5788
5927
|
case "gemini":
|
|
5789
|
-
return
|
|
5928
|
+
return join6(projectDir ?? homeDir, ".gemini", "skills", "topolo", "SKILL.md");
|
|
5790
5929
|
case "copilot":
|
|
5791
|
-
return projectDir ?
|
|
5930
|
+
return projectDir ? join6(projectDir, ".github", "skills", "topolo", "SKILL.md") : join6(homeDir, ".copilot", "skills", "topolo", "SKILL.md");
|
|
5792
5931
|
case "windsurf":
|
|
5793
|
-
return projectDir ?
|
|
5932
|
+
return projectDir ? join6(projectDir, ".windsurf", "rules", "topolo.md") : void 0;
|
|
5794
5933
|
case "cline":
|
|
5795
|
-
return
|
|
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:
|
|
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:
|
|
5951
|
+
return [{ path: join6(homeDir, ".codex", "config.toml"), format: "toml" }];
|
|
5813
5952
|
case "cursor":
|
|
5814
5953
|
return [{
|
|
5815
|
-
path: projectDir ?
|
|
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 ?
|
|
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 ?
|
|
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:
|
|
5833
|
-
{ path:
|
|
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:
|
|
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:
|
|
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 ?
|
|
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:
|
|
5994
|
+
return [{ path: join6(homeDir, ".config", "goose", "config.yaml"), format: "yaml" }];
|
|
5856
5995
|
case "amazon-q":
|
|
5857
5996
|
if (projectDir) {
|
|
5858
|
-
return [{ path:
|
|
5997
|
+
return [{ path: join6(projectDir, ".amazonq", "mcp.json"), format: "json", rootKey: "mcpServers" }];
|
|
5859
5998
|
}
|
|
5860
5999
|
return [{
|
|
5861
|
-
path: existsSync3(
|
|
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
|
|
5898
|
-
if (process.platform === "win32") return
|
|
5899
|
-
return
|
|
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
|
|
5903
|
-
if (process.platform === "win32") return
|
|
5904
|
-
return
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
5
|
-
"topoloPublishedVersion": "0.12.
|
|
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.
|
|
32
|
-
"@topolo/sdk": "0.21.
|
|
31
|
+
"@topolo/mcp": "0.14.4",
|
|
32
|
+
"@topolo/sdk": "0.21.4",
|
|
33
33
|
"commander": "^12.1.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
package/skills/topolo/SKILL.md
CHANGED
|
@@ -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:
|