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