@topolo/cli 0.11.0 → 0.12.0

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.
Files changed (2) hide show
  1. package/dist/index.js +577 -129
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -9,13 +9,13 @@ 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.17.1/node_modules/@topolo/sdk/dist/index.js
12
+ // node_modules/.pnpm/@topolo+sdk@0.21.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);
16
16
  return;
17
17
  }
18
- headers.set("Authorization", `Bearer ${credential.accessToken}`);
18
+ headers.set("Authorization", `${credential.kind === "dpop_access_token" ? "DPoP" : "Bearer"} ${credential.accessToken}`);
19
19
  }
20
20
  function applyAuditHeaders(headers, agent, requestId) {
21
21
  headers.set("X-Topolo-Client", `${agent.clientName}/${agent.clientVersion}`);
@@ -28,6 +28,58 @@ 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
+ }
31
83
  var TopoloSdkError = class extends Error {
32
84
  code;
33
85
  constructor(code, message2) {
@@ -1146,7 +1198,7 @@ var TopoloClient = class _TopoloClient {
1146
1198
  timeoutMs;
1147
1199
  fetchImpl;
1148
1200
  credentialProvider;
1149
- credentialLoadInFlight = null;
1201
+ credentialLoadsInFlight = /* @__PURE__ */ new Map();
1150
1202
  refreshCredential;
1151
1203
  refreshInFlight = null;
1152
1204
  debug;
@@ -1203,7 +1255,16 @@ var TopoloClient = class _TopoloClient {
1203
1255
  `Mutating ${method} requests require { confirm: true }. This guardrail prevents agents from issuing writes without an explicit human-in-the-loop acknowledgement.`
1204
1256
  );
1205
1257
  }
1206
- await this.loadCurrentCredential();
1258
+ const actionId = requestActionId(opts.headers);
1259
+ const taskId = opts.actorEnvelope?.delegation?.taskId ?? this.actorContext?.taskId;
1260
+ let requestCredential = await this.loadCurrentCredential({
1261
+ service: normalizeLookupKey(opts.service),
1262
+ method,
1263
+ path: opts.path,
1264
+ ...actionId ? { actionId } : {},
1265
+ ...taskId ? { taskId } : {},
1266
+ ...opts.resource ? { resource: opts.resource } : {}
1267
+ });
1207
1268
  const resolvedService = opts.baseUrl ? { serviceKey: normalizeLookupKey(opts.service), baseUrl: this.validatedServiceOrigin(opts.service, opts.baseUrl) } : await this.resolveRequestService(opts.service);
1208
1269
  const baseUrl = resolvedService.baseUrl;
1209
1270
  const url = new URL(opts.path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
@@ -1226,7 +1287,6 @@ var TopoloClient = class _TopoloClient {
1226
1287
  if (opts.body !== void 0) headers.set("Content-Type", "application/json");
1227
1288
  const platformAppId = resolveConfiguredAppId(resolvedService.serviceKey, this.appIds);
1228
1289
  if (platformAppId) headers.set("X-App-ID", platformAppId);
1229
- applyAuthHeaders(headers, this.credential);
1230
1290
  const actorEnvelope = opts.actorEnvelope === void 0 ? void 0 : parseActorEnvelope(opts.actorEnvelope);
1231
1291
  const requestId = actorEnvelope?.action.requestId ?? generateRequestId();
1232
1292
  applyAuditHeaders(headers, this.agent, requestId);
@@ -1260,9 +1320,19 @@ var TopoloClient = class _TopoloClient {
1260
1320
  }, this.timeoutMs);
1261
1321
  const signal = opts.signal ? mergeSignals(opts.signal, controller.signal) : controller.signal;
1262
1322
  try {
1323
+ const requestHeaders = new Headers(headers);
1324
+ applyAuthHeaders(requestHeaders, requestCredential);
1325
+ if (requestCredential.kind === "dpop_access_token") {
1326
+ requestHeaders.set("DPoP", await createDpopProof({
1327
+ accessToken: requestCredential.accessToken,
1328
+ method,
1329
+ targetUrl: urlStr,
1330
+ privateJwk: requestCredential.privateJwk
1331
+ }));
1332
+ }
1263
1333
  return await this.fetchImpl(urlStr, {
1264
1334
  method,
1265
- headers: new Headers(headers),
1335
+ headers: requestHeaders,
1266
1336
  body: opts.rawBody ?? (opts.body !== void 0 ? JSON.stringify(opts.body) : null),
1267
1337
  signal
1268
1338
  });
@@ -1293,13 +1363,13 @@ var TopoloClient = class _TopoloClient {
1293
1363
  };
1294
1364
  let res;
1295
1365
  try {
1296
- const attemptedCredential = this.credential;
1366
+ const attemptedCredential = requestCredential;
1297
1367
  res = await fetchWithReadRetry();
1298
1368
  if (res.status === 401 && attemptedCredential.kind === "access_token" && this.refreshCredential) {
1299
1369
  await res.body?.cancel().catch(() => void 0);
1300
1370
  const refreshed = await this.refreshRejectedAccessToken(attemptedCredential.accessToken);
1301
1371
  if (refreshed) {
1302
- applyAuthHeaders(headers, refreshed);
1372
+ requestCredential = refreshed;
1303
1373
  res = await fetchWithReadRetry();
1304
1374
  }
1305
1375
  }
@@ -1389,18 +1459,23 @@ var TopoloClient = class _TopoloClient {
1389
1459
  });
1390
1460
  return parsed;
1391
1461
  }
1392
- async loadCurrentCredential() {
1393
- if (!this.credentialProvider) return;
1394
- this.credentialLoadInFlight ??= this.credentialProvider().then((credential) => {
1395
- if (!credential) {
1396
- throw new TopoloAuthError("The configured Topolo credential is no longer available.");
1397
- }
1398
- this.credential = credential;
1399
- return credential;
1400
- }).finally(() => {
1401
- this.credentialLoadInFlight = null;
1402
- });
1403
- await this.credentialLoadInFlight;
1462
+ async loadCurrentCredential(context) {
1463
+ if (!this.credentialProvider) return this.credential;
1464
+ const key = JSON.stringify(context);
1465
+ let load = this.credentialLoadsInFlight.get(key);
1466
+ if (!load) {
1467
+ load = this.credentialProvider(context).then((credential) => {
1468
+ if (!credential) {
1469
+ throw new TopoloAuthError("The configured Topolo credential is no longer available.");
1470
+ }
1471
+ this.credential = credential;
1472
+ return credential;
1473
+ }).finally(() => {
1474
+ this.credentialLoadsInFlight.delete(key);
1475
+ });
1476
+ this.credentialLoadsInFlight.set(key, load);
1477
+ }
1478
+ return load;
1404
1479
  }
1405
1480
  async refreshRejectedAccessToken(attemptedAccessToken) {
1406
1481
  if (this.credential.kind === "access_token" && this.credential.accessToken !== attemptedAccessToken) {
@@ -1730,6 +1805,14 @@ var TopoloClient = class _TopoloClient {
1730
1805
  const entry = await this.getAction(action);
1731
1806
  return this.executeResolvedAction(entry, input2, options);
1732
1807
  }
1808
+ async issueExternalAgentCredential(input2) {
1809
+ const actionId = "app_topolo_auth.principal_credentials.issue";
1810
+ const entry = await this.getAction(actionId);
1811
+ if (entry.actionId !== actionId) {
1812
+ throw new TopoloSdkError("unexpected_action", "External agent credential issuance resolved an unexpected action.");
1813
+ }
1814
+ return this.executeResolvedAction(entry, input2, { confirm: true }, true);
1815
+ }
1733
1816
  async uploadAction(action, content, input2, options) {
1734
1817
  const entry = await this.getAction(action);
1735
1818
  const contract = parseTopoloActionUploadContract(entry.agentContract?.upload);
@@ -1861,7 +1944,7 @@ var TopoloClient = class _TopoloClient {
1861
1944
  }
1862
1945
  return this.executeResolvedAction(entry, input2, options);
1863
1946
  }
1864
- async executeResolvedAction(entry, input2, options) {
1947
+ async executeResolvedAction(entry, input2, options, preserveSensitiveResult = false) {
1865
1948
  const agentAccess = entry.agentAccess ?? (entry.destructive ? "off" : entry.readOnly && !entry.requiresConfirmation ? "auto" : "confirm");
1866
1949
  if (agentAccess === "off") {
1867
1950
  throw new TopoloPermissionError(
@@ -1919,7 +2002,7 @@ var TopoloClient = class _TopoloClient {
1919
2002
  },
1920
2003
  confirm: options.confirm === true || entry.readOnly && !entry.destructive && !entry.requiresConfirmation
1921
2004
  });
1922
- return redactActionResult(result);
2005
+ return preserveSensitiveResult ? result : redactActionResult(result);
1923
2006
  }
1924
2007
  async buildVerifiedAgentActorEnvelope(actionId, input2, resource) {
1925
2008
  if (!this.actorContext) return void 0;
@@ -2395,6 +2478,12 @@ function pickString(value) {
2395
2478
  }
2396
2479
  return void 0;
2397
2480
  }
2481
+ function requestActionId(headers) {
2482
+ const value = Object.entries(headers ?? {}).find(
2483
+ ([name]) => name.toLowerCase() === TOPOLO_ACTION_ID_HEADER.toLowerCase()
2484
+ )?.[1];
2485
+ return value?.trim() || void 0;
2486
+ }
2398
2487
  function extractRequired(body) {
2399
2488
  if (body && typeof body === "object") {
2400
2489
  const maybe = body;
@@ -3253,10 +3342,15 @@ function createTopolo(options) {
3253
3342
  };
3254
3343
  }
3255
3344
 
3256
- // node_modules/.pnpm/@topolo+sdk@0.17.1/node_modules/@topolo/sdk/dist/node/credential-store.js
3257
- import { mkdir, readFile, writeFile, chmod, rm, stat } from "fs/promises";
3345
+ // node_modules/.pnpm/@topolo+sdk@0.21.0/node_modules/@topolo/sdk/dist/node/credential-store.js
3346
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, chmod as chmod2, rm as rm2, stat } from "fs/promises";
3347
+ import { existsSync as existsSync2 } from "fs";
3348
+ import { homedir as homedir2, platform } from "os";
3349
+ import { dirname as dirname2, join as join2 } from "path";
3350
+ import { spawnSync } from "child_process";
3351
+ import { mkdir, readFile, readdir, rename, rm, writeFile, chmod } from "fs/promises";
3258
3352
  import { existsSync } from "fs";
3259
- import { homedir, platform } from "os";
3353
+ import { homedir, platform as hostPlatform } from "os";
3260
3354
  import { dirname, join } from "path";
3261
3355
  var TopoloSdkError2 = class extends Error {
3262
3356
  code;
@@ -3665,6 +3759,157 @@ function topoloServiceOrigins2(serviceUrls, environment) {
3665
3759
  ])
3666
3760
  );
3667
3761
  }
3762
+ var KEYCHAIN_SERVICE = "topolo-external-agent-installation";
3763
+ function installationStorePath() {
3764
+ const override = process.env.TOPOLO_INSTALLATIONS_PATH?.trim();
3765
+ if (override) return override;
3766
+ const base = process.env.XDG_CONFIG_HOME || (hostPlatform() === "win32" ? join(homedir(), "AppData", "Roaming") : join(homedir(), ".config"));
3767
+ return join(base, "topolo", "installations");
3768
+ }
3769
+ function installationPath(environment, id) {
3770
+ if (!/^[A-Za-z0-9_-]+$/.test(id)) throw new TypeError("Installation id contains unsupported characters.");
3771
+ return join(installationStorePath(), environment, `${id}.json`);
3772
+ }
3773
+ function accountName(environment, id) {
3774
+ return `${environment}:${id}`;
3775
+ }
3776
+ function runOrThrow(result, operation) {
3777
+ if (result.status !== 0) {
3778
+ throw new Error(`${operation} failed: ${(result.stderr || result.error?.message || "platform credential store unavailable").trim()}`);
3779
+ }
3780
+ return result.stdout.trim();
3781
+ }
3782
+ function createPlatformInstallationSecretBackend(options = {}) {
3783
+ const platform22 = options.platform ?? hostPlatform();
3784
+ const spawn2 = options.spawn ?? ((command, args, spawnOptions) => spawnSync(command, args, spawnOptions));
3785
+ const root = options.root ?? installationStorePath();
3786
+ if (platform22 === "darwin") {
3787
+ return {
3788
+ async set(account, value) {
3789
+ runOrThrow(spawn2("security", [
3790
+ "add-generic-password",
3791
+ "-U",
3792
+ "-a",
3793
+ account,
3794
+ "-s",
3795
+ KEYCHAIN_SERVICE,
3796
+ "-w"
3797
+ ], { input: `${value}
3798
+ `, encoding: "utf8" }), "macOS Keychain write");
3799
+ },
3800
+ async get(account) {
3801
+ const result = spawn2("security", [
3802
+ "find-generic-password",
3803
+ "-a",
3804
+ account,
3805
+ "-s",
3806
+ KEYCHAIN_SERVICE,
3807
+ "-w"
3808
+ ], { encoding: "utf8" });
3809
+ if (result.status === 44) return null;
3810
+ return runOrThrow(result, "macOS Keychain read");
3811
+ },
3812
+ async delete(account) {
3813
+ const result = spawn2("security", [
3814
+ "delete-generic-password",
3815
+ "-a",
3816
+ account,
3817
+ "-s",
3818
+ KEYCHAIN_SERVICE
3819
+ ], { encoding: "utf8" });
3820
+ if (result.status !== 44) runOrThrow(result, "macOS Keychain delete");
3821
+ }
3822
+ };
3823
+ }
3824
+ if (platform22 === "linux") {
3825
+ return {
3826
+ async set(account, value) {
3827
+ runOrThrow(spawn2("secret-tool", [
3828
+ "store",
3829
+ "--label=Topolo external agent installation",
3830
+ "service",
3831
+ KEYCHAIN_SERVICE,
3832
+ "account",
3833
+ account
3834
+ ], { input: value, encoding: "utf8" }), "Linux Secret Service write");
3835
+ },
3836
+ async get(account) {
3837
+ const result = spawn2("secret-tool", [
3838
+ "lookup",
3839
+ "service",
3840
+ KEYCHAIN_SERVICE,
3841
+ "account",
3842
+ account
3843
+ ], { encoding: "utf8" });
3844
+ if (result.status === 1) return null;
3845
+ return runOrThrow(result, "Linux Secret Service read");
3846
+ },
3847
+ async delete(account) {
3848
+ const result = spawn2("secret-tool", [
3849
+ "clear",
3850
+ "service",
3851
+ KEYCHAIN_SERVICE,
3852
+ "account",
3853
+ account
3854
+ ], { encoding: "utf8" });
3855
+ if (result.status !== 1) runOrThrow(result, "Linux Secret Service delete");
3856
+ }
3857
+ };
3858
+ }
3859
+ if (platform22 === "win32") {
3860
+ const secretPath = (account) => join(root, ".secrets", `${account.replace(":", "-")}.bin`);
3861
+ const powershell = (script, path, input2) => spawn2(
3862
+ "powershell.exe",
3863
+ ["-NoProfile", "-NonInteractive", "-Command", script],
3864
+ { input: input2, encoding: "utf8", env: { ...process.env, TOPOLO_SECRET_PATH: path } }
3865
+ );
3866
+ return {
3867
+ async set(account, value) {
3868
+ const path = secretPath(account);
3869
+ await mkdir(dirname(path), { recursive: true });
3870
+ const script = "$p=$env:TOPOLO_SECRET_PATH;$v=[Console]::In.ReadToEnd();$b=[Text.Encoding]::UTF8.GetBytes($v);$e=[Security.Cryptography.ProtectedData]::Protect($b,$null,[Security.Cryptography.DataProtectionScope]::CurrentUser);[IO.File]::WriteAllBytes($p,$e)";
3871
+ runOrThrow(powershell(script, path, value), "Windows DPAPI write");
3872
+ },
3873
+ async get(account) {
3874
+ const path = secretPath(account);
3875
+ if (!existsSync(path)) return null;
3876
+ const script = "$p=$env:TOPOLO_SECRET_PATH;$b=[IO.File]::ReadAllBytes($p);$d=[Security.Cryptography.ProtectedData]::Unprotect($b,$null,[Security.Cryptography.DataProtectionScope]::CurrentUser);[Console]::Out.Write([Text.Encoding]::UTF8.GetString($d))";
3877
+ return runOrThrow(powershell(script, path), "Windows DPAPI read");
3878
+ },
3879
+ async delete(account) {
3880
+ await rm(secretPath(account), { force: true });
3881
+ }
3882
+ };
3883
+ }
3884
+ throw new Error(`Secure external-agent installation storage is unsupported on ${platform22}.`);
3885
+ }
3886
+ async function writeExternalAgentInstallation(installation, secret, backend = createPlatformInstallationSecretBackend()) {
3887
+ const path = installationPath(installation.environment, installation.id);
3888
+ await backend.set(accountName(installation.environment, installation.id), JSON.stringify(secret));
3889
+ const temporary = `${path}.${process.pid}.tmp`;
3890
+ try {
3891
+ await mkdir(dirname(path), { recursive: true, mode: 448 });
3892
+ await writeFile(temporary, `${JSON.stringify(installation, null, 2)}
3893
+ `, { encoding: "utf8", mode: 384 });
3894
+ await rename(temporary, path);
3895
+ if (hostPlatform() !== "win32") await chmod(path, 384);
3896
+ } catch (error) {
3897
+ await backend.delete(accountName(installation.environment, installation.id)).catch(() => void 0);
3898
+ await rm(temporary, { force: true });
3899
+ throw error;
3900
+ }
3901
+ }
3902
+ async function listExternalAgentInstallations(environment) {
3903
+ const directory = join(installationStorePath(), environment);
3904
+ if (!existsSync(directory)) return [];
3905
+ const entries = await readdir(directory, { withFileTypes: true });
3906
+ const installations = await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map(async (entry) => JSON.parse(await readFile(join(directory, entry.name), "utf8"))));
3907
+ return installations.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
3908
+ }
3909
+ async function deleteExternalAgentInstallation(environment, id, backend = createPlatformInstallationSecretBackend()) {
3910
+ await backend.delete(accountName(environment, id));
3911
+ await rm(installationPath(environment, id), { force: true });
3912
+ }
3668
3913
  var TOPOLO_ENVIRONMENTS = TOPOLO_RUNTIME_ENVIRONMENTS;
3669
3914
  function contextKey(contextType, organizationId) {
3670
3915
  return contextType === "organization" && organizationId ? `org:${organizationId}` : "personal";
@@ -3673,14 +3918,14 @@ var ENVIRONMENT_DEFAULT_SERVICE_URLS = TOPOLO_ENVIRONMENT_SERVICE_URLS;
3673
3918
  function configPath() {
3674
3919
  const override = process.env.TOPOLO_CONFIG_PATH;
3675
3920
  if (override) return override;
3676
- const base = process.env.XDG_CONFIG_HOME || (platform() === "win32" ? join(homedir(), "AppData", "Roaming") : join(homedir(), ".config"));
3677
- return join(base, "topolo", "config.json");
3921
+ const base = process.env.XDG_CONFIG_HOME || (platform() === "win32" ? join2(homedir2(), "AppData", "Roaming") : join2(homedir2(), ".config"));
3922
+ return join2(base, "topolo", "config.json");
3678
3923
  }
3679
3924
  async function readConfig() {
3680
3925
  const path = configPath();
3681
- if (!existsSync(path)) return {};
3926
+ if (!existsSync2(path)) return {};
3682
3927
  try {
3683
- const raw = await readFile(path, "utf8");
3928
+ const raw = await readFile2(path, "utf8");
3684
3929
  return JSON.parse(raw);
3685
3930
  } catch {
3686
3931
  return {};
@@ -3688,11 +3933,11 @@ async function readConfig() {
3688
3933
  }
3689
3934
  async function writeConfig(config) {
3690
3935
  const path = configPath();
3691
- await mkdir(dirname(path), { recursive: true });
3692
- await writeFile(path, JSON.stringify(config, null, 2) + "\n", "utf8");
3936
+ await mkdir2(dirname2(path), { recursive: true });
3937
+ await writeFile2(path, JSON.stringify(config, null, 2) + "\n", "utf8");
3693
3938
  if (platform() !== "win32") {
3694
3939
  try {
3695
- await chmod(path, 384);
3940
+ await chmod2(path, 384);
3696
3941
  } catch {
3697
3942
  }
3698
3943
  }
@@ -3798,18 +4043,18 @@ var CONFIG_LOCK_TIMEOUT_MS = 5e3;
3798
4043
  var CONFIG_LOCK_STALE_MS = 12e4;
3799
4044
  async function withConfigWriteLock(operation) {
3800
4045
  const lockPath = `${configPath()}.lock`;
3801
- await mkdir(dirname(lockPath), { recursive: true });
4046
+ await mkdir2(dirname2(lockPath), { recursive: true });
3802
4047
  const startedAt = Date.now();
3803
4048
  while (true) {
3804
4049
  try {
3805
- await mkdir(lockPath);
4050
+ await mkdir2(lockPath);
3806
4051
  break;
3807
4052
  } catch (error) {
3808
4053
  if (error.code !== "EEXIST") throw error;
3809
4054
  try {
3810
4055
  const lock = await stat(lockPath);
3811
4056
  if (Date.now() - lock.mtimeMs > CONFIG_LOCK_STALE_MS) {
3812
- await rm(lockPath, { recursive: true, force: true });
4057
+ await rm2(lockPath, { recursive: true, force: true });
3813
4058
  continue;
3814
4059
  }
3815
4060
  } catch (inspectionError) {
@@ -3825,7 +4070,7 @@ async function withConfigWriteLock(operation) {
3825
4070
  try {
3826
4071
  return await operation();
3827
4072
  } finally {
3828
- await rm(lockPath, { recursive: true, force: true });
4073
+ await rm2(lockPath, { recursive: true, force: true });
3829
4074
  }
3830
4075
  }
3831
4076
  async function refreshCredentialIfNeeded(environment = selectedEnvironment(), options = {}) {
@@ -4077,14 +4322,14 @@ function normalizeError(err) {
4077
4322
 
4078
4323
  // src/version.ts
4079
4324
  import { readFileSync } from "fs";
4080
- import { dirname as dirname2, join as join2 } from "path";
4325
+ import { dirname as dirname3, join as join3 } from "path";
4081
4326
  import { fileURLToPath } from "url";
4082
4327
  function readCliVersion() {
4083
4328
  try {
4084
- const here = dirname2(fileURLToPath(import.meta.url));
4329
+ const here = dirname3(fileURLToPath(import.meta.url));
4085
4330
  const candidates = [
4086
- join2(here, "package.json"),
4087
- join2(here, "..", "package.json")
4331
+ join3(here, "package.json"),
4332
+ join3(here, "..", "package.json")
4088
4333
  ];
4089
4334
  for (const path of candidates) {
4090
4335
  try {
@@ -5177,8 +5422,8 @@ function normalizeList(values, fallback = []) {
5177
5422
  }
5178
5423
 
5179
5424
  // src/commands/integrations.ts
5180
- import { readdir, readFile as readFile2, stat as stat2 } from "fs/promises";
5181
- import { join as join3, relative } from "path";
5425
+ import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "fs/promises";
5426
+ import { join as join4, relative } from "path";
5182
5427
  function registerIntegrationsCommand(program2) {
5183
5428
  const integrations = program2.command("integrations").description("Generate and validate Topolo integration handoffs.");
5184
5429
  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) => {
@@ -5321,7 +5566,7 @@ function detectFramework(packageJson, haystack) {
5321
5566
  }
5322
5567
  async function readPackageJson(repoRoot) {
5323
5568
  try {
5324
- return JSON.parse(await readFile2(join3(repoRoot, "package.json"), "utf8"));
5569
+ return JSON.parse(await readFile3(join4(repoRoot, "package.json"), "utf8"));
5325
5570
  } catch {
5326
5571
  return null;
5327
5572
  }
@@ -5331,9 +5576,9 @@ async function collectFiles(repoRoot) {
5331
5576
  const root = repoRoot;
5332
5577
  async function walk(dir) {
5333
5578
  if (result.length > 2e3) return;
5334
- const entries = await readdir(dir, { withFileTypes: true });
5579
+ const entries = await readdir2(dir, { withFileTypes: true });
5335
5580
  for (const entry of entries) {
5336
- const fullPath = join3(dir, entry.name);
5581
+ const fullPath = join4(dir, entry.name);
5337
5582
  if (entry.isDirectory()) {
5338
5583
  if ([".git", "node_modules", "dist", "build", ".next", "coverage"].includes(entry.name)) continue;
5339
5584
  await walk(fullPath);
@@ -5344,7 +5589,7 @@ async function collectFiles(repoRoot) {
5344
5589
  if (info.size > 25e4) continue;
5345
5590
  result.push({
5346
5591
  path: relative(root, fullPath),
5347
- content: await readFile2(fullPath, "utf8")
5592
+ content: await readFile3(fullPath, "utf8")
5348
5593
  });
5349
5594
  }
5350
5595
  }
@@ -5360,20 +5605,20 @@ function formatCheckResult(result) {
5360
5605
  }
5361
5606
 
5362
5607
  // src/commands/skills.ts
5363
- import { spawnSync as spawnSync2 } from "child_process";
5364
- import { existsSync as existsSync4 } from "fs";
5365
- import { readFile as readFile5, rm as rm2 } from "fs/promises";
5366
- import { homedir as homedir3 } from "os";
5367
- import { dirname as dirname4, join as join5, resolve } from "path";
5608
+ import { spawnSync as spawnSync3 } from "child_process";
5609
+ import { existsSync as existsSync5 } from "fs";
5610
+ import { readFile as readFile6, rm as rm3 } from "fs/promises";
5611
+ import { homedir as homedir4 } from "os";
5612
+ import { dirname as dirname5, join as join6, resolve } from "path";
5368
5613
  import { fileURLToPath as fileURLToPath2 } from "url";
5369
5614
  import { createRequire } from "module";
5370
5615
 
5371
5616
  // src/commands/agent-host.ts
5372
- import { spawnSync } from "child_process";
5373
- import { existsSync as existsSync2 } from "fs";
5374
- import { readFile as readFile3 } from "fs/promises";
5375
- import { homedir as homedir2 } from "os";
5376
- import { isAbsolute, join as join4 } from "path";
5617
+ import { spawnSync as spawnSync2 } from "child_process";
5618
+ import { existsSync as existsSync3 } from "fs";
5619
+ import { readFile as readFile4 } from "fs/promises";
5620
+ import { homedir as homedir3 } from "os";
5621
+ import { isAbsolute, join as join5 } from "path";
5377
5622
  var SUPPORTED_AGENT_IDS = [
5378
5623
  "claude",
5379
5624
  "claude-desktop",
@@ -5422,41 +5667,41 @@ function selectStableNodeCommand(whichOutput, fallback = process.execPath) {
5422
5667
  return candidate && isAbsolute(candidate) ? candidate : fallback;
5423
5668
  }
5424
5669
  function stableNodeCommand() {
5425
- const probe = spawnSync(process.platform === "win32" ? "where" : "which", ["node"], {
5670
+ const probe = spawnSync2(process.platform === "win32" ? "where" : "which", ["node"], {
5426
5671
  encoding: "utf8",
5427
5672
  stdio: ["ignore", "pipe", "ignore"]
5428
5673
  });
5429
5674
  return selectStableNodeCommand(probe.status === 0 ? probe.stdout : void 0);
5430
5675
  }
5431
- function agentSkillPath(agent, homeDir = homedir2(), projectDir) {
5676
+ function agentSkillPath(agent, homeDir = homedir3(), projectDir) {
5432
5677
  switch (agent) {
5433
5678
  case "claude":
5434
- return projectDir ? join4(projectDir, ".claude", "skills", "topolo", "SKILL.md") : join4(homeDir, ".claude", "skills", "topolo", "SKILL.md");
5679
+ return projectDir ? join5(projectDir, ".claude", "skills", "topolo", "SKILL.md") : join5(homeDir, ".claude", "skills", "topolo", "SKILL.md");
5435
5680
  case "claude-desktop":
5436
- return join4(homeDir, ".claude", "skills", "topolo", "SKILL.md");
5681
+ return join5(homeDir, ".claude", "skills", "topolo", "SKILL.md");
5437
5682
  case "codex":
5438
- return join4(homeDir, ".codex", "skills", "topolo", "SKILL.md");
5683
+ return join5(homeDir, ".codex", "skills", "topolo", "SKILL.md");
5439
5684
  case "cursor":
5440
- return join4(projectDir ?? homeDir, ".cursor", "rules", "topolo.mdc");
5685
+ return join5(projectDir ?? homeDir, ".cursor", "rules", "topolo.mdc");
5441
5686
  case "grok":
5442
- return join4(projectDir ?? homeDir, ".grok", "skills", "topolo", "SKILL.md");
5687
+ return join5(projectDir ?? homeDir, ".grok", "skills", "topolo", "SKILL.md");
5443
5688
  case "gemini":
5444
- return join4(projectDir ?? homeDir, ".gemini", "skills", "topolo", "SKILL.md");
5689
+ return join5(projectDir ?? homeDir, ".gemini", "skills", "topolo", "SKILL.md");
5445
5690
  case "copilot":
5446
- return projectDir ? join4(projectDir, ".github", "skills", "topolo", "SKILL.md") : join4(homeDir, ".copilot", "skills", "topolo", "SKILL.md");
5691
+ return projectDir ? join5(projectDir, ".github", "skills", "topolo", "SKILL.md") : join5(homeDir, ".copilot", "skills", "topolo", "SKILL.md");
5447
5692
  case "windsurf":
5448
- return projectDir ? join4(projectDir, ".windsurf", "rules", "topolo.md") : void 0;
5693
+ return projectDir ? join5(projectDir, ".windsurf", "rules", "topolo.md") : void 0;
5449
5694
  case "cline":
5450
- return join4(projectDir ?? homeDir, ".cline", "skills", "topolo", "SKILL.md");
5695
+ return join5(projectDir ?? homeDir, ".cline", "skills", "topolo", "SKILL.md");
5451
5696
  case "goose":
5452
5697
  case "amazon-q":
5453
5698
  return void 0;
5454
5699
  }
5455
5700
  }
5456
- function agentConfigPaths(agent, homeDir = homedir2(), projectDir) {
5701
+ function agentConfigPaths(agent, homeDir = homedir3(), projectDir) {
5457
5702
  switch (agent) {
5458
5703
  case "claude":
5459
- return [{ path: join4(homeDir, ".claude.json"), format: "json", rootKey: "mcpServers" }];
5704
+ return [{ path: join5(homeDir, ".claude.json"), format: "json", rootKey: "mcpServers" }];
5460
5705
  case "claude-desktop":
5461
5706
  return [{
5462
5707
  path: claudeDesktopConfigPath(homeDir),
@@ -5464,31 +5709,31 @@ function agentConfigPaths(agent, homeDir = homedir2(), projectDir) {
5464
5709
  rootKey: "mcpServers"
5465
5710
  }];
5466
5711
  case "codex":
5467
- return [{ path: join4(homeDir, ".codex", "config.toml"), format: "toml" }];
5712
+ return [{ path: join5(homeDir, ".codex", "config.toml"), format: "toml" }];
5468
5713
  case "cursor":
5469
5714
  return [{
5470
- path: projectDir ? join4(projectDir, ".cursor", "mcp.json") : join4(homeDir, ".cursor", "mcp.json"),
5715
+ path: projectDir ? join5(projectDir, ".cursor", "mcp.json") : join5(homeDir, ".cursor", "mcp.json"),
5471
5716
  format: "json",
5472
5717
  rootKey: "mcpServers"
5473
5718
  }];
5474
5719
  case "grok":
5475
5720
  return [{
5476
- path: projectDir ? join4(projectDir, ".grok", "config.toml") : join4(homeDir, ".grok", "config.toml"),
5721
+ path: projectDir ? join5(projectDir, ".grok", "config.toml") : join5(homeDir, ".grok", "config.toml"),
5477
5722
  format: "toml"
5478
5723
  }];
5479
5724
  case "gemini":
5480
5725
  return [{
5481
- path: projectDir ? join4(projectDir, ".gemini", "settings.json") : join4(homeDir, ".gemini", "settings.json"),
5726
+ path: projectDir ? join5(projectDir, ".gemini", "settings.json") : join5(homeDir, ".gemini", "settings.json"),
5482
5727
  format: "json",
5483
5728
  rootKey: "mcpServers"
5484
5729
  }];
5485
5730
  case "copilot":
5486
5731
  return projectDir ? [
5487
- { path: join4(projectDir, ".mcp.json"), format: "json", rootKey: "mcpServers" },
5488
- { path: join4(projectDir, ".vscode", "mcp.json"), format: "json", rootKey: "servers" }
5732
+ { path: join5(projectDir, ".mcp.json"), format: "json", rootKey: "mcpServers" },
5733
+ { path: join5(projectDir, ".vscode", "mcp.json"), format: "json", rootKey: "servers" }
5489
5734
  ] : [
5490
5735
  {
5491
- path: join4(homeDir, ".copilot", "mcp-config.json"),
5736
+ path: join5(homeDir, ".copilot", "mcp-config.json"),
5492
5737
  format: "json",
5493
5738
  rootKey: "mcpServers"
5494
5739
  },
@@ -5496,24 +5741,24 @@ function agentConfigPaths(agent, homeDir = homedir2(), projectDir) {
5496
5741
  ];
5497
5742
  case "windsurf":
5498
5743
  return [{
5499
- path: join4(homeDir, ".codeium", "windsurf", "mcp_config.json"),
5744
+ path: join5(homeDir, ".codeium", "windsurf", "mcp_config.json"),
5500
5745
  format: "json",
5501
5746
  rootKey: "mcpServers"
5502
5747
  }];
5503
5748
  case "cline":
5504
5749
  return [{
5505
- path: projectDir ? join4(projectDir, ".cline", "mcp.json") : join4(homeDir, ".cline", "mcp.json"),
5750
+ path: projectDir ? join5(projectDir, ".cline", "mcp.json") : join5(homeDir, ".cline", "mcp.json"),
5506
5751
  format: "json",
5507
5752
  rootKey: "mcpServers"
5508
5753
  }];
5509
5754
  case "goose":
5510
- return [{ path: join4(homeDir, ".config", "goose", "config.yaml"), format: "yaml" }];
5755
+ return [{ path: join5(homeDir, ".config", "goose", "config.yaml"), format: "yaml" }];
5511
5756
  case "amazon-q":
5512
5757
  if (projectDir) {
5513
- return [{ path: join4(projectDir, ".amazonq", "mcp.json"), format: "json", rootKey: "mcpServers" }];
5758
+ return [{ path: join5(projectDir, ".amazonq", "mcp.json"), format: "json", rootKey: "mcpServers" }];
5514
5759
  }
5515
5760
  return [{
5516
- path: existsSync2(join4(homeDir, ".aws", "amazonq", "mcp.json")) ? join4(homeDir, ".aws", "amazonq", "mcp.json") : join4(homeDir, ".aws", "amazonq", "default.json"),
5761
+ path: existsSync3(join5(homeDir, ".aws", "amazonq", "mcp.json")) ? join5(homeDir, ".aws", "amazonq", "mcp.json") : join5(homeDir, ".aws", "amazonq", "default.json"),
5517
5762
  format: "json",
5518
5763
  rootKey: "mcpServers"
5519
5764
  }];
@@ -5545,23 +5790,23 @@ function agentCliNames(agent) {
5545
5790
  return ["qchat", "q"];
5546
5791
  }
5547
5792
  }
5548
- function agentConfigExists(agent, homeDir = homedir2(), projectDir) {
5549
- return agentConfigPaths(agent, homeDir, projectDir).some((config) => existsSync2(config.path));
5793
+ function agentConfigExists(agent, homeDir = homedir3(), projectDir) {
5794
+ return agentConfigPaths(agent, homeDir, projectDir).some((config) => existsSync3(config.path));
5550
5795
  }
5551
5796
  function claudeDesktopConfigPath(homeDir) {
5552
- if (process.platform === "darwin") return join4(homeDir, "Library", "Application Support", "Claude", "claude_desktop_config.json");
5553
- if (process.platform === "win32") return join4(process.env.APPDATA ?? join4(homeDir, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
5554
- return join4(process.env.XDG_CONFIG_HOME ?? join4(homeDir, ".config"), "Claude", "claude_desktop_config.json");
5797
+ if (process.platform === "darwin") return join5(homeDir, "Library", "Application Support", "Claude", "claude_desktop_config.json");
5798
+ if (process.platform === "win32") return join5(process.env.APPDATA ?? join5(homeDir, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
5799
+ return join5(process.env.XDG_CONFIG_HOME ?? join5(homeDir, ".config"), "Claude", "claude_desktop_config.json");
5555
5800
  }
5556
5801
  function vscodeUserConfigPath(homeDir) {
5557
- if (process.platform === "darwin") return join4(homeDir, "Library", "Application Support", "Code", "User", "mcp.json");
5558
- if (process.platform === "win32") return join4(process.env.APPDATA ?? join4(homeDir, "AppData", "Roaming"), "Code", "User", "mcp.json");
5559
- return join4(process.env.XDG_CONFIG_HOME ?? join4(homeDir, ".config"), "Code", "User", "mcp.json");
5802
+ if (process.platform === "darwin") return join5(homeDir, "Library", "Application Support", "Code", "User", "mcp.json");
5803
+ if (process.platform === "win32") return join5(process.env.APPDATA ?? join5(homeDir, "AppData", "Roaming"), "Code", "User", "mcp.json");
5804
+ return join5(process.env.XDG_CONFIG_HOME ?? join5(homeDir, ".config"), "Code", "User", "mcp.json");
5560
5805
  }
5561
- async function readAgentRegistrations(agent, homeDir = homedir2(), projectDir) {
5806
+ async function readAgentRegistrations(agent, homeDir = homedir3(), projectDir) {
5562
5807
  const registrations = [];
5563
5808
  for (const config of agentConfigPaths(agent, homeDir, projectDir)) {
5564
- if (!existsSync2(config.path)) continue;
5809
+ if (!existsSync3(config.path)) continue;
5565
5810
  if (config.format === "json") {
5566
5811
  registrations.push(...await readJsonRegistrations(config.path, config.rootKey ?? "mcpServers", agent));
5567
5812
  } else if (config.format === "toml") {
@@ -5575,7 +5820,7 @@ async function readAgentRegistrations(agent, homeDir = homedir2(), projectDir) {
5575
5820
  async function readJsonRegistrations(path, rootKey, agent) {
5576
5821
  let parsed;
5577
5822
  try {
5578
- parsed = JSON.parse(await readFile3(path, "utf8"));
5823
+ parsed = JSON.parse(await readFile4(path, "utf8"));
5579
5824
  } catch (error) {
5580
5825
  throw new Error("Cannot read malformed " + (agent === "claude" ? "Claude" : AGENT_LABELS[agent]) + " configuration at " + path + ": " + message(error));
5581
5826
  }
@@ -5587,7 +5832,7 @@ async function readTomlRegistrations(path, agent) {
5587
5832
  const registrations = /* @__PURE__ */ new Map();
5588
5833
  let section;
5589
5834
  let pending;
5590
- for (const rawLine of (await readFile3(path, "utf8")).split(/\r?\n/)) {
5835
+ for (const rawLine of (await readFile4(path, "utf8")).split(/\r?\n/)) {
5591
5836
  const line = stripTomlComment(rawLine).trim();
5592
5837
  if (pending) {
5593
5838
  if (line.startsWith("[")) {
@@ -5626,7 +5871,7 @@ async function readTomlRegistrations(path, agent) {
5626
5871
  return [...registrations.values()].filter((registration) => registration.command);
5627
5872
  }
5628
5873
  async function readGooseRegistrations(path) {
5629
- const text2 = await readFile3(path, "utf8");
5874
+ const text2 = await readFile4(path, "utf8");
5630
5875
  const registrations = [];
5631
5876
  const lines = text2.split(/\r?\n/);
5632
5877
  let inExtensions = false;
@@ -5822,9 +6067,9 @@ function message(error) {
5822
6067
  }
5823
6068
 
5824
6069
  // src/commands/agent-config.ts
5825
- import { existsSync as existsSync3 } from "fs";
5826
- import { mkdir as mkdir2, readFile as readFile4, rename, writeFile as writeFile2 } from "fs/promises";
5827
- import { dirname as dirname3 } from "path";
6070
+ import { existsSync as existsSync4 } from "fs";
6071
+ import { mkdir as mkdir3, readFile as readFile5, rename as rename2, writeFile as writeFile3 } from "fs/promises";
6072
+ import { dirname as dirname4 } from "path";
5828
6073
  async function installAgentSkill(agent, source, homeDir, projectDir) {
5829
6074
  const path = agentSkillPath(agent, homeDir, projectDir);
5830
6075
  if (!path) return { installed: false };
@@ -5845,11 +6090,11 @@ async function registerAgentConfig(agent, mcp, homeDir, registration, projectDir
5845
6090
  }
5846
6091
  async function unregisterAgentConfig(agent, homeDir, name, projectDir) {
5847
6092
  for (const config of agentConfigPaths(agent, homeDir, projectDir)) {
5848
- if (!existsSync3(config.path)) continue;
6093
+ if (!existsSync4(config.path)) continue;
5849
6094
  if (config.format === "json") {
5850
6095
  await unregisterJson(config, agent, name);
5851
6096
  } else if (config.format === "toml") {
5852
- const text2 = withoutMcpServerSections(await readFile4(config.path, "utf8"), name);
6097
+ const text2 = withoutMcpServerSections(await readFile5(config.path, "utf8"), name);
5853
6098
  await atomicWrite(config.path, text2 ? text2 + "\n" : "");
5854
6099
  } else {
5855
6100
  await unregisterGoose(config.path, name);
@@ -5859,14 +6104,15 @@ async function unregisterAgentConfig(agent, homeDir, name, projectDir) {
5859
6104
  function registrationEnvironment(agent, registration) {
5860
6105
  return {
5861
6106
  TOPOLO_AGENT_NAME: AGENT_NAME_FOR[agent],
6107
+ ...registration.installationId ? { TOPOLO_AGENT_INSTALLATION_ID: registration.installationId } : {},
5862
6108
  ...registration.context ? { TOPOLO_CONTEXT: registration.context, TOPOLO_ENV: registration.environment } : {}
5863
6109
  };
5864
6110
  }
5865
6111
  async function registerJson(config, agent, mcp, registration) {
5866
6112
  let parsed = {};
5867
- if (existsSync3(config.path)) {
6113
+ if (existsSync4(config.path)) {
5868
6114
  try {
5869
- parsed = JSON.parse(await readFile4(config.path, "utf8"));
6115
+ parsed = JSON.parse(await readFile5(config.path, "utf8"));
5870
6116
  } catch (error) {
5871
6117
  throw new Error(
5872
6118
  "Cannot update malformed " + (agent === "claude" ? "Claude" : AGENT_LABELS[agent]) + " configuration at " + config.path + ": " + (error instanceof Error ? error.message : String(error))
@@ -5892,7 +6138,7 @@ async function registerJson(config, agent, mcp, registration) {
5892
6138
  async function unregisterJson(config, agent, name) {
5893
6139
  let parsed;
5894
6140
  try {
5895
- parsed = JSON.parse(await readFile4(config.path, "utf8"));
6141
+ parsed = JSON.parse(await readFile5(config.path, "utf8"));
5896
6142
  } catch (error) {
5897
6143
  throw new Error(
5898
6144
  "Cannot update malformed " + AGENT_LABELS[agent] + " configuration at " + config.path + ": " + (error instanceof Error ? error.message : String(error))
@@ -5909,7 +6155,7 @@ async function unregisterJson(config, agent, name) {
5909
6155
  await atomicWrite(config.path, JSON.stringify(parsed, null, 2) + "\n");
5910
6156
  }
5911
6157
  async function registerToml(path, agent, mcp, registration) {
5912
- const existing = existsSync3(path) ? await readFile4(path, "utf8") : "";
6158
+ const existing = existsSync4(path) ? await readFile5(path, "utf8") : "";
5913
6159
  const text2 = withoutMcpServerSections(existing, registration.name);
5914
6160
  const args = mcp.args.length ? "\nargs = [" + mcp.args.map((item) => JSON.stringify(item)).join(", ") + "]" : "";
5915
6161
  const environment = registrationEnvironment(agent, registration);
@@ -5918,7 +6164,7 @@ async function registerToml(path, agent, mcp, registration) {
5918
6164
  await atomicWrite(path, text2 + block);
5919
6165
  }
5920
6166
  async function registerGoose(path, mcp, registration) {
5921
- const existing = existsSync3(path) ? await readFile4(path, "utf8") : "";
6167
+ const existing = existsSync4(path) ? await readFile5(path, "utf8") : "";
5922
6168
  const withoutTopolo = removeGooseExtension(existing, registration.name);
5923
6169
  const environment = registrationEnvironment("goose", registration);
5924
6170
  const block = [
@@ -5935,7 +6181,7 @@ async function registerGoose(path, mcp, registration) {
5935
6181
  await atomicWrite(path, insertGooseExtension(withoutTopolo, block));
5936
6182
  }
5937
6183
  async function unregisterGoose(path, name) {
5938
- const existing = await readFile4(path, "utf8");
6184
+ const existing = await readFile5(path, "utf8");
5939
6185
  await atomicWrite(path, removeGooseExtension(existing, name));
5940
6186
  }
5941
6187
  function removeGooseExtension(text2, name) {
@@ -5986,10 +6232,10 @@ function formatSkillForAgent(agent, source) {
5986
6232
  return "---\ntrigger: model_decision\ndescription: Use Topolo MCP for Topolo platform operations.\n---\n\n" + body;
5987
6233
  }
5988
6234
  async function atomicWrite(path, contents) {
5989
- await mkdir2(dirname3(path), { recursive: true });
6235
+ await mkdir3(dirname4(path), { recursive: true });
5990
6236
  const temporaryPath = path + ".topolo-" + process.pid + ".tmp";
5991
- await writeFile2(temporaryPath, contents, { encoding: "utf8", mode: 384 });
5992
- await rename(temporaryPath, path);
6237
+ await writeFile3(temporaryPath, contents, { encoding: "utf8", mode: 384 });
6238
+ await rename2(temporaryPath, path);
5993
6239
  }
5994
6240
  function withoutMcpServerSections(text2, name) {
5995
6241
  const section = "[mcp_servers." + name + "]";
@@ -6009,11 +6255,11 @@ function withoutMcpServerSections(text2, name) {
6009
6255
  // src/commands/skills.ts
6010
6256
  var AGENT_LABELS2 = AGENT_LABELS;
6011
6257
  function skillsSourceDir() {
6012
- const here = dirname4(fileURLToPath2(import.meta.url));
6258
+ const here = dirname5(fileURLToPath2(import.meta.url));
6013
6259
  const distCandidate = resolve(here, "../skills");
6014
6260
  const srcCandidate = resolve(here, "../../skills");
6015
6261
  for (const candidate of [distCandidate, srcCandidate]) {
6016
- if (existsSync4(join5(candidate, "topolo", "SKILL.md"))) return candidate;
6262
+ if (existsSync5(join6(candidate, "topolo", "SKILL.md"))) return candidate;
6017
6263
  }
6018
6264
  return distCandidate;
6019
6265
  }
@@ -6023,7 +6269,7 @@ function resolveMcpCommand() {
6023
6269
  }
6024
6270
  function agentBinaryExists(agent) {
6025
6271
  return agentCliNames(agent).some((bin) => {
6026
- const probe = spawnSync2(process.platform === "win32" ? "where" : "which", [bin], {
6272
+ const probe = spawnSync3(process.platform === "win32" ? "where" : "which", [bin], {
6027
6273
  stdio: "ignore"
6028
6274
  });
6029
6275
  return probe.status === 0;
@@ -6049,10 +6295,14 @@ function resolveMcpRegistration(options) {
6049
6295
  if (!["production", "staging", "development", "local"].includes(environment)) {
6050
6296
  throw new Error("Environment must be production, staging, development, or local.");
6051
6297
  }
6052
- return { name, ...context ? { context } : {}, environment };
6298
+ const installationId = options.installationId?.trim();
6299
+ if (installationId && /[\r\n]/.test(installationId)) {
6300
+ throw new Error("Installation id must be a single-line identifier.");
6301
+ }
6302
+ return { name, ...context ? { context } : {}, environment, ...installationId ? { installationId } : {} };
6053
6303
  }
6054
6304
  async function installForAgent(agent, sourceDir, mcp, opts) {
6055
- const source = await readFile5(join5(sourceDir, "topolo", "SKILL.md"), "utf8");
6305
+ const source = await readFile6(join6(sourceDir, "topolo", "SKILL.md"), "utf8");
6056
6306
  const skill = await installAgentSkill(agent, source, opts.homeDir, opts.projectDir);
6057
6307
  if (!opts.withMcp) {
6058
6308
  return { agent, skillPath: skill.path, skillInstalled: skill.installed, mcp: "skipped" };
@@ -6091,18 +6341,22 @@ function resolveTargets(opts) {
6091
6341
  }))];
6092
6342
  }
6093
6343
  if (opts.all) return [...SUPPORTED_AGENT_IDS];
6094
- const homeDir = opts.homeDir ?? homedir3();
6095
- const detectBinaries = homeDir === homedir3();
6344
+ const homeDir = opts.homeDir ?? homedir4();
6345
+ const detectBinaries = homeDir === homedir4();
6096
6346
  const detected = SUPPORTED_AGENT_IDS.filter((agent) => agentConfigExists(agent, homeDir, opts.project) || detectBinaries && agentBinaryExists(agent));
6097
6347
  return [.../* @__PURE__ */ new Set(["claude", "codex", ...detected])];
6098
6348
  }
6099
6349
  function registerSkillsCommand(program2) {
6100
6350
  const skills = program2.command("skills").description("Install the Topolo agent skill + MCP server into supported agent hosts");
6101
- skills.command("install").description("Copy the skill file and register the topolo MCP server (credential-free)").option("--claude", "Target Claude Code only").option("--codex", "Target Codex only").option("--agent <host...>", "Target one or more hosts by id (for example cursor gemini)").option("--all", "Configure every supported local host").option("--project <dir>", "Write project-scoped host files under <dir> where supported").option("--agent-home <dir>", "Target an alternate agent home (managed installs and clean-room verification)").option("--name <name>", "Register a named pinned MCP, such as topolo-acme").option("--context <context>", "Pin the named MCP to an exact context returned by topolo context ls").option("--no-mcp", "Copy the skill file but do not register the MCP server").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(
6351
+ skills.command("install").description("Copy the skill file and register the topolo MCP server (credential-free)").option("--claude", "Target Claude Code only").option("--codex", "Target Codex only").option("--agent <host...>", "Target one or more hosts by id (for example cursor gemini)").option("--all", "Configure every supported local host").option("--project <dir>", "Write project-scoped host files under <dir> where supported").option("--agent-home <dir>", "Target an alternate agent home (managed installs and clean-room verification)").option("--name <name>", "Register a named pinned MCP, such as topolo-acme").option("--context <context>", "Pin the named MCP to an exact context returned by topolo context ls").option("--installation <id>", "Bind the MCP runtime to a registered external-agent installation").option("--no-mcp", "Copy the skill file but do not register the MCP server").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(
6102
6352
  async (opts) => {
6103
6353
  const mode = resolveOutputMode(opts.json);
6104
6354
  try {
6105
- const { results, mcp } = await installAgentSurfaces({ ...opts, homeDir: opts.agentHome });
6355
+ const { results, mcp } = await installAgentSurfaces({
6356
+ ...opts,
6357
+ installationId: opts.installation,
6358
+ homeDir: opts.agentHome
6359
+ });
6106
6360
  assertAgentSetupSucceeded(results);
6107
6361
  emit({ installed: results, mcpCommand: mcp }, mode, () => formatInstall(results, mcp));
6108
6362
  } catch (err) {
@@ -6144,12 +6398,12 @@ function registerSkillsCommand(program2) {
6144
6398
  }
6145
6399
  async function installAgentSurfaces(opts) {
6146
6400
  const sourceDir = skillsSourceDir();
6147
- if (!existsSync4(join5(sourceDir, "topolo", "SKILL.md"))) {
6401
+ if (!existsSync5(join6(sourceDir, "topolo", "SKILL.md"))) {
6148
6402
  throw new Error("Bundled skill file not found. Reinstall the CLI: npm install -g @topolo/cli");
6149
6403
  }
6150
6404
  const mcp = resolveMcpCommand();
6151
6405
  const registration = resolveMcpRegistration(opts);
6152
- const homeDir = opts.homeDir ?? homedir3();
6406
+ const homeDir = opts.homeDir ?? homedir4();
6153
6407
  const results = [];
6154
6408
  for (const agent of resolveTargets({ ...opts, homeDir, project: opts.project })) {
6155
6409
  results.push(await installForAgent(agent, sourceDir, mcp, {
@@ -6162,10 +6416,14 @@ async function installAgentSurfaces(opts) {
6162
6416
  return { results, mcp };
6163
6417
  }
6164
6418
  function registerSetupCommand(program2) {
6165
- program2.command("setup").description("Install the Topolo skill and deterministic local MCP server for supported agent hosts").option("--claude", "Target Claude Code only").option("--codex", "Target Codex only").option("--agent <host...>", "Target one or more hosts by id").option("--all", "Configure every supported local host").option("--project <dir>", "Write project-scoped host files under <dir> where supported").option("--agent-home <dir>", "Target an alternate agent home (managed installs and clean-room verification)").option("--name <name>", "Register a named pinned MCP, such as topolo-acme").option("--context <context>", "Pin the named MCP to an exact context returned by topolo context ls").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(async (opts) => {
6419
+ program2.command("setup").description("Install the Topolo skill and deterministic local MCP server for supported agent hosts").option("--claude", "Target Claude Code only").option("--codex", "Target Codex only").option("--agent <host...>", "Target one or more hosts by id").option("--all", "Configure every supported local host").option("--project <dir>", "Write project-scoped host files under <dir> where supported").option("--agent-home <dir>", "Target an alternate agent home (managed installs and clean-room verification)").option("--name <name>", "Register a named pinned MCP, such as topolo-acme").option("--context <context>", "Pin the named MCP to an exact context returned by topolo context ls").option("--installation <id>", "Bind the MCP runtime to a registered external-agent installation").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(async (opts) => {
6166
6420
  const mode = resolveOutputMode(opts.json);
6167
6421
  try {
6168
- const { results, mcp } = await installAgentSurfaces({ ...opts, homeDir: opts.agentHome });
6422
+ const { results, mcp } = await installAgentSurfaces({
6423
+ ...opts,
6424
+ installationId: opts.installation,
6425
+ homeDir: opts.agentHome
6426
+ });
6169
6427
  assertAgentSetupSucceeded(results);
6170
6428
  emit({ installed: results, mcpCommand: mcp }, mode, () => formatInstall(results, mcp));
6171
6429
  } catch (error) {
@@ -6180,8 +6438,8 @@ function assertAgentSetupSucceeded(results) {
6180
6438
  }
6181
6439
  }
6182
6440
  async function agentSurfaceStatus(options = {}) {
6183
- const homeDir = options.homeDir ?? homedir3();
6184
- const sourceSkill = await readFile5(join5(skillsSourceDir(), "topolo", "SKILL.md"), "utf8");
6441
+ const homeDir = options.homeDir ?? homedir4();
6442
+ const sourceSkill = await readFile6(join6(skillsSourceDir(), "topolo", "SKILL.md"), "utf8");
6185
6443
  const agents = resolveTargets({
6186
6444
  agent: options.agent,
6187
6445
  all: options.all,
@@ -6190,7 +6448,7 @@ async function agentSurfaceStatus(options = {}) {
6190
6448
  });
6191
6449
  return Promise.all(agents.map(async (agent) => {
6192
6450
  const skillPath = agentSkillPath(agent, homeDir, options.project);
6193
- const installedSkill = skillPath && existsSync4(skillPath) ? await readFile5(skillPath, "utf8") : void 0;
6451
+ const installedSkill = skillPath && existsSync5(skillPath) ? await readFile6(skillPath, "utf8") : void 0;
6194
6452
  const registrations = (await readAgentRegistrations(agent, homeDir, options.project)).filter((registration) => !options.name || registration.name === options.name);
6195
6453
  return {
6196
6454
  agent,
@@ -6204,7 +6462,7 @@ async function agentSurfaceStatus(options = {}) {
6204
6462
  }));
6205
6463
  }
6206
6464
  async function uninstallAgentSurfaces(options) {
6207
- const homeDir = options.homeDir ?? homedir3();
6465
+ const homeDir = options.homeDir ?? homedir4();
6208
6466
  const name = options.name?.trim() || "topolo";
6209
6467
  resolveMcpRegistration({ name, ...name === "topolo" ? {} : { context: "uninstall-validation" } });
6210
6468
  const removed = [];
@@ -6214,8 +6472,8 @@ async function uninstallAgentSurfaces(options) {
6214
6472
  await unregisterAgentConfig(agent, homeDir, name, options.project);
6215
6473
  const remaining = await readAgentRegistrations(agent, homeDir, options.project);
6216
6474
  const skillPath = agentSkillPath(agent, homeDir, options.project);
6217
- const skillRemoved = remaining.length === 0 && Boolean(skillPath && existsSync4(skillPath));
6218
- if (skillRemoved && skillPath) await rm2(dirname4(skillPath), { recursive: true, force: true });
6475
+ const skillRemoved = remaining.length === 0 && Boolean(skillPath && existsSync5(skillPath));
6476
+ if (skillRemoved && skillPath) await rm3(dirname5(skillPath), { recursive: true, force: true });
6219
6477
  removed.push({ agent, name, registrationRemoved, skillRemoved });
6220
6478
  }
6221
6479
  return removed;
@@ -6245,7 +6503,7 @@ function formatStatus(rows) {
6245
6503
 
6246
6504
  // src/commands/doctor.ts
6247
6505
  import { access } from "fs/promises";
6248
- import { homedir as homedir4 } from "os";
6506
+ import { homedir as homedir5 } from "os";
6249
6507
  import { resolve as resolve2 } from "path";
6250
6508
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
6251
6509
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
@@ -6308,7 +6566,7 @@ async function verifyMcpProtocol(command, args, env = definedEnvironment(process
6308
6566
  }
6309
6567
  async function resolveInstalledDoctorTarget(options) {
6310
6568
  const name = options.name?.trim() || "topolo";
6311
- const homeDir = options.agentHome ? resolve2(options.agentHome) : homedir4();
6569
+ const homeDir = options.agentHome ? resolve2(options.agentHome) : homedir5();
6312
6570
  const expected = resolveMcpCommand();
6313
6571
  const requestedAgents = resolveTargets({
6314
6572
  ...options,
@@ -6759,6 +7017,195 @@ function formatWorkspaceMutation(result) {
6759
7017
  Workspace: ${workspace.name} (${workspace.id})${workspace.isDefault ? "\nDefault: yes" : ""}`;
6760
7018
  }
6761
7019
 
7020
+ // src/commands/installations.ts
7021
+ var CREATE_PRINCIPAL = "app_topolo_auth.organization_principals.create";
7022
+ var UPDATE_PRINCIPAL = "app_topolo_auth.organization_principals.update";
7023
+ var REVOKE_CREDENTIAL = "app_topolo_auth.principal_credentials.revoke";
7024
+ var INSTALLATION_BOOTSTRAP_GRANT = "app_topolo_auth:identity";
7025
+ function actionData(value) {
7026
+ if (!value || typeof value !== "object") throw new Error("Topolo returned an invalid action response.");
7027
+ const record2 = value;
7028
+ const data = record2.data;
7029
+ return data && typeof data === "object" ? data : record2;
7030
+ }
7031
+ function requiredRecord(value, name) {
7032
+ if (!value || typeof value !== "object") throw new Error(`Topolo did not return ${name}.`);
7033
+ return value;
7034
+ }
7035
+ function requiredString(record2, key) {
7036
+ const value = record2[key];
7037
+ if (typeof value !== "string" || !value.trim()) throw new Error(`Topolo did not return ${key}.`);
7038
+ return value;
7039
+ }
7040
+ async function createInstallationKey() {
7041
+ const keyPair = await crypto.subtle.generateKey(
7042
+ { name: "ECDSA", namedCurve: "P-256" },
7043
+ true,
7044
+ ["sign", "verify"]
7045
+ );
7046
+ const privateJwk = await crypto.subtle.exportKey("jwk", keyPair.privateKey);
7047
+ return { privateJwk, dpopJkt: await calculateDpopJkt(privateJwk) };
7048
+ }
7049
+ async function registerExternalAgentInstallation(input2, dependencies) {
7050
+ const organizationId = dependencies.identity.organization?.id;
7051
+ if (!organizationId) throw new Error("Select an organization context before registering an installation.");
7052
+ const serviceGrants = Array.from(/* @__PURE__ */ new Set([
7053
+ INSTALLATION_BOOTSTRAP_GRANT,
7054
+ ...input2.serviceGrants.map((grant) => grant.trim()).filter(Boolean)
7055
+ ]));
7056
+ const { privateJwk, dpopJkt } = await createInstallationKey();
7057
+ const principalResult = actionData(await dependencies.client.callAction(CREATE_PRINCIPAL, {
7058
+ orgId: organizationId,
7059
+ principalType: "external_agent",
7060
+ displayName: input2.displayName,
7061
+ accountableUserId: dependencies.identity.user.id,
7062
+ provider: input2.provider,
7063
+ ...input2.model ? { model: input2.model } : {},
7064
+ serviceGrants
7065
+ }, { confirm: true }));
7066
+ const principal = requiredRecord(principalResult.principal, "principal");
7067
+ const principalId = requiredString(principal, "id");
7068
+ const installationId = typeof principal.installationId === "string" ? principal.installationId : principalId;
7069
+ let credentialId = null;
7070
+ try {
7071
+ const credentialResult = actionData(await dependencies.client.issueExternalAgentCredential({
7072
+ orgId: organizationId,
7073
+ principalId,
7074
+ displayName: `${input2.displayName} installation credential`,
7075
+ expiresAt: input2.expiresAt ?? null,
7076
+ dpopJkt
7077
+ }));
7078
+ const credential = requiredRecord(credentialResult.credential, "credential");
7079
+ credentialId = requiredString(credential, "id");
7080
+ const installation = {
7081
+ id: installationId,
7082
+ environment: dependencies.environment,
7083
+ organizationId,
7084
+ principalId,
7085
+ credentialId,
7086
+ clientId: requiredString(credential, "clientId"),
7087
+ displayName: input2.displayName,
7088
+ provider: input2.provider,
7089
+ host: input2.host,
7090
+ ...input2.model ? { model: input2.model } : {},
7091
+ dpopJkt,
7092
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
7093
+ };
7094
+ await (dependencies.write ?? writeExternalAgentInstallation)(installation, {
7095
+ clientSecret: requiredString(credential, "clientSecret"),
7096
+ privateJwk
7097
+ });
7098
+ return installation;
7099
+ } catch (error) {
7100
+ if (credentialId) {
7101
+ await dependencies.client.callAction(REVOKE_CREDENTIAL, {
7102
+ orgId: organizationId,
7103
+ principalId,
7104
+ credentialId
7105
+ }, { confirm: true }).catch(() => void 0);
7106
+ }
7107
+ await dependencies.client.callAction(UPDATE_PRINCIPAL, {
7108
+ orgId: organizationId,
7109
+ principalId,
7110
+ status: "suspended"
7111
+ }, { confirm: true }).catch(() => void 0);
7112
+ throw error;
7113
+ }
7114
+ }
7115
+ async function revokeInstallation(client, installation) {
7116
+ await client.callAction(REVOKE_CREDENTIAL, {
7117
+ orgId: installation.organizationId,
7118
+ principalId: installation.principalId,
7119
+ credentialId: installation.credentialId
7120
+ }, { confirm: true });
7121
+ await client.callAction(UPDATE_PRINCIPAL, {
7122
+ orgId: installation.organizationId,
7123
+ principalId: installation.principalId,
7124
+ status: "suspended"
7125
+ }, { confirm: true });
7126
+ await deleteExternalAgentInstallation(installation.environment, installation.id);
7127
+ }
7128
+ function parseExpiry(value) {
7129
+ if (value === void 0) return void 0;
7130
+ if (value === "never") return null;
7131
+ const timestamp2 = Math.floor(new Date(value).getTime() / 1e3);
7132
+ if (!Number.isSafeInteger(timestamp2) || timestamp2 <= Math.floor(Date.now() / 1e3)) {
7133
+ throw new Error('--expires-at must be a future ISO-8601 timestamp or "never".');
7134
+ }
7135
+ return timestamp2;
7136
+ }
7137
+ async function requiredClient3() {
7138
+ const client = await buildClient({ requireConfirmForWrites: true });
7139
+ if (!client) throw new Error("No credential configured. Run `topolo auth login`.");
7140
+ return client;
7141
+ }
7142
+ function registerInstallationsCommand(program2) {
7143
+ const installations = program2.command("installations").description("Register and manage accountable external agent installations");
7144
+ installations.command("register").requiredOption("--name <name>", "Human-readable installation name").requiredOption("--provider <provider>", "Agent provider, such as openai or anthropic").requiredOption("--host <host>", "Agent host, such as codex or claude").option("--model <model>", "Model identifier recorded for audit attribution").option("--grant <app:scope...>", "One or more explicit application grants", []).option("--expires-at <timestamp>", "Credential expiry in ISO-8601 form, or never", "never").option("--project <dir>", "Write project-scoped host files under <dir> where supported").option("--agent-home <dir>", "Target an alternate agent home").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (opts) => {
7145
+ const mode = resolveOutputMode(opts.json);
7146
+ try {
7147
+ const host = opts.host.trim();
7148
+ if (!SUPPORTED_AGENT_IDS.includes(host)) {
7149
+ throw new Error(`Unsupported agent host "${opts.host}". Supported hosts: ${SUPPORTED_AGENT_IDS.join(", ")}.`);
7150
+ }
7151
+ const topolo = await requiredClient3();
7152
+ const installation = await registerExternalAgentInstallation({
7153
+ displayName: opts.name.trim(),
7154
+ provider: opts.provider.trim(),
7155
+ host,
7156
+ model: opts.model?.trim(),
7157
+ serviceGrants: opts.grant,
7158
+ expiresAt: parseExpiry(opts.expiresAt)
7159
+ }, {
7160
+ client: topolo.client,
7161
+ identity: await topolo.identity.whoami(),
7162
+ environment: selectedEnvironment()
7163
+ });
7164
+ try {
7165
+ const configured = await installAgentSurfaces({
7166
+ agent: [host],
7167
+ project: opts.project,
7168
+ homeDir: opts.agentHome,
7169
+ installationId: installation.id
7170
+ });
7171
+ assertAgentSetupSucceeded(configured.results);
7172
+ } catch (error) {
7173
+ await revokeInstallation(topolo.client, installation).catch(() => void 0);
7174
+ throw error;
7175
+ }
7176
+ emit(
7177
+ { installation },
7178
+ mode,
7179
+ () => `Registered ${installation.displayName} (${installation.id}) for ${installation.host}.`
7180
+ );
7181
+ } catch (error) {
7182
+ fail2(error, mode);
7183
+ }
7184
+ });
7185
+ installations.command("list").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (opts) => {
7186
+ const mode = resolveOutputMode(opts.json);
7187
+ try {
7188
+ const rows = await listExternalAgentInstallations(selectedEnvironment());
7189
+ emit({ installations: rows }, mode, () => rows.length ? rows.map((row) => `${row.id} ${row.displayName} ${row.host} ${row.provider}`).join("\n") : "No external agent installations are registered on this device.");
7190
+ } catch (error) {
7191
+ fail2(error, mode);
7192
+ }
7193
+ });
7194
+ installations.command("revoke <installation-id>").description("Revoke the remote credential and remove its protected local installation").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (installationId, opts) => {
7195
+ const mode = resolveOutputMode(opts.json);
7196
+ try {
7197
+ const environment = selectedEnvironment();
7198
+ const row = (await listExternalAgentInstallations(environment)).find((installation) => installation.id === installationId);
7199
+ if (!row) throw new Error(`Installation ${installationId} was not found on this device.`);
7200
+ const topolo = await requiredClient3();
7201
+ await revokeInstallation(topolo.client, row);
7202
+ emit({ revoked: true, installationId }, mode, () => `Revoked ${installationId}.`);
7203
+ } catch (error) {
7204
+ fail2(error, mode);
7205
+ }
7206
+ });
7207
+ }
7208
+
6762
7209
  // src/index.ts
6763
7210
  installOutputErrorHandlers();
6764
7211
  var program = new Command();
@@ -6812,6 +7259,7 @@ registerDoctorCommand(program);
6812
7259
  registerContextCommand(program);
6813
7260
  registerResourcesCommand(program);
6814
7261
  registerWorkspacesCommand(program);
7262
+ registerInstallationsCommand(program);
6815
7263
  program.parseAsync(argv).catch((err) => {
6816
7264
  process.stderr.write(`Unexpected error: ${err instanceof Error ? err.message : String(err)}
6817
7265
  `);
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@topolo/cli",
3
3
  "packageManager": "pnpm@10.17.1",
4
- "version": "0.11.0",
5
- "topoloPublishedVersion": "0.11.0",
4
+ "version": "0.12.0",
5
+ "topoloPublishedVersion": "0.12.0",
6
6
  "description": "Public command-line interface for Topolo customer organizations, developers, and agents.",
7
7
  "license": "UNLICENSED",
8
8
  "author": "Topolo",
@@ -28,8 +28,8 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@modelcontextprotocol/sdk": "^1.0.0",
31
- "@topolo/mcp": "0.12.0",
32
- "@topolo/sdk": "0.17.1",
31
+ "@topolo/mcp": "0.13.0",
32
+ "@topolo/sdk": "0.21.0",
33
33
  "commander": "^12.1.0"
34
34
  },
35
35
  "devDependencies": {