@topolo/cli 0.13.17 → 0.13.19
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/README.md +18 -4
- package/agent-packs/README.md +48 -0
- package/agent-packs/coverage_changed.py +1 -1
- package/agent-packs/src/topolo_agent_pack/cli.py +7 -2
- package/agent-packs/src/topolo_agent_pack/execution.py +1 -1
- package/agent-packs/src/topolo_agent_pack/fresh.py +1 -1
- package/agent-packs/src/topolo_agent_pack/policy.py +8 -0
- package/agent-packs/src/topolo_agent_pack/task_models.py +11 -7
- package/agent-packs/src/topolo_agent_pack/throughput.py +149 -0
- package/dist/index.js +321 -156
- package/package.json +8 -6
package/dist/index.js
CHANGED
|
@@ -108,7 +108,7 @@ function registerAgentPacksCommand(program2) {
|
|
|
108
108
|
if (name === "approvals") command.option("--connection <id>", "Exact private dynamic lane control connection").option("--intake <id>", "Exact owning native intake identity").option("--fingerprint <hash>", "Exact pending native request fingerprint").option("--response <path>", "Recorded one-request response JSON").option("--human-decision-recorded", "Assert that the exact human decision is recorded in the intake");
|
|
109
109
|
command.option("--harness <name>", "Local host adapter (codex, claude Desktop Code sessions)", "codex").option("--adapter-help", "Show every option supported by the selected adapter").argument("[arguments...]").allowUnknownOption().allowExcessArguments().addHelpText("after", name === "add" ? "\nExamples:\n topolo agent-packs add founder\n topolo agent-packs add founder --group specialists\n topolo agent-packs add founder --agent strategy\n" : "\nRun topolo agent-packs <command> --adapter-help for all adapter options. Cloud commands require explicit --environment and --context-key.\n").action(async (args, options) => {
|
|
110
110
|
if (!["codex", "claude"].includes(options.harness)) throw new Error(`The ${options.harness} pack adapter is not available. Supported adapters: codex, claude.`);
|
|
111
|
-
if (name === "lane" && options.harness === "claude" && !options.adapterHelp && !["status", "models-template", "models-install"].includes(args[0] ?? "")) {
|
|
111
|
+
if (name === "lane" && options.harness === "claude" && !options.adapterHelp && !["allocate", "status", "models-template", "models-install"].includes(args[0] ?? "")) {
|
|
112
112
|
if (options.connection) {
|
|
113
113
|
if (args.length !== 1 || !["read", "context-start", "context-read", "fixture-start", "fixture-read", "retire", "close"].includes(args[0]))
|
|
114
114
|
throw new Error("Closed existing Claude lane control operation required.");
|
|
@@ -226,12 +226,10 @@ function registerAgentPacksCommand(program2) {
|
|
|
226
226
|
import { Command, Help } from "commander";
|
|
227
227
|
|
|
228
228
|
// src/commands/auth.ts
|
|
229
|
-
import { spawn } from "child_process";
|
|
230
|
-
import { platform as platform2 } from "os";
|
|
231
229
|
import { createInterface } from "readline/promises";
|
|
232
230
|
import { stdin as input } from "process";
|
|
233
231
|
|
|
234
|
-
// node_modules/.pnpm/@topolo+sdk@0.23.
|
|
232
|
+
// node_modules/.pnpm/@topolo+sdk@0.23.24/node_modules/@topolo/sdk/dist/index.js
|
|
235
233
|
function record(value) {
|
|
236
234
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
237
235
|
}
|
|
@@ -249,6 +247,56 @@ function validTopoloActionAccessPolicy(value) {
|
|
|
249
247
|
}
|
|
250
248
|
return true;
|
|
251
249
|
}
|
|
250
|
+
function record2(value) {
|
|
251
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
252
|
+
}
|
|
253
|
+
function text(value, limit = 512) {
|
|
254
|
+
return typeof value === "string" && value.trim() ? value.trim().slice(0, limit) : null;
|
|
255
|
+
}
|
|
256
|
+
function strings2(value) {
|
|
257
|
+
return Array.isArray(value) ? value.slice(0, 8).flatMap((item) => {
|
|
258
|
+
const result = text(item);
|
|
259
|
+
return result ? [result] : [];
|
|
260
|
+
}) : [];
|
|
261
|
+
}
|
|
262
|
+
function pairs(value, first, second) {
|
|
263
|
+
return Array.isArray(value) ? value.slice(0, 8).flatMap((item) => {
|
|
264
|
+
if (!record2(item)) return [];
|
|
265
|
+
const a = text(item[first], 160);
|
|
266
|
+
const b = text(item[second]);
|
|
267
|
+
return a && b ? [{ [first]: a, [second]: b }] : [];
|
|
268
|
+
}) : [];
|
|
269
|
+
}
|
|
270
|
+
function docs(value) {
|
|
271
|
+
const result = text(value, 2048);
|
|
272
|
+
if (!result) return null;
|
|
273
|
+
try {
|
|
274
|
+
const url = new URL(result);
|
|
275
|
+
return url.protocol === "https:" && !url.username && !url.password ? url.href : null;
|
|
276
|
+
} catch {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function normalizeApplicationGuide(value) {
|
|
281
|
+
if (!record2(value) || value.schemaVersion !== 1 || value.source !== "published_application_manifest") return null;
|
|
282
|
+
const purpose = text(value.purpose, 2048);
|
|
283
|
+
const gettingStarted = strings2(value.gettingStarted);
|
|
284
|
+
const docsUrl = docs(value.docsUrl);
|
|
285
|
+
return {
|
|
286
|
+
schemaVersion: 1,
|
|
287
|
+
source: "published_application_manifest",
|
|
288
|
+
publishedAt: text(value.publishedAt, 160),
|
|
289
|
+
purpose,
|
|
290
|
+
problemStatement: text(value.problemStatement, 2048),
|
|
291
|
+
solutionStatement: text(value.solutionStatement, 2048),
|
|
292
|
+
benefits: strings2(value.benefits),
|
|
293
|
+
features: pairs(value.features, "title", "description"),
|
|
294
|
+
gettingStarted,
|
|
295
|
+
worksWellWith: pairs(value.worksWellWith, "appId", "reason"),
|
|
296
|
+
docsUrl,
|
|
297
|
+
missingFields: [...!purpose ? ["purpose"] : [], ...!docsUrl ? ["docsUrl"] : [], ...!gettingStarted.length ? ["gettingStarted"] : []]
|
|
298
|
+
};
|
|
299
|
+
}
|
|
252
300
|
function applyAuthHeaders(headers, credential) {
|
|
253
301
|
if (credential.kind === "api_key") {
|
|
254
302
|
headers.set("X-Api-Key", credential.apiKey);
|
|
@@ -364,9 +412,9 @@ function canonicalTopoloServiceErrorCode(value) {
|
|
|
364
412
|
}
|
|
365
413
|
function sanitizeTopoloServiceMessage(value) {
|
|
366
414
|
if (typeof value !== "string") return void 0;
|
|
367
|
-
const
|
|
368
|
-
if (!
|
|
369
|
-
return
|
|
415
|
+
const text4 = 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();
|
|
416
|
+
if (!text4) return void 0;
|
|
417
|
+
return text4.length > SERVICE_MESSAGE_MAX_LENGTH ? `${text4.slice(0, SERVICE_MESSAGE_MAX_LENGTH - 1).trimEnd()}\u2026` : text4;
|
|
370
418
|
}
|
|
371
419
|
function canonicalTopoloRequestId(value) {
|
|
372
420
|
if (typeof value !== "string") return void 0;
|
|
@@ -434,10 +482,10 @@ var TopoloPermissionError = class extends TopoloSdkError {
|
|
|
434
482
|
};
|
|
435
483
|
function extractTopoloServiceErrorCode(body) {
|
|
436
484
|
if (!body || typeof body !== "object" || Array.isArray(body)) return null;
|
|
437
|
-
const
|
|
438
|
-
const topLevel = canonicalTopoloServiceErrorCode(
|
|
485
|
+
const record5 = body;
|
|
486
|
+
const topLevel = canonicalTopoloServiceErrorCode(record5.code);
|
|
439
487
|
if (topLevel) return topLevel;
|
|
440
|
-
const nested =
|
|
488
|
+
const nested = record5.error;
|
|
441
489
|
const nestedString = canonicalTopoloServiceErrorCode(nested);
|
|
442
490
|
if (nestedString) return nestedString;
|
|
443
491
|
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
@@ -449,13 +497,13 @@ function extractTopoloServiceErrorCode(body) {
|
|
|
449
497
|
}
|
|
450
498
|
function extractTopoloServiceErrorMessage(body) {
|
|
451
499
|
if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
|
|
452
|
-
const
|
|
453
|
-
const nested =
|
|
500
|
+
const record5 = body;
|
|
501
|
+
const nested = record5.error;
|
|
454
502
|
const candidates = [];
|
|
455
503
|
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
456
504
|
candidates.push(nested.message);
|
|
457
505
|
}
|
|
458
|
-
candidates.push(
|
|
506
|
+
candidates.push(record5.message, record5.error_description);
|
|
459
507
|
if (typeof nested === "string" && !canonicalTopoloServiceErrorCode(nested)) candidates.push(nested);
|
|
460
508
|
for (const candidate of candidates) {
|
|
461
509
|
const message3 = sanitizeTopoloServiceMessage(candidate);
|
|
@@ -468,14 +516,14 @@ function serviceErrorCode(body, fallback) {
|
|
|
468
516
|
}
|
|
469
517
|
function extractTopoloServiceErrorDetails(body) {
|
|
470
518
|
if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
|
|
471
|
-
const
|
|
472
|
-
let candidate =
|
|
473
|
-
const nested =
|
|
519
|
+
const record5 = body;
|
|
520
|
+
let candidate = record5.details;
|
|
521
|
+
const nested = record5.error;
|
|
474
522
|
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
475
523
|
const nestedRecord = nested;
|
|
476
524
|
if (candidate === void 0) candidate = nestedRecord.details ?? nestedRecord;
|
|
477
525
|
}
|
|
478
|
-
if (candidate === void 0) candidate =
|
|
526
|
+
if (candidate === void 0) candidate = record5;
|
|
479
527
|
if (Array.isArray(candidate)) return validationIssueDetails(canonicalTopoloValidationIssues(candidate)) ?? void 0;
|
|
480
528
|
if (!candidate || typeof candidate !== "object") return void 0;
|
|
481
529
|
const source = candidate;
|
|
@@ -534,18 +582,18 @@ function validationIssueDetails(issues) {
|
|
|
534
582
|
return paths.length ? { paths, issues } : { issues };
|
|
535
583
|
}
|
|
536
584
|
function issue(path, message3) {
|
|
537
|
-
const
|
|
538
|
-
if (!path && !
|
|
539
|
-
return { ...path ? { path } : {}, ...
|
|
585
|
+
const text4 = sanitizeTopoloServiceMessage(message3);
|
|
586
|
+
if (!path && !text4) return null;
|
|
587
|
+
return { ...path ? { path } : {}, ...text4 ? { message: text4 } : {} };
|
|
540
588
|
}
|
|
541
589
|
function canonicalTopoloValidationIssues(values) {
|
|
542
590
|
const issues = [];
|
|
543
591
|
for (const value of values) {
|
|
544
592
|
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
545
|
-
const
|
|
546
|
-
const path = canonicalTopoloValidationPath(
|
|
593
|
+
const record5 = value;
|
|
594
|
+
const path = canonicalTopoloValidationPath(record5.field) ?? canonicalTopoloValidationPath(record5.path);
|
|
547
595
|
if (!path) continue;
|
|
548
|
-
const projected = issue(path,
|
|
596
|
+
const projected = issue(path, record5.message);
|
|
549
597
|
if (projected) issues.push(projected);
|
|
550
598
|
if (issues.length === SERVICE_ISSUE_LIMIT) break;
|
|
551
599
|
}
|
|
@@ -699,8 +747,8 @@ function sensitive(key, value) {
|
|
|
699
747
|
const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
700
748
|
if (normalized === "capability") {
|
|
701
749
|
if (typeof value !== "string") return value !== null && value !== void 0 && typeof value !== "boolean" && typeof value !== "number";
|
|
702
|
-
const
|
|
703
|
-
return Boolean(
|
|
750
|
+
const text4 = value.trim();
|
|
751
|
+
return Boolean(text4) && !/^[a-z][a-z0-9_-]{0,31}$/.test(text4);
|
|
704
752
|
}
|
|
705
753
|
return ["authorization", "cookie", "setcookie", "proxyauthorization"].includes(normalized) || /(token|secret|password|privatekey|apikey|backupcodes?|recoverycodes?)$/.test(normalized);
|
|
706
754
|
}
|
|
@@ -1269,7 +1317,7 @@ var ActorEnvelopeValidationError = class extends Error {
|
|
|
1269
1317
|
}
|
|
1270
1318
|
};
|
|
1271
1319
|
function parseActorEnvelope(value) {
|
|
1272
|
-
const envelope =
|
|
1320
|
+
const envelope = record3(value, "actorEnvelope");
|
|
1273
1321
|
exactKeys(envelope, [
|
|
1274
1322
|
"schemaVersion",
|
|
1275
1323
|
"organizationId",
|
|
@@ -1287,28 +1335,28 @@ function parseActorEnvelope(value) {
|
|
|
1287
1335
|
}
|
|
1288
1336
|
const accountableSubject = parseAccountableSubject(envelope.accountableSubject);
|
|
1289
1337
|
const performer = parseIdentity(envelope.performer, "actorEnvelope.performer");
|
|
1290
|
-
const organizationId =
|
|
1338
|
+
const organizationId = text2(envelope.organizationId, "actorEnvelope.organizationId");
|
|
1291
1339
|
const channel = member(envelope.channel, CHANNELS, "actorEnvelope.channel");
|
|
1292
1340
|
const issuedAt = timestamp(envelope.issuedAt, "actorEnvelope.issuedAt");
|
|
1293
1341
|
const expiresAt = envelope.expiresAt === void 0 ? void 0 : timestamp(envelope.expiresAt, "actorEnvelope.expiresAt");
|
|
1294
1342
|
if (expiresAt && Date.parse(expiresAt) <= Date.parse(issuedAt)) {
|
|
1295
1343
|
fail("actorEnvelope.expiresAt", "must be later than issuedAt");
|
|
1296
1344
|
}
|
|
1297
|
-
const assuranceValue =
|
|
1345
|
+
const assuranceValue = record3(envelope.assurance, "actorEnvelope.assurance");
|
|
1298
1346
|
exactKeys(assuranceValue, ["level", "methods"], "actorEnvelope.assurance");
|
|
1299
1347
|
const assurance = {
|
|
1300
1348
|
level: member(assuranceValue.level, ASSURANCE_LEVELS, "actorEnvelope.assurance.level"),
|
|
1301
1349
|
methods: textArray(assuranceValue.methods, "actorEnvelope.assurance.methods")
|
|
1302
1350
|
};
|
|
1303
|
-
const actionValue =
|
|
1351
|
+
const actionValue = record3(envelope.action, "actorEnvelope.action");
|
|
1304
1352
|
exactKeys(actionValue, ["requestId", "actionId", "resourceType", "resourceId", "inputHash"], "actorEnvelope.action");
|
|
1305
|
-
const inputHash =
|
|
1353
|
+
const inputHash = text2(actionValue.inputHash, "actorEnvelope.action.inputHash");
|
|
1306
1354
|
if (!HASH_PATTERN.test(inputHash)) fail("actorEnvelope.action.inputHash", "must be a lowercase sha256 digest");
|
|
1307
1355
|
const action = {
|
|
1308
|
-
requestId:
|
|
1309
|
-
actionId:
|
|
1310
|
-
...actionValue.resourceType === void 0 ? {} : { resourceType:
|
|
1311
|
-
...actionValue.resourceId === void 0 ? {} : { resourceId:
|
|
1356
|
+
requestId: text2(actionValue.requestId, "actorEnvelope.action.requestId"),
|
|
1357
|
+
actionId: text2(actionValue.actionId, "actorEnvelope.action.actionId"),
|
|
1358
|
+
...actionValue.resourceType === void 0 ? {} : { resourceType: text2(actionValue.resourceType, "actorEnvelope.action.resourceType") },
|
|
1359
|
+
...actionValue.resourceId === void 0 ? {} : { resourceId: text2(actionValue.resourceId, "actorEnvelope.action.resourceId") },
|
|
1312
1360
|
inputHash
|
|
1313
1361
|
};
|
|
1314
1362
|
if (action.resourceType === void 0 !== (action.resourceId === void 0)) {
|
|
@@ -1370,12 +1418,12 @@ function canonicalJson(value) {
|
|
|
1370
1418
|
}
|
|
1371
1419
|
function parseAccountableSubject(value) {
|
|
1372
1420
|
const path = "actorEnvelope.accountableSubject";
|
|
1373
|
-
const subject =
|
|
1421
|
+
const subject = record3(value, path);
|
|
1374
1422
|
exactKeys(subject, ["principalType", "principalId", "displayName"], path);
|
|
1375
1423
|
return {
|
|
1376
1424
|
principalType: member(subject.principalType, ACCOUNTABLE_SUBJECT_TYPES, `${path}.principalType`),
|
|
1377
|
-
principalId:
|
|
1378
|
-
...subject.displayName === void 0 ? {} : { displayName:
|
|
1425
|
+
principalId: text2(subject.principalId, `${path}.principalId`),
|
|
1426
|
+
...subject.displayName === void 0 ? {} : { displayName: text2(subject.displayName, `${path}.displayName`) }
|
|
1379
1427
|
};
|
|
1380
1428
|
}
|
|
1381
1429
|
async function sha256CanonicalValue(value) {
|
|
@@ -1384,7 +1432,7 @@ async function sha256CanonicalValue(value) {
|
|
|
1384
1432
|
return `sha256:${Array.from(new Uint8Array(digest2), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
1385
1433
|
}
|
|
1386
1434
|
function parseIdentity(value, path) {
|
|
1387
|
-
const identity =
|
|
1435
|
+
const identity = record3(value, path);
|
|
1388
1436
|
exactKeys(identity, [
|
|
1389
1437
|
"principalType",
|
|
1390
1438
|
"principalId",
|
|
@@ -1397,7 +1445,7 @@ function parseIdentity(value, path) {
|
|
|
1397
1445
|
], path);
|
|
1398
1446
|
return {
|
|
1399
1447
|
principalType: member(identity.principalType, PRINCIPAL_TYPES, `${path}.principalType`),
|
|
1400
|
-
principalId:
|
|
1448
|
+
principalId: text2(identity.principalId, `${path}.principalId`),
|
|
1401
1449
|
...optionalTextFields(identity, path, [
|
|
1402
1450
|
"displayName",
|
|
1403
1451
|
"installationId",
|
|
@@ -1410,25 +1458,25 @@ function parseIdentity(value, path) {
|
|
|
1410
1458
|
}
|
|
1411
1459
|
function parseDelegation(value) {
|
|
1412
1460
|
if (value === null) return null;
|
|
1413
|
-
const delegation =
|
|
1461
|
+
const delegation = record3(value, "actorEnvelope.delegation");
|
|
1414
1462
|
exactKeys(delegation, ["taskId", "runId", "lineage"], "actorEnvelope.delegation");
|
|
1415
1463
|
if (!Array.isArray(delegation.lineage) || delegation.lineage.length === 0 || delegation.lineage.length > 16) {
|
|
1416
1464
|
fail("actorEnvelope.delegation.lineage", "must contain between 1 and 16 hops");
|
|
1417
1465
|
}
|
|
1418
1466
|
return {
|
|
1419
|
-
taskId:
|
|
1420
|
-
runId:
|
|
1467
|
+
taskId: text2(delegation.taskId, "actorEnvelope.delegation.taskId"),
|
|
1468
|
+
runId: text2(delegation.runId, "actorEnvelope.delegation.runId"),
|
|
1421
1469
|
lineage: delegation.lineage.map((value2, index) => {
|
|
1422
|
-
const hop =
|
|
1470
|
+
const hop = record3(value2, `actorEnvelope.delegation.lineage[${index}]`);
|
|
1423
1471
|
exactKeys(hop, ["principalType", "principalId"], `actorEnvelope.delegation.lineage[${index}]`);
|
|
1424
1472
|
return {
|
|
1425
1473
|
principalType: member(hop.principalType, PRINCIPAL_TYPES, `actorEnvelope.delegation.lineage[${index}].principalType`),
|
|
1426
|
-
principalId:
|
|
1474
|
+
principalId: text2(hop.principalId, `actorEnvelope.delegation.lineage[${index}].principalId`)
|
|
1427
1475
|
};
|
|
1428
1476
|
})
|
|
1429
1477
|
};
|
|
1430
1478
|
}
|
|
1431
|
-
function
|
|
1479
|
+
function record3(value, path) {
|
|
1432
1480
|
if (!value || typeof value !== "object" || Array.isArray(value)) fail(path, "must be an object");
|
|
1433
1481
|
return value;
|
|
1434
1482
|
}
|
|
@@ -1436,14 +1484,14 @@ function exactKeys(value, allowed, path) {
|
|
|
1436
1484
|
const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
|
|
1437
1485
|
if (unknown.length) fail(path, `contains unknown field ${unknown[0]}`);
|
|
1438
1486
|
}
|
|
1439
|
-
function
|
|
1487
|
+
function text2(value, path) {
|
|
1440
1488
|
if (typeof value !== "string" || value.trim() !== value || value.length === 0 || value.length > 256) {
|
|
1441
1489
|
fail(path, "must be a non-empty trimmed string of at most 256 characters");
|
|
1442
1490
|
}
|
|
1443
1491
|
return value;
|
|
1444
1492
|
}
|
|
1445
1493
|
function timestamp(value, path) {
|
|
1446
|
-
const result =
|
|
1494
|
+
const result = text2(value, path);
|
|
1447
1495
|
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(result) || Number.isNaN(Date.parse(result))) {
|
|
1448
1496
|
fail(path, "must be an RFC 3339 UTC timestamp");
|
|
1449
1497
|
}
|
|
@@ -1455,10 +1503,10 @@ function member(value, values, path) {
|
|
|
1455
1503
|
}
|
|
1456
1504
|
function textArray(value, path) {
|
|
1457
1505
|
if (!Array.isArray(value) || value.length === 0 || value.length > 16) fail(path, "must contain between 1 and 16 values");
|
|
1458
|
-
return value.map((item, index) =>
|
|
1506
|
+
return value.map((item, index) => text2(item, `${path}[${index}]`));
|
|
1459
1507
|
}
|
|
1460
1508
|
function optionalTextFields(value, path, keys) {
|
|
1461
|
-
return Object.fromEntries(keys.flatMap((key) => value[key] === void 0 ? [] : [[key,
|
|
1509
|
+
return Object.fromEntries(keys.flatMap((key) => value[key] === void 0 ? [] : [[key, text2(value[key], `${path}.${key}`)]]));
|
|
1462
1510
|
}
|
|
1463
1511
|
function fail(path, message3) {
|
|
1464
1512
|
throw new ActorEnvelopeValidationError(path, message3);
|
|
@@ -2128,6 +2176,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2128
2176
|
for (const item of raw) {
|
|
2129
2177
|
if (!isRecord4(item)) continue;
|
|
2130
2178
|
const s = item;
|
|
2179
|
+
const applicationGuide = normalizeApplicationGuide(s.applicationGuide);
|
|
2131
2180
|
const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
|
|
2132
2181
|
if (!appId) continue;
|
|
2133
2182
|
const permissions = Array.isArray(s.granted_permissions) ? s.granted_permissions.filter((permission) => typeof permission === "string" && permission.trim().length > 0) : [];
|
|
@@ -2141,6 +2190,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2141
2190
|
slug: typeof s.app_slug === "string" ? s.app_slug : null,
|
|
2142
2191
|
name: typeof s.name === "string" ? s.name : null,
|
|
2143
2192
|
description: typeof s.description === "string" ? s.description : null,
|
|
2193
|
+
...applicationGuide ? { applicationGuide } : {},
|
|
2144
2194
|
apiBaseUrl: typeof s.api_base_url === "string" ? s.api_base_url : typeof s.apiBaseUrl === "string" ? s.apiBaseUrl : null,
|
|
2145
2195
|
launchUrl: typeof s.launch_url === "string" ? s.launch_url : typeof s.launchUrl === "string" ? s.launchUrl : null,
|
|
2146
2196
|
status: typeof s.status === "string" ? s.status : null,
|
|
@@ -2260,14 +2310,14 @@ var TopoloClient = class _TopoloClient {
|
|
|
2260
2310
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
2261
2311
|
throw new TopoloSdkError("action_catalog_summary_unavailable", "Action catalog summary is unavailable.");
|
|
2262
2312
|
}
|
|
2263
|
-
const
|
|
2264
|
-
if (
|
|
2313
|
+
const record5 = raw;
|
|
2314
|
+
if (record5.algorithm !== "sha256-v1" || typeof record5.digest !== "string" || !/^[a-f0-9]{64}$/.test(record5.digest) || !Number.isSafeInteger(record5.actionCount) || Number(record5.actionCount) < 0) {
|
|
2265
2315
|
throw new TopoloSdkError("action_catalog_summary_unavailable", "Action catalog summary is invalid.");
|
|
2266
2316
|
}
|
|
2267
2317
|
return {
|
|
2268
2318
|
algorithm: "sha256-v1",
|
|
2269
|
-
digest:
|
|
2270
|
-
actionCount: Number(
|
|
2319
|
+
digest: record5.digest,
|
|
2320
|
+
actionCount: Number(record5.actionCount)
|
|
2271
2321
|
};
|
|
2272
2322
|
}
|
|
2273
2323
|
/** @deprecated Prefer searchActions() so callers retain the continuation cursor. */
|
|
@@ -2814,6 +2864,7 @@ function normalizeCatalogPage(value) {
|
|
|
2814
2864
|
}
|
|
2815
2865
|
function normalizeServiceCatalogEntry(item) {
|
|
2816
2866
|
if (!isRecord4(item)) return null;
|
|
2867
|
+
const applicationGuide = normalizeApplicationGuide(item.applicationGuide);
|
|
2817
2868
|
const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
|
|
2818
2869
|
if (!appId) return null;
|
|
2819
2870
|
const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
|
|
@@ -2829,6 +2880,7 @@ function normalizeServiceCatalogEntry(item) {
|
|
|
2829
2880
|
slug: typeof item.app_slug === "string" ? item.app_slug : null,
|
|
2830
2881
|
name: typeof item.name === "string" ? item.name : null,
|
|
2831
2882
|
description: typeof item.description === "string" ? item.description : null,
|
|
2883
|
+
...applicationGuide ? { applicationGuide } : {},
|
|
2832
2884
|
apiBaseUrl: typeof item.api_base_url === "string" ? item.api_base_url : typeof item.apiBaseUrl === "string" ? item.apiBaseUrl : null,
|
|
2833
2885
|
launchUrl: typeof item.launch_url === "string" ? item.launch_url : typeof item.launchUrl === "string" ? item.launchUrl : null,
|
|
2834
2886
|
status: typeof item.status === "string" ? item.status : null,
|
|
@@ -2991,10 +3043,10 @@ function extractRequired(body) {
|
|
|
2991
3043
|
if (Array.isArray(maybe.required)) return canonicalTopoloRequired(maybe.required);
|
|
2992
3044
|
for (const nested of [maybe.details, maybe.error]) {
|
|
2993
3045
|
if (!nested || typeof nested !== "object" || Array.isArray(nested)) continue;
|
|
2994
|
-
const
|
|
2995
|
-
if (Array.isArray(
|
|
2996
|
-
if (
|
|
2997
|
-
const required =
|
|
3046
|
+
const record5 = nested;
|
|
3047
|
+
if (Array.isArray(record5.required)) return canonicalTopoloRequired(record5.required);
|
|
3048
|
+
if (record5.details && typeof record5.details === "object" && !Array.isArray(record5.details)) {
|
|
3049
|
+
const required = record5.details.required;
|
|
2998
3050
|
if (Array.isArray(required)) return canonicalTopoloRequired(required);
|
|
2999
3051
|
}
|
|
3000
3052
|
}
|
|
@@ -3071,8 +3123,8 @@ function compactResultValue(value, depth, budget) {
|
|
|
3071
3123
|
return value.length > ACTION_RESULT_MAX_ITEMS ? { count: value.length, items, omittedItems: value.length - ACTION_RESULT_MAX_ITEMS } : items;
|
|
3072
3124
|
}
|
|
3073
3125
|
if (typeof value === "object") {
|
|
3074
|
-
const
|
|
3075
|
-
const allKeys = Object.keys(
|
|
3126
|
+
const record5 = value;
|
|
3127
|
+
const allKeys = Object.keys(record5);
|
|
3076
3128
|
const ranked = allKeys.sort((left, right) => resultKeyPriority(left) - resultKeyPriority(right));
|
|
3077
3129
|
const prioritized = ranked.filter((key) => resultKeyPriority(key) < 2);
|
|
3078
3130
|
const ordinary = ranked.filter((key) => resultKeyPriority(key) === 2);
|
|
@@ -3081,7 +3133,7 @@ function compactResultValue(value, depth, budget) {
|
|
|
3081
3133
|
...prioritized,
|
|
3082
3134
|
...ordinary.slice(0, ordinaryLimit)
|
|
3083
3135
|
].slice(0, ACTION_RESULT_MAX_KEYS);
|
|
3084
|
-
const compact = Object.fromEntries(keys.map((key) => [key, compactResultValue(
|
|
3136
|
+
const compact = Object.fromEntries(keys.map((key) => [key, compactResultValue(record5[key], depth + 1, budget)]));
|
|
3085
3137
|
if (allKeys.length > keys.length) compact["_omittedKeys"] = allKeys.length - keys.length;
|
|
3086
3138
|
return compact;
|
|
3087
3139
|
}
|
|
@@ -3886,12 +3938,32 @@ async function pollUntilApproved(params) {
|
|
|
3886
3938
|
throw new Error("Device code expired. Run `topolo auth login` again.");
|
|
3887
3939
|
}
|
|
3888
3940
|
|
|
3889
|
-
//
|
|
3941
|
+
// src/lib/open-browser.ts
|
|
3942
|
+
import { spawn } from "child_process";
|
|
3943
|
+
import { platform } from "os";
|
|
3944
|
+
function tryOpenBrowser(url) {
|
|
3945
|
+
try {
|
|
3946
|
+
const os = platform();
|
|
3947
|
+
const cmd = os === "darwin" ? "open" : os === "win32" ? "start" : "xdg-open";
|
|
3948
|
+
const args = os === "win32" ? ["", url] : [url];
|
|
3949
|
+
const child = spawn(cmd, args, {
|
|
3950
|
+
detached: true,
|
|
3951
|
+
stdio: "ignore",
|
|
3952
|
+
...os === "win32" ? { shell: true } : {}
|
|
3953
|
+
});
|
|
3954
|
+
child.unref();
|
|
3955
|
+
return true;
|
|
3956
|
+
} catch {
|
|
3957
|
+
return false;
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
// node_modules/.pnpm/@topolo+sdk@0.23.24/node_modules/@topolo/sdk/dist/node/credential-store.js
|
|
3890
3962
|
import { mkdir as mkdir2, readFile as readFile2, chmod as chmod2, lstat, readdir as readdir2 } from "fs/promises";
|
|
3891
3963
|
import { existsSync as existsSync2, mkdirSync, lstatSync, readdirSync, readFileSync as readFileSync2, openSync, writeFileSync, fsyncSync, closeSync, renameSync, unlinkSync, rmdirSync, linkSync } from "fs";
|
|
3892
3964
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
|
|
3893
3965
|
import { constants, fstatSync, readSync } from "fs";
|
|
3894
|
-
import { homedir as homedir2, platform } from "os";
|
|
3966
|
+
import { homedir as homedir2, platform as platform2 } from "os";
|
|
3895
3967
|
import { dirname as dirname2, join as join3 } from "path";
|
|
3896
3968
|
import { createHash, randomUUID } from "crypto";
|
|
3897
3969
|
import { join as join2 } from "path";
|
|
@@ -3997,9 +4069,9 @@ function canonicalTopoloServiceErrorCode2(value) {
|
|
|
3997
4069
|
}
|
|
3998
4070
|
function sanitizeTopoloServiceMessage2(value) {
|
|
3999
4071
|
if (typeof value !== "string") return void 0;
|
|
4000
|
-
const
|
|
4001
|
-
if (!
|
|
4002
|
-
return
|
|
4072
|
+
const text3 = 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();
|
|
4073
|
+
if (!text3) return void 0;
|
|
4074
|
+
return text3.length > SERVICE_MESSAGE_MAX_LENGTH2 ? `${text3.slice(0, SERVICE_MESSAGE_MAX_LENGTH2 - 1).trimEnd()}\u2026` : text3;
|
|
4003
4075
|
}
|
|
4004
4076
|
function canonicalTopoloRequestId2(value) {
|
|
4005
4077
|
if (typeof value !== "string") return void 0;
|
|
@@ -4040,10 +4112,10 @@ var TopoloAuthError2 = class extends TopoloSdkError2 {
|
|
|
4040
4112
|
};
|
|
4041
4113
|
function extractTopoloServiceErrorCode2(body) {
|
|
4042
4114
|
if (!body || typeof body !== "object" || Array.isArray(body)) return null;
|
|
4043
|
-
const
|
|
4044
|
-
const topLevel = canonicalTopoloServiceErrorCode2(
|
|
4115
|
+
const record4 = body;
|
|
4116
|
+
const topLevel = canonicalTopoloServiceErrorCode2(record4.code);
|
|
4045
4117
|
if (topLevel) return topLevel;
|
|
4046
|
-
const nested =
|
|
4118
|
+
const nested = record4.error;
|
|
4047
4119
|
const nestedString = canonicalTopoloServiceErrorCode2(nested);
|
|
4048
4120
|
if (nestedString) return nestedString;
|
|
4049
4121
|
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
@@ -4058,14 +4130,14 @@ function serviceErrorCode2(body, fallback) {
|
|
|
4058
4130
|
}
|
|
4059
4131
|
function extractTopoloServiceErrorDetails2(body) {
|
|
4060
4132
|
if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
|
|
4061
|
-
const
|
|
4062
|
-
let candidate =
|
|
4063
|
-
const nested =
|
|
4133
|
+
const record4 = body;
|
|
4134
|
+
let candidate = record4.details;
|
|
4135
|
+
const nested = record4.error;
|
|
4064
4136
|
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
4065
4137
|
const nestedRecord = nested;
|
|
4066
4138
|
if (candidate === void 0) candidate = nestedRecord.details ?? nestedRecord;
|
|
4067
4139
|
}
|
|
4068
|
-
if (candidate === void 0) candidate =
|
|
4140
|
+
if (candidate === void 0) candidate = record4;
|
|
4069
4141
|
if (Array.isArray(candidate)) return validationIssueDetails2(canonicalTopoloValidationIssues2(candidate)) ?? void 0;
|
|
4070
4142
|
if (!candidate || typeof candidate !== "object") return void 0;
|
|
4071
4143
|
const source = candidate;
|
|
@@ -4124,18 +4196,18 @@ function validationIssueDetails2(issues) {
|
|
|
4124
4196
|
return paths.length ? { paths, issues } : { issues };
|
|
4125
4197
|
}
|
|
4126
4198
|
function issue2(path, message3) {
|
|
4127
|
-
const
|
|
4128
|
-
if (!path && !
|
|
4129
|
-
return { ...path ? { path } : {}, ...
|
|
4199
|
+
const text3 = sanitizeTopoloServiceMessage2(message3);
|
|
4200
|
+
if (!path && !text3) return null;
|
|
4201
|
+
return { ...path ? { path } : {}, ...text3 ? { message: text3 } : {} };
|
|
4130
4202
|
}
|
|
4131
4203
|
function canonicalTopoloValidationIssues2(values) {
|
|
4132
4204
|
const issues = [];
|
|
4133
4205
|
for (const value of values) {
|
|
4134
4206
|
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
4135
|
-
const
|
|
4136
|
-
const path = canonicalTopoloValidationPath2(
|
|
4207
|
+
const record4 = value;
|
|
4208
|
+
const path = canonicalTopoloValidationPath2(record4.field) ?? canonicalTopoloValidationPath2(record4.path);
|
|
4137
4209
|
if (!path) continue;
|
|
4138
|
-
const projected = issue2(path,
|
|
4210
|
+
const projected = issue2(path, record4.message);
|
|
4139
4211
|
if (projected) issues.push(projected);
|
|
4140
4212
|
if (issues.length === SERVICE_ISSUE_LIMIT2) break;
|
|
4141
4213
|
}
|
|
@@ -4572,18 +4644,18 @@ function createProtectedActionSecretStore(options) {
|
|
|
4572
4644
|
const key = await account(reference);
|
|
4573
4645
|
const encoded = await backend.get(key);
|
|
4574
4646
|
if (!encoded) throw new TopoloSdkError2("action_secret_unavailable", "Protected action reference is missing or belongs to another identity or environment.");
|
|
4575
|
-
let
|
|
4647
|
+
let record4;
|
|
4576
4648
|
try {
|
|
4577
|
-
|
|
4649
|
+
record4 = JSON.parse(encoded);
|
|
4578
4650
|
} catch {
|
|
4579
4651
|
throw new TopoloSdkError2("action_secret_invalid", "Protected action reference is corrupt.");
|
|
4580
4652
|
}
|
|
4581
|
-
if (!Number.isFinite(
|
|
4582
|
-
if (
|
|
4653
|
+
if (!Number.isFinite(record4?.expiresAt) || !Object.hasOwn(record4, "value")) throw new TopoloSdkError2("action_secret_invalid", "Protected action reference is corrupt.");
|
|
4654
|
+
if (record4.expiresAt <= now()) {
|
|
4583
4655
|
await backend.delete(key);
|
|
4584
4656
|
throw new TopoloSdkError2("action_secret_expired", "Protected action reference expired. Repeat the originating action if its contract permits.");
|
|
4585
4657
|
}
|
|
4586
|
-
return
|
|
4658
|
+
return record4.value;
|
|
4587
4659
|
},
|
|
4588
4660
|
async delete(reference) {
|
|
4589
4661
|
await backend.delete(await account(reference));
|
|
@@ -4659,7 +4731,7 @@ var ENVIRONMENT_DEFAULT_SERVICE_URLS = TOPOLO_ENVIRONMENT_SERVICE_URLS;
|
|
|
4659
4731
|
function configPath() {
|
|
4660
4732
|
const override = process.env.TOPOLO_CONFIG_PATH;
|
|
4661
4733
|
if (override) return override;
|
|
4662
|
-
const base = process.env.XDG_CONFIG_HOME || (
|
|
4734
|
+
const base = process.env.XDG_CONFIG_HOME || (platform2() === "win32" ? join3(homedir2(), "AppData", "Roaming") : join3(homedir2(), ".config"));
|
|
4663
4735
|
return join3(base, "topolo", "config.json");
|
|
4664
4736
|
}
|
|
4665
4737
|
async function readConfig() {
|
|
@@ -4678,7 +4750,7 @@ async function writeConfig(config) {
|
|
|
4678
4750
|
await withConfigWriteLock(async (_assertOwned, persist) => {
|
|
4679
4751
|
await persist((current) => protectStoredGrants(config, current), { allowCreate: true });
|
|
4680
4752
|
});
|
|
4681
|
-
if (
|
|
4753
|
+
if (platform2() !== "win32") {
|
|
4682
4754
|
try {
|
|
4683
4755
|
await chmod2(path, 384);
|
|
4684
4756
|
} catch {
|
|
@@ -5524,6 +5596,27 @@ async function continueReauthorization(environment, key) {
|
|
|
5524
5596
|
}
|
|
5525
5597
|
});
|
|
5526
5598
|
}
|
|
5599
|
+
async function awaitReauthorization(environment, key, options = {}) {
|
|
5600
|
+
const deadline = Date.now() + (options.timeoutMs ?? 15 * 6e4);
|
|
5601
|
+
let lastCode;
|
|
5602
|
+
for (; ; ) {
|
|
5603
|
+
const profile = profileForEnvironment(await readConfig(), environment);
|
|
5604
|
+
const slotKey = key ?? profile.activeContext ?? DEFAULT_SLOT;
|
|
5605
|
+
try {
|
|
5606
|
+
return await continueReauthorization(environment, slotKey);
|
|
5607
|
+
} catch (error) {
|
|
5608
|
+
if (!(error instanceof TopoloReauthRequiredError)) throw error;
|
|
5609
|
+
const prompt = error.reauthorization;
|
|
5610
|
+
if (prompt.userCode !== lastCode) {
|
|
5611
|
+
lastCode = prompt.userCode;
|
|
5612
|
+
options.onPrompt?.(error);
|
|
5613
|
+
}
|
|
5614
|
+
if (prompt.state !== "awaiting_approval" || options.signal?.aborted || Date.now() >= deadline) throw error;
|
|
5615
|
+
const waitMs = Math.max(1, prompt.pollIntervalSeconds ?? 5) * 1e3;
|
|
5616
|
+
await new Promise((resolve22) => setTimeout(resolve22, Math.min(waitMs, Math.max(0, deadline - Date.now()))));
|
|
5617
|
+
}
|
|
5618
|
+
}
|
|
5619
|
+
}
|
|
5527
5620
|
function resolveAgentName(configAgentName) {
|
|
5528
5621
|
return process.env.TOPOLO_AGENT_NAME ?? configAgentName;
|
|
5529
5622
|
}
|
|
@@ -5734,13 +5827,43 @@ function normalizeError(err) {
|
|
|
5734
5827
|
retryAfter: err.retryAfter,
|
|
5735
5828
|
// SDK HTTP errors already carry sanitized details; projecting them again
|
|
5736
5829
|
// would drop form-level issues that have no field path.
|
|
5737
|
-
details: err instanceof TopoloHttpError || err instanceof TopoloAuthError || err instanceof TopoloPermissionError ? err.details : extractTopoloServiceErrorDetails(suppliedDetails === void 0 ? body : { details: suppliedDetails }),
|
|
5738
|
-
hint: topoloErrorHint({ code, body }) ?? (code ? fallbackHintMap[code] : void 0),
|
|
5830
|
+
details: reauthorizationDetails(err) ?? (err instanceof TopoloHttpError || err instanceof TopoloAuthError || err instanceof TopoloPermissionError ? err.details : extractTopoloServiceErrorDetails(suppliedDetails === void 0 ? body : { details: suppliedDetails })),
|
|
5831
|
+
hint: reauthorizationHint(err) ?? topoloErrorHint({ code, body }) ?? (code ? fallbackHintMap[code] : void 0),
|
|
5739
5832
|
exitCode: status === 401 || code === "auth_error" || code === "confirmation_required" ? 2 : status === 403 || code === "permission_denied" ? 3 : code === "immutable_drift" ? 4 : 1
|
|
5740
5833
|
};
|
|
5741
5834
|
}
|
|
5742
5835
|
return { message: String(err), exitCode: 1 };
|
|
5743
5836
|
}
|
|
5837
|
+
function reauthorizationOf(err) {
|
|
5838
|
+
const prompt = err?.reauthorization;
|
|
5839
|
+
return prompt && typeof prompt === "object" ? prompt : void 0;
|
|
5840
|
+
}
|
|
5841
|
+
var REAUTHORIZATION_FIELDS = [
|
|
5842
|
+
"environment",
|
|
5843
|
+
"context",
|
|
5844
|
+
"reason",
|
|
5845
|
+
"state",
|
|
5846
|
+
"verificationUri",
|
|
5847
|
+
"verificationUriComplete",
|
|
5848
|
+
"userCode",
|
|
5849
|
+
"expiresAt",
|
|
5850
|
+
"pollIntervalSeconds",
|
|
5851
|
+
"loginCommand"
|
|
5852
|
+
];
|
|
5853
|
+
function reauthorizationDetails(err) {
|
|
5854
|
+
const prompt = reauthorizationOf(err);
|
|
5855
|
+
if (!prompt) return void 0;
|
|
5856
|
+
const safe = {};
|
|
5857
|
+
for (const field of REAUTHORIZATION_FIELDS) if (prompt[field] !== void 0) safe[field] = prompt[field];
|
|
5858
|
+
return { reauthorization: safe };
|
|
5859
|
+
}
|
|
5860
|
+
function reauthorizationHint(err) {
|
|
5861
|
+
const prompt = reauthorizationOf(err);
|
|
5862
|
+
if (!prompt) return void 0;
|
|
5863
|
+
const url = prompt.verificationUriComplete ?? prompt.verificationUri;
|
|
5864
|
+
const approve = url ? `Approve at ${url}${prompt.userCode ? ` (code ${prompt.userCode})` : ""}` : "No approval could be started";
|
|
5865
|
+
return `${approve}; or run: ${prompt.loginCommand ?? "topolo auth login"}. The saved sign-in will not be retried.`;
|
|
5866
|
+
}
|
|
5744
5867
|
|
|
5745
5868
|
// src/version.ts
|
|
5746
5869
|
import { readFileSync as readFileSync3 } from "fs";
|
|
@@ -6028,22 +6151,6 @@ async function resolveLoginContext(credential, serviceUrls, clientId, environmen
|
|
|
6028
6151
|
}
|
|
6029
6152
|
return { identity, context: { contextType: "personal", credential, oauthClientId: clientId } };
|
|
6030
6153
|
}
|
|
6031
|
-
function tryOpenBrowser(url) {
|
|
6032
|
-
try {
|
|
6033
|
-
const os = platform2();
|
|
6034
|
-
const cmd = os === "darwin" ? "open" : os === "win32" ? "start" : "xdg-open";
|
|
6035
|
-
const args = os === "win32" ? ["", url] : [url];
|
|
6036
|
-
const child = spawn(cmd, args, {
|
|
6037
|
-
detached: true,
|
|
6038
|
-
stdio: "ignore",
|
|
6039
|
-
...os === "win32" ? { shell: true } : {}
|
|
6040
|
-
});
|
|
6041
|
-
child.unref();
|
|
6042
|
-
return true;
|
|
6043
|
-
} catch {
|
|
6044
|
-
return false;
|
|
6045
|
-
}
|
|
6046
|
-
}
|
|
6047
6154
|
function startSpinner() {
|
|
6048
6155
|
if (!process.stderr.isTTY) return null;
|
|
6049
6156
|
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
@@ -6060,12 +6167,66 @@ function startSpinner() {
|
|
|
6060
6167
|
};
|
|
6061
6168
|
}
|
|
6062
6169
|
|
|
6170
|
+
// src/lib/reauthorization.ts
|
|
6171
|
+
function isReauthRequired(error) {
|
|
6172
|
+
const candidate = error;
|
|
6173
|
+
return error instanceof Error && candidate?.code === "reauth_required" && typeof candidate.reauthorization === "object" && candidate.reauthorization !== null;
|
|
6174
|
+
}
|
|
6175
|
+
function reauthorizationIsInteractive(env = process.env) {
|
|
6176
|
+
return Boolean(process.stderr.isTTY && process.stdin.isTTY) && !env.CI && env.TOPOLO_NO_REAUTH_PROMPT !== "1";
|
|
6177
|
+
}
|
|
6178
|
+
function describe(error) {
|
|
6179
|
+
const prompt = error.reauthorization;
|
|
6180
|
+
const url = prompt.verificationUriComplete ?? prompt.verificationUri;
|
|
6181
|
+
const expires = prompt.expiresAt ? ` (expires ${prompt.expiresAt})` : "";
|
|
6182
|
+
return ` ${url}
|
|
6183
|
+
|
|
6184
|
+
` + (prompt.userCode ? ` Confirm the page shows this code:
|
|
6185
|
+
|
|
6186
|
+
${prompt.userCode}
|
|
6187
|
+
|
|
6188
|
+
` : "") + ` Waiting for approval${expires}
|
|
6189
|
+
`;
|
|
6190
|
+
}
|
|
6191
|
+
async function credentialWithReauthorization(load, environment, context) {
|
|
6192
|
+
try {
|
|
6193
|
+
return await load();
|
|
6194
|
+
} catch (error) {
|
|
6195
|
+
if (!isReauthRequired(error)) throw error;
|
|
6196
|
+
const prompt = error.reauthorization;
|
|
6197
|
+
const url = prompt.verificationUriComplete ?? prompt.verificationUri;
|
|
6198
|
+
if (!reauthorizationIsInteractive() || prompt.state !== "awaiting_approval" || !url) throw error;
|
|
6199
|
+
const opened = tryOpenBrowser(url);
|
|
6200
|
+
process.stderr.write(
|
|
6201
|
+
`
|
|
6202
|
+
Your saved Topolo sign-in for ${prompt.context} (${prompt.environment}) has ended.
|
|
6203
|
+
${opened ? "Opening your browser to approve it again:" : "Open this link in your browser to approve it again:"}
|
|
6204
|
+
` + describe(error)
|
|
6205
|
+
);
|
|
6206
|
+
const remaining = prompt.expiresAt ? Date.parse(prompt.expiresAt) - Date.now() : Number.NaN;
|
|
6207
|
+
const timeoutMs = Number.isFinite(remaining) ? Math.max(1e3, Math.min(remaining, 15 * 6e4)) : 10 * 6e4;
|
|
6208
|
+
const credential = await awaitReauthorization(environment, context, {
|
|
6209
|
+
timeoutMs,
|
|
6210
|
+
onPrompt: (next) => process.stderr.write(`
|
|
6211
|
+
A new approval was started:
|
|
6212
|
+
${describe(next)}`)
|
|
6213
|
+
});
|
|
6214
|
+
if (!credential) throw error;
|
|
6215
|
+
process.stderr.write(" Approved. Continuing.\n");
|
|
6216
|
+
return credential;
|
|
6217
|
+
}
|
|
6218
|
+
}
|
|
6219
|
+
|
|
6063
6220
|
// src/client.ts
|
|
6064
6221
|
async function buildClient(options) {
|
|
6065
6222
|
const environment = options?.environment ?? selectedEnvironment();
|
|
6066
6223
|
const config = await readConfig();
|
|
6067
6224
|
const context = commandContextKey(config, environment, options?.context?.trim() || process.env.TOPOLO_CONTEXT);
|
|
6068
|
-
const credential =
|
|
6225
|
+
const credential = await credentialWithReauthorization(
|
|
6226
|
+
() => context ? refreshContextCredentialIfNeeded(environment, context) : refreshCredentialIfNeeded(environment),
|
|
6227
|
+
environment,
|
|
6228
|
+
context
|
|
6229
|
+
);
|
|
6069
6230
|
if (!credential) return null;
|
|
6070
6231
|
const profile = resolvedProfileConfig(config, environment);
|
|
6071
6232
|
const topolo = createTopolo({
|
|
@@ -6081,7 +6242,11 @@ async function buildClient(options) {
|
|
|
6081
6242
|
serviceUrls: profile.serviceUrls,
|
|
6082
6243
|
requireConfirmForWrites: options?.requireConfirmForWrites,
|
|
6083
6244
|
...credential.kind === "access_token" && credential.refreshToken ? {
|
|
6084
|
-
refreshCredential: () =>
|
|
6245
|
+
refreshCredential: () => credentialWithReauthorization(
|
|
6246
|
+
() => context ? refreshContextCredentialIfNeeded(environment, context, { force: true }) : refreshCredentialIfNeeded(environment, { force: true }),
|
|
6247
|
+
environment,
|
|
6248
|
+
context
|
|
6249
|
+
)
|
|
6085
6250
|
} : {},
|
|
6086
6251
|
...isDebugEnabled() ? { debug: stderrDebugLogger } : {}
|
|
6087
6252
|
});
|
|
@@ -6263,10 +6428,10 @@ function resolveConfiguredServiceUrl2(service, serviceUrls) {
|
|
|
6263
6428
|
}
|
|
6264
6429
|
return void 0;
|
|
6265
6430
|
}
|
|
6266
|
-
function parseQueryPairs(
|
|
6267
|
-
if (!
|
|
6431
|
+
function parseQueryPairs(pairs2) {
|
|
6432
|
+
if (!pairs2 || pairs2.length === 0) return void 0;
|
|
6268
6433
|
const out = {};
|
|
6269
|
-
for (const pair of
|
|
6434
|
+
for (const pair of pairs2) {
|
|
6270
6435
|
const eq = pair.indexOf("=");
|
|
6271
6436
|
if (eq <= 0) {
|
|
6272
6437
|
throw new Error(`Invalid --query entry "${pair}". Expected key=value.`);
|
|
@@ -6474,9 +6639,9 @@ function externalApprovalHandoff(action, result) {
|
|
|
6474
6639
|
if (!result || typeof result !== "object" || Array.isArray(result)) return null;
|
|
6475
6640
|
const invocation = result.invocation;
|
|
6476
6641
|
if (!invocation || typeof invocation !== "object" || Array.isArray(invocation)) return null;
|
|
6477
|
-
const
|
|
6478
|
-
if (
|
|
6479
|
-
const approval =
|
|
6642
|
+
const record4 = invocation;
|
|
6643
|
+
if (record4.status !== "awaiting_approval") return null;
|
|
6644
|
+
const approval = record4.approval;
|
|
6480
6645
|
if (!approval || typeof approval !== "object" || Array.isArray(approval)) return null;
|
|
6481
6646
|
const handoff = approval;
|
|
6482
6647
|
if (typeof handoff.requestId !== "string" || typeof handoff.verificationUrl !== "string") return null;
|
|
@@ -6752,9 +6917,9 @@ async function writeCredentialCustody(request, deps) {
|
|
|
6752
6917
|
}
|
|
6753
6918
|
}
|
|
6754
6919
|
function requiredText(value, flag) {
|
|
6755
|
-
const
|
|
6756
|
-
if (!
|
|
6757
|
-
return
|
|
6920
|
+
const text3 = value?.trim();
|
|
6921
|
+
if (!text3) throw new CustodyError("custody_invalid_input", `${flag} is required.`);
|
|
6922
|
+
return text3;
|
|
6758
6923
|
}
|
|
6759
6924
|
function currentUid() {
|
|
6760
6925
|
return typeof process.getuid === "function" ? process.getuid() : null;
|
|
@@ -6923,9 +7088,9 @@ async function writeCustodyFiles(directory, files) {
|
|
|
6923
7088
|
}
|
|
6924
7089
|
function withheldIfSecretLeaks(error, secret) {
|
|
6925
7090
|
const variants = [secret, encodeURIComponent(secret)];
|
|
6926
|
-
const
|
|
6927
|
-
const parts = [String(error), String(
|
|
6928
|
-
for (const value of [
|
|
7091
|
+
const record4 = error;
|
|
7092
|
+
const parts = [String(error), String(record4?.message), String(record4?.stack)];
|
|
7093
|
+
for (const value of [record4?.body, record4?.details]) {
|
|
6929
7094
|
try {
|
|
6930
7095
|
parts.push(String(value), JSON.stringify(value ?? null));
|
|
6931
7096
|
} catch {
|
|
@@ -7276,8 +7441,8 @@ async function authFetchJson(path, init = {}) {
|
|
|
7276
7441
|
}
|
|
7277
7442
|
headers.set("Authorization", `Bearer ${credential.accessToken}`);
|
|
7278
7443
|
const response = await fetch(`${authBaseUrl}${path}`, { ...init, headers });
|
|
7279
|
-
const
|
|
7280
|
-
const payload =
|
|
7444
|
+
const text3 = await response.text();
|
|
7445
|
+
const payload = text3 ? JSON.parse(text3) : {};
|
|
7281
7446
|
if (!response.ok || payload.success === false) {
|
|
7282
7447
|
const err = new Error(payload.message || payload.error_description || payload.error || "Topolo Auth request failed");
|
|
7283
7448
|
err.code = payload.code || payload.error;
|
|
@@ -7786,9 +7951,9 @@ async function readTomlRegistrations(path, agent) {
|
|
|
7786
7951
|
return [...registrations.values()].filter((registration) => registration.command || registration.url);
|
|
7787
7952
|
}
|
|
7788
7953
|
async function readGooseRegistrations(path) {
|
|
7789
|
-
const
|
|
7954
|
+
const text3 = await readFile4(path, "utf8");
|
|
7790
7955
|
const registrations = [];
|
|
7791
|
-
const lines =
|
|
7956
|
+
const lines = text3.split(/\r?\n/);
|
|
7792
7957
|
let inExtensions = false;
|
|
7793
7958
|
let inTopolo = false;
|
|
7794
7959
|
let current;
|
|
@@ -8030,10 +8195,10 @@ async function registerAgentHttpConfig(agent, homeDir, registration, codexHome)
|
|
|
8030
8195
|
return;
|
|
8031
8196
|
}
|
|
8032
8197
|
const before = await fileSnapshot(config.path);
|
|
8033
|
-
const
|
|
8198
|
+
const text3 = withoutMcpServerSections(before.contents ?? "", registration.name);
|
|
8034
8199
|
const headers = Object.entries(registration.headers).map(([key, value]) => JSON.stringify(key) + " = " + JSON.stringify(value)).join(", ");
|
|
8035
8200
|
const block = "\n\n[mcp_servers." + registration.name + "]\nurl = " + JSON.stringify(registration.url) + "\nhttp_headers = { " + headers + " }\n";
|
|
8036
|
-
await atomicWrite(config.path,
|
|
8201
|
+
await atomicWrite(config.path, text3 + block, before);
|
|
8037
8202
|
}
|
|
8038
8203
|
async function unregisterAgentConfig(agent, homeDir, name, projectDir, codexHome) {
|
|
8039
8204
|
for (const config of agentConfigPaths(agent, homeDir, projectDir, codexHome)) {
|
|
@@ -8041,8 +8206,8 @@ async function unregisterAgentConfig(agent, homeDir, name, projectDir, codexHome
|
|
|
8041
8206
|
if (config.format === "json") {
|
|
8042
8207
|
await unregisterJson(config, agent, name);
|
|
8043
8208
|
} else if (config.format === "toml") {
|
|
8044
|
-
const
|
|
8045
|
-
await atomicWrite(config.path,
|
|
8209
|
+
const text3 = withoutMcpServerSections(await readFile5(config.path, "utf8"), name);
|
|
8210
|
+
await atomicWrite(config.path, text3 ? text3 + "\n" : "");
|
|
8046
8211
|
} else {
|
|
8047
8212
|
await unregisterGoose(config.path, name);
|
|
8048
8213
|
}
|
|
@@ -8132,12 +8297,12 @@ async function unregisterJson(config, agent, name) {
|
|
|
8132
8297
|
async function registerToml(path, agent, mcp, registration) {
|
|
8133
8298
|
const before = await fileSnapshot(path);
|
|
8134
8299
|
const existing = before.contents ?? "";
|
|
8135
|
-
const
|
|
8300
|
+
const text3 = withoutMcpServerSections(existing, registration.name);
|
|
8136
8301
|
const args = mcp.args.length ? "\nargs = [" + mcp.args.map((item) => JSON.stringify(item)).join(", ") + "]" : "";
|
|
8137
8302
|
const environment = registrationEnvironment(agent, registration);
|
|
8138
8303
|
const environmentText = Object.entries(environment).map(([key, value]) => JSON.stringify(key) + " = " + JSON.stringify(value)).join(", ");
|
|
8139
8304
|
const block = agent === "grok" ? "\n\n[mcp_servers." + registration.name + "]\ncommand = " + JSON.stringify(mcp.command) + args + "\nenv = { " + environmentText + " }\n" : "\n\n[mcp_servers." + registration.name + "]\ncommand = " + JSON.stringify(mcp.command) + args + "\n\n[mcp_servers." + registration.name + ".env]\n" + Object.entries(environment).map(([key, value]) => key + " = " + JSON.stringify(value)).join("\n") + "\n";
|
|
8140
|
-
await atomicWrite(path,
|
|
8305
|
+
await atomicWrite(path, text3 + block, before);
|
|
8141
8306
|
}
|
|
8142
8307
|
async function registerGoose(path, mcp, registration) {
|
|
8143
8308
|
const existing = existsSync5(path) ? await readFile5(path, "utf8") : "";
|
|
@@ -8160,8 +8325,8 @@ async function unregisterGoose(path, name) {
|
|
|
8160
8325
|
const existing = await readFile5(path, "utf8");
|
|
8161
8326
|
await atomicWrite(path, removeGooseExtension(existing, name));
|
|
8162
8327
|
}
|
|
8163
|
-
function removeGooseExtension(
|
|
8164
|
-
const lines =
|
|
8328
|
+
function removeGooseExtension(text3, name) {
|
|
8329
|
+
const lines = text3.split(/\r?\n/);
|
|
8165
8330
|
const kept = [];
|
|
8166
8331
|
let inExtensions = false;
|
|
8167
8332
|
let skipping = false;
|
|
@@ -8181,11 +8346,11 @@ function removeGooseExtension(text2, name) {
|
|
|
8181
8346
|
}
|
|
8182
8347
|
return kept.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
8183
8348
|
}
|
|
8184
|
-
function insertGooseExtension(
|
|
8185
|
-
if (!
|
|
8186
|
-
const lines =
|
|
8349
|
+
function insertGooseExtension(text3, block) {
|
|
8350
|
+
if (!text3.trim()) return "extensions:\n" + block + "\n";
|
|
8351
|
+
const lines = text3.split(/\r?\n/);
|
|
8187
8352
|
const extensionIndex = lines.findIndex((line) => /^extensions:\s*$/.test(line));
|
|
8188
|
-
if (extensionIndex < 0) return
|
|
8353
|
+
if (extensionIndex < 0) return text3.trimEnd() + "\n\nextensions:\n" + block + "\n";
|
|
8189
8354
|
let end = lines.length;
|
|
8190
8355
|
for (let index = extensionIndex + 1; index < lines.length; index += 1) {
|
|
8191
8356
|
if (/^\S/.test(lines[index]) && lines[index].trim()) {
|
|
@@ -8284,11 +8449,11 @@ async function atomicWrite(path, contents, before) {
|
|
|
8284
8449
|
}
|
|
8285
8450
|
}
|
|
8286
8451
|
}
|
|
8287
|
-
function withoutMcpServerSections(
|
|
8452
|
+
function withoutMcpServerSections(text3, name) {
|
|
8288
8453
|
const section = "[mcp_servers." + name + "]";
|
|
8289
8454
|
const subsectionPrefix = "[mcp_servers." + name + ".";
|
|
8290
8455
|
let skipping = false;
|
|
8291
|
-
return
|
|
8456
|
+
return text3.split(/\r?\n/).filter((line) => {
|
|
8292
8457
|
const trimmed = line.trim();
|
|
8293
8458
|
if (trimmed === section || trimmed.startsWith(subsectionPrefix) && trimmed.endsWith("]")) {
|
|
8294
8459
|
skipping = true;
|
|
@@ -8334,18 +8499,18 @@ function validateMcpHubIdentity(value, where = "identity") {
|
|
|
8334
8499
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
8335
8500
|
throw new Error(`MCP hub ${where} must be an object.`);
|
|
8336
8501
|
}
|
|
8337
|
-
const
|
|
8338
|
-
const unknown = Object.keys(
|
|
8502
|
+
const record4 = value;
|
|
8503
|
+
const unknown = Object.keys(record4).filter((key) => !["name", "env", "context"].includes(key));
|
|
8339
8504
|
if (unknown.length) throw new Error(`MCP hub ${where} has unsupported field(s): ${unknown.join(", ")}.`);
|
|
8340
|
-
const name =
|
|
8505
|
+
const name = record4.name;
|
|
8341
8506
|
if (typeof name !== "string" || !NAME_PATTERN.test(name) || name === RESERVED_NAME) {
|
|
8342
8507
|
throw new Error(`MCP hub ${where} name must be topolo or topolo-<name> (and not reserved).`);
|
|
8343
8508
|
}
|
|
8344
|
-
const environment =
|
|
8509
|
+
const environment = record4.env;
|
|
8345
8510
|
if (typeof environment !== "string" || !MCP_HUB_ENVIRONMENTS.includes(environment)) {
|
|
8346
8511
|
throw new Error(`MCP hub identity ${name} env must be one of ${MCP_HUB_ENVIRONMENTS.join(", ")}.`);
|
|
8347
8512
|
}
|
|
8348
|
-
const context =
|
|
8513
|
+
const context = record4.context;
|
|
8349
8514
|
if (context === void 0) {
|
|
8350
8515
|
throw new Error(`MCP hub identity ${name} must pin a context (a key from \`topolo context ls\`).`);
|
|
8351
8516
|
}
|
|
@@ -8354,27 +8519,27 @@ function validateMcpHubIdentity(value, where = "identity") {
|
|
|
8354
8519
|
}
|
|
8355
8520
|
return { name, environment, context: context.trim() };
|
|
8356
8521
|
}
|
|
8357
|
-
function parseMcpHubConfig(
|
|
8522
|
+
function parseMcpHubConfig(text3, source) {
|
|
8358
8523
|
let parsed;
|
|
8359
8524
|
try {
|
|
8360
|
-
parsed = JSON.parse(
|
|
8525
|
+
parsed = JSON.parse(text3);
|
|
8361
8526
|
} catch (error) {
|
|
8362
8527
|
throw new Error(`Cannot read malformed MCP hub configuration at ${source}: ${error.message}`);
|
|
8363
8528
|
}
|
|
8364
8529
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8365
8530
|
throw new Error(`MCP hub configuration at ${source} must be a JSON object.`);
|
|
8366
8531
|
}
|
|
8367
|
-
const
|
|
8368
|
-
const unknown = Object.keys(
|
|
8532
|
+
const record4 = parsed;
|
|
8533
|
+
const unknown = Object.keys(record4).filter((key) => !["version", "port", "identities"].includes(key));
|
|
8369
8534
|
if (unknown.length) {
|
|
8370
8535
|
throw new Error(`MCP hub configuration at ${source} has unsupported field(s): ${unknown.join(", ")}.`);
|
|
8371
8536
|
}
|
|
8372
|
-
if (
|
|
8373
|
-
const port =
|
|
8374
|
-
if (
|
|
8537
|
+
if (record4.version !== 1) throw new Error(`MCP hub configuration at ${source} must declare "version": 1.`);
|
|
8538
|
+
const port = record4.port === void 0 ? MCP_HUB_DEFAULT_PORT : validateMcpHubPort(record4.port);
|
|
8539
|
+
if (record4.identities !== void 0 && !Array.isArray(record4.identities)) {
|
|
8375
8540
|
throw new Error(`MCP hub configuration at ${source} identities must be an array.`);
|
|
8376
8541
|
}
|
|
8377
|
-
const identities = (
|
|
8542
|
+
const identities = (record4.identities ?? []).map((entry, index) => validateMcpHubIdentity(entry, `identity #${index + 1}`));
|
|
8378
8543
|
const seen = /* @__PURE__ */ new Set();
|
|
8379
8544
|
for (const identity of identities) {
|
|
8380
8545
|
if (seen.has(identity.name)) {
|
|
@@ -8396,14 +8561,14 @@ function serializeMcpHubConfig(config) {
|
|
|
8396
8561
|
}, null, 2) + "\n";
|
|
8397
8562
|
}
|
|
8398
8563
|
async function readMcpHubConfig(path) {
|
|
8399
|
-
let
|
|
8564
|
+
let text3;
|
|
8400
8565
|
try {
|
|
8401
|
-
|
|
8566
|
+
text3 = await readFile6(path, "utf8");
|
|
8402
8567
|
} catch (error) {
|
|
8403
8568
|
if (error.code === "ENOENT") return emptyMcpHubConfig();
|
|
8404
8569
|
throw error;
|
|
8405
8570
|
}
|
|
8406
|
-
return parseMcpHubConfig(
|
|
8571
|
+
return parseMcpHubConfig(text3, path);
|
|
8407
8572
|
}
|
|
8408
8573
|
function upsertMcpHubIdentity(config, identity) {
|
|
8409
8574
|
const identities = config.identities.filter((entry) => entry.name !== identity.name);
|
|
@@ -9725,16 +9890,16 @@ var REVOKE_CREDENTIAL = "app_topolo_auth.principal_credentials.revoke";
|
|
|
9725
9890
|
var INSTALLATION_BOOTSTRAP_GRANT = "app_topolo_auth:identity";
|
|
9726
9891
|
function actionData(value) {
|
|
9727
9892
|
if (!value || typeof value !== "object") throw new Error("Topolo returned an invalid action response.");
|
|
9728
|
-
const
|
|
9729
|
-
const data =
|
|
9730
|
-
return data && typeof data === "object" ? data :
|
|
9893
|
+
const record4 = value;
|
|
9894
|
+
const data = record4.data;
|
|
9895
|
+
return data && typeof data === "object" ? data : record4;
|
|
9731
9896
|
}
|
|
9732
9897
|
function requiredRecord(value, name) {
|
|
9733
9898
|
if (!value || typeof value !== "object") throw new Error(`Topolo did not return ${name}.`);
|
|
9734
9899
|
return value;
|
|
9735
9900
|
}
|
|
9736
|
-
function requiredString(
|
|
9737
|
-
const value =
|
|
9901
|
+
function requiredString(record4, key) {
|
|
9902
|
+
const value = record4[key];
|
|
9738
9903
|
if (typeof value !== "string" || !value.trim()) throw new Error(`Topolo did not return ${key}.`);
|
|
9739
9904
|
return value;
|
|
9740
9905
|
}
|