@topolo/cli 0.13.7 → 0.13.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +173 -24
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -158,7 +158,7 @@ import { platform as platform2 } from "os";
|
|
|
158
158
|
import { createInterface } from "readline/promises";
|
|
159
159
|
import { stdin as input } from "process";
|
|
160
160
|
|
|
161
|
-
// node_modules/.pnpm/@topolo+sdk@0.23.
|
|
161
|
+
// node_modules/.pnpm/@topolo+sdk@0.23.20/node_modules/@topolo/sdk/dist/index.js
|
|
162
162
|
function record(value) {
|
|
163
163
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
164
164
|
}
|
|
@@ -3768,10 +3768,11 @@ async function pollUntilApproved(params) {
|
|
|
3768
3768
|
throw new Error("Device code expired. Run `topolo auth login` again.");
|
|
3769
3769
|
}
|
|
3770
3770
|
|
|
3771
|
-
// node_modules/.pnpm/@topolo+sdk@0.23.
|
|
3772
|
-
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, chmod as chmod2, lstat, readdir as readdir2
|
|
3771
|
+
// node_modules/.pnpm/@topolo+sdk@0.23.20/node_modules/@topolo/sdk/dist/node/credential-store.js
|
|
3772
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, chmod as chmod2, lstat, readdir as readdir2 } from "fs/promises";
|
|
3773
3773
|
import { existsSync as existsSync2, mkdirSync, lstatSync, readdirSync, readFileSync as readFileSync2, openSync, writeFileSync, fsyncSync, closeSync, renameSync, unlinkSync, rmdirSync, linkSync } from "fs";
|
|
3774
3774
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
|
|
3775
|
+
import { constants, fstatSync, readSync } from "fs";
|
|
3775
3776
|
import { homedir as homedir2, platform } from "os";
|
|
3776
3777
|
import { dirname as dirname2, join as join3 } from "path";
|
|
3777
3778
|
import { createHash, randomUUID } from "crypto";
|
|
@@ -4637,6 +4638,7 @@ async function resolveCredential(environment = selectedEnvironment()) {
|
|
|
4637
4638
|
var REFRESH_SKEW_SECONDS = 60;
|
|
4638
4639
|
var CONFIG_LOCK_RETRY_MS = 25;
|
|
4639
4640
|
var CONFIG_LOCK_TIMEOUT_MS = 5e3;
|
|
4641
|
+
var CONFIG_LOCK_DEAD_OWNER_MIN_AGE_MS = 2e3;
|
|
4640
4642
|
var ConfigOwnershipError = class extends TopoloSdkError2 {
|
|
4641
4643
|
constructor() {
|
|
4642
4644
|
super("state_conflict", "Credential configuration ownership cannot be verified. Retain the unresolved operation instead of starting another renewal.");
|
|
@@ -4651,32 +4653,145 @@ function lockEntryExists(path) {
|
|
|
4651
4653
|
throw new ConfigOwnershipError();
|
|
4652
4654
|
}
|
|
4653
4655
|
}
|
|
4656
|
+
function lockIdentity(entry) {
|
|
4657
|
+
return [entry.dev, entry.ino, entry.uid, entry.mode, entry.nlink, entry.size, entry.mtimeMs, entry.ctimeMs];
|
|
4658
|
+
}
|
|
4659
|
+
function privateLockDirectory(path) {
|
|
4660
|
+
const entry = lstatSync(path);
|
|
4661
|
+
if (typeof process.getuid !== "function" || !entry.isDirectory() || entry.isSymbolicLink() || entry.uid !== process.getuid() || (entry.mode & 511) !== 448) throw new ConfigOwnershipError();
|
|
4662
|
+
return entry;
|
|
4663
|
+
}
|
|
4664
|
+
function readPrivateLockMarker(path) {
|
|
4665
|
+
const before = lstatSync(path);
|
|
4666
|
+
if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1 || before.size > 512 || before.uid !== process.getuid?.() || (before.mode & 511) !== 384) throw new ConfigOwnershipError();
|
|
4667
|
+
const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
4668
|
+
try {
|
|
4669
|
+
if (JSON.stringify(lockIdentity(fstatSync(fd))) !== JSON.stringify(lockIdentity(before))) throw new ConfigOwnershipError();
|
|
4670
|
+
const bytes = Buffer.alloc(513);
|
|
4671
|
+
const count = readSync(fd, bytes, 0, bytes.length, 0);
|
|
4672
|
+
if (count !== before.size || count > 512 || JSON.stringify(lockIdentity(fstatSync(fd))) !== JSON.stringify(lockIdentity(before)) || JSON.stringify(lockIdentity(lstatSync(path))) !== JSON.stringify(lockIdentity(before))) throw new ConfigOwnershipError();
|
|
4673
|
+
return bytes.subarray(0, count).toString("utf8");
|
|
4674
|
+
} finally {
|
|
4675
|
+
closeSync(fd);
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4678
|
+
var OWNER_MARKER_NAME = /^owner-[a-f0-9-]{36}\.json$/;
|
|
4679
|
+
function readOwnerMarker(lockPath, name) {
|
|
4680
|
+
const bytes = readPrivateLockMarker(join3(lockPath, name));
|
|
4681
|
+
const owner = JSON.parse(bytes);
|
|
4682
|
+
if (owner.version !== 1 || Object.keys(owner).sort().join(",") !== "identity,pid,version" || name !== "owner-" + owner.identity + ".json" || !Number.isSafeInteger(owner.pid) || owner.pid <= 0) throw new ConfigOwnershipError();
|
|
4683
|
+
return { bytes, pid: owner.pid };
|
|
4684
|
+
}
|
|
4685
|
+
function writePrivateLockRecord(path, bytes) {
|
|
4686
|
+
const fd = openSync(path, "wx", 384);
|
|
4687
|
+
try {
|
|
4688
|
+
writeFileSync(fd, bytes);
|
|
4689
|
+
fsyncSync(fd);
|
|
4690
|
+
} finally {
|
|
4691
|
+
closeSync(fd);
|
|
4692
|
+
}
|
|
4693
|
+
}
|
|
4694
|
+
function deadOwnerMarker(lockPath, ignoreOwnFence = false) {
|
|
4695
|
+
try {
|
|
4696
|
+
if (!ignoreOwnFence && lockEntryExists(lockPath + ".reconcile")) return null;
|
|
4697
|
+
const directory = privateLockDirectory(lockPath);
|
|
4698
|
+
const entries = readdirSync(lockPath);
|
|
4699
|
+
if (entries.length !== 1 || !OWNER_MARKER_NAME.test(entries[0])) return null;
|
|
4700
|
+
const name = entries[0];
|
|
4701
|
+
const { bytes, pid } = readOwnerMarker(lockPath, name);
|
|
4702
|
+
if (Date.now() - lstatSync(join3(lockPath, name)).mtimeMs < CONFIG_LOCK_DEAD_OWNER_MIN_AGE_MS) return null;
|
|
4703
|
+
try {
|
|
4704
|
+
process.kill(pid, 0);
|
|
4705
|
+
return null;
|
|
4706
|
+
} catch (error) {
|
|
4707
|
+
if (error.code !== "ESRCH") return null;
|
|
4708
|
+
}
|
|
4709
|
+
return { directory, name, bytes, pid };
|
|
4710
|
+
} catch {
|
|
4711
|
+
return null;
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4714
|
+
function reclaimDeadOwnerLock(lockPath, markerName, marker) {
|
|
4715
|
+
const observed = deadOwnerMarker(lockPath);
|
|
4716
|
+
if (!observed) return false;
|
|
4717
|
+
const fence = lockPath + ".reconcile";
|
|
4718
|
+
try {
|
|
4719
|
+
mkdirSync(fence, { mode: 448 });
|
|
4720
|
+
} catch {
|
|
4721
|
+
return false;
|
|
4722
|
+
}
|
|
4723
|
+
const fenceMarker = join3(fence, markerName);
|
|
4724
|
+
const quarantine = join3(dirname2(lockPath), ".credential-reclaim-" + randomUUID2());
|
|
4725
|
+
let captured;
|
|
4726
|
+
let reclaimed = false;
|
|
4727
|
+
try {
|
|
4728
|
+
writePrivateLockRecord(fenceMarker, marker);
|
|
4729
|
+
const current = deadOwnerMarker(lockPath, true);
|
|
4730
|
+
if (!current || current.name !== observed.name || current.bytes !== observed.bytes || current.directory.dev !== observed.directory.dev || current.directory.ino !== observed.directory.ino) return false;
|
|
4731
|
+
mkdirSync(quarantine, { mode: 448 });
|
|
4732
|
+
writePrivateLockRecord(join3(quarantine, "receipt.json"), JSON.stringify({
|
|
4733
|
+
schemaVersion: 1,
|
|
4734
|
+
disposition: "DEAD_OWNER_RECLAIMED",
|
|
4735
|
+
lockPath,
|
|
4736
|
+
deadOwnerMarker: current.name,
|
|
4737
|
+
deadOwnerPid: current.pid,
|
|
4738
|
+
deadOwnerEvidence: "ESRCH",
|
|
4739
|
+
reclaimedByPid: process.pid,
|
|
4740
|
+
reclaimedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4741
|
+
configCredentialOrOAuthEffectsByThisReclaim: "none"
|
|
4742
|
+
}, null, 2));
|
|
4743
|
+
captured = join3(quarantine, current.name);
|
|
4744
|
+
renameSync(join3(lockPath, current.name), captured);
|
|
4745
|
+
const directory = privateLockDirectory(lockPath);
|
|
4746
|
+
if (directory.dev !== observed.directory.dev || directory.ino !== observed.directory.ino || readdirSync(lockPath).length) throw new ConfigOwnershipError();
|
|
4747
|
+
writePrivateLockRecord(join3(lockPath, markerName), marker);
|
|
4748
|
+
reclaimed = true;
|
|
4749
|
+
} catch {
|
|
4750
|
+
if (captured && !reclaimed) {
|
|
4751
|
+
try {
|
|
4752
|
+
linkSync(captured, join3(lockPath, observed.name));
|
|
4753
|
+
} catch {
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
} finally {
|
|
4757
|
+
try {
|
|
4758
|
+
if (readPrivateLockMarker(fenceMarker) !== marker || readdirSync(fence).length !== 1) throw new ConfigOwnershipError();
|
|
4759
|
+
unlinkSync(fenceMarker);
|
|
4760
|
+
rmdirSync(fence);
|
|
4761
|
+
} catch {
|
|
4762
|
+
reclaimed = false;
|
|
4763
|
+
}
|
|
4764
|
+
}
|
|
4765
|
+
return reclaimed;
|
|
4766
|
+
}
|
|
4767
|
+
var RELEASE_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
4654
4768
|
async function withConfigWriteLock(operation) {
|
|
4655
4769
|
const lockPath = `${configPath()}.lock`;
|
|
4656
4770
|
await mkdir2(dirname2(lockPath), { recursive: true });
|
|
4657
4771
|
const startedAt = performance.now();
|
|
4772
|
+
const identity = randomUUID2();
|
|
4773
|
+
const markerName = "owner-" + identity + ".json";
|
|
4774
|
+
const markerPath = join3(lockPath, markerName);
|
|
4775
|
+
const marker = JSON.stringify({ version: 1, identity, pid: process.pid });
|
|
4658
4776
|
while (true) {
|
|
4659
4777
|
try {
|
|
4660
4778
|
if (lockEntryExists(lockPath + ".reconcile")) throw Object.assign(new Error(), { code: "EEXIST" });
|
|
4661
|
-
|
|
4779
|
+
mkdirSync(lockPath, { mode: 448 });
|
|
4662
4780
|
if (lockEntryExists(lockPath + ".reconcile")) {
|
|
4663
4781
|
throw new ConfigOwnershipError();
|
|
4664
4782
|
}
|
|
4783
|
+
writeFileSync(markerPath, marker, { flag: "wx", mode: 384 });
|
|
4665
4784
|
break;
|
|
4666
4785
|
} catch (error) {
|
|
4667
4786
|
if (error.code !== "EEXIST") throw error;
|
|
4787
|
+
if (reclaimDeadOwnerLock(lockPath, markerName, marker)) break;
|
|
4668
4788
|
if (performance.now() - startedAt >= CONFIG_LOCK_TIMEOUT_MS) {
|
|
4669
4789
|
throw new TopoloSdkError2("state_conflict", "Credential configuration is still held or unresolved. This waiter did not dispatch a renewal.");
|
|
4670
4790
|
}
|
|
4671
4791
|
await new Promise((resolve22) => setTimeout(resolve22, CONFIG_LOCK_RETRY_MS));
|
|
4672
4792
|
}
|
|
4673
4793
|
}
|
|
4674
|
-
const directory =
|
|
4675
|
-
const identity = randomUUID2();
|
|
4676
|
-
const markerName = "owner-" + identity + ".json";
|
|
4677
|
-
const markerPath = join3(lockPath, markerName);
|
|
4678
|
-
const marker = JSON.stringify({ version: 1, identity, pid: process.pid });
|
|
4679
|
-
await writeFile2(markerPath, marker, { flag: "wx", mode: 384 });
|
|
4794
|
+
const directory = lstatSync(lockPath);
|
|
4680
4795
|
const assertDirectory = async () => {
|
|
4681
4796
|
const current = await lstat(lockPath);
|
|
4682
4797
|
if (lockEntryExists(lockPath + ".reconcile") || !current.isDirectory() || current.isSymbolicLink() || current.dev !== directory.dev || current.ino !== directory.ino)
|
|
@@ -4700,10 +4815,14 @@ async function withConfigWriteLock(operation) {
|
|
|
4700
4815
|
throw new ConfigOwnershipError();
|
|
4701
4816
|
};
|
|
4702
4817
|
const assertOwnedSync = () => {
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4818
|
+
try {
|
|
4819
|
+
assertDirectorySync();
|
|
4820
|
+
const entries = readdirSync(lockPath);
|
|
4821
|
+
const entry = lstatSync(markerPath);
|
|
4822
|
+
if (entries.length !== 1 || entries[0] !== markerName || !entry.isFile() || entry.isSymbolicLink() || readFileSync2(markerPath, "utf8") !== marker) throw new ConfigOwnershipError();
|
|
4823
|
+
} catch {
|
|
4824
|
+
throw new ConfigOwnershipError();
|
|
4825
|
+
}
|
|
4707
4826
|
};
|
|
4708
4827
|
const persist = async (update) => {
|
|
4709
4828
|
const path = configPath();
|
|
@@ -4732,14 +4851,14 @@ async function withConfigWriteLock(operation) {
|
|
|
4732
4851
|
if (temporaryOwned) unlinkSync(temporary);
|
|
4733
4852
|
}
|
|
4734
4853
|
};
|
|
4735
|
-
const release =
|
|
4736
|
-
|
|
4854
|
+
const release = () => {
|
|
4855
|
+
assertOwnedSync();
|
|
4737
4856
|
const quarantine = join3(dirname2(lockPath), ".credential-release-" + randomUUID2());
|
|
4738
|
-
|
|
4857
|
+
mkdirSync(quarantine, { mode: 448 });
|
|
4739
4858
|
const captured = join3(quarantine, markerName);
|
|
4740
4859
|
try {
|
|
4741
|
-
|
|
4742
|
-
|
|
4860
|
+
assertOwnedSync();
|
|
4861
|
+
renameSync(markerPath, captured);
|
|
4743
4862
|
assertDirectorySync();
|
|
4744
4863
|
const entry = lstatSync(captured);
|
|
4745
4864
|
if (!entry.isFile() || entry.isSymbolicLink() || readFileSync2(captured, "utf8") !== marker || readdirSync(lockPath).length) throw new ConfigOwnershipError();
|
|
@@ -4751,20 +4870,40 @@ async function withConfigWriteLock(operation) {
|
|
|
4751
4870
|
} catch {
|
|
4752
4871
|
if (existsSync2(captured)) {
|
|
4753
4872
|
try {
|
|
4754
|
-
|
|
4873
|
+
linkSync(captured, markerPath);
|
|
4755
4874
|
} catch {
|
|
4756
4875
|
}
|
|
4757
4876
|
}
|
|
4758
4877
|
throw new ConfigOwnershipError();
|
|
4759
4878
|
}
|
|
4760
4879
|
};
|
|
4880
|
+
let held = true;
|
|
4881
|
+
const releaseOnce = () => {
|
|
4882
|
+
if (!held) return;
|
|
4883
|
+
held = false;
|
|
4884
|
+
process.off("exit", onExit);
|
|
4885
|
+
for (const signal of RELEASE_SIGNALS) process.off(signal, onSignal);
|
|
4886
|
+
release();
|
|
4887
|
+
};
|
|
4888
|
+
const onExit = () => {
|
|
4889
|
+
try {
|
|
4890
|
+
releaseOnce();
|
|
4891
|
+
} catch {
|
|
4892
|
+
}
|
|
4893
|
+
};
|
|
4894
|
+
const onSignal = (signal) => {
|
|
4895
|
+
onExit();
|
|
4896
|
+
if (process.listenerCount(signal) === 0) process.kill(process.pid, signal);
|
|
4897
|
+
};
|
|
4898
|
+
process.on("exit", onExit);
|
|
4899
|
+
for (const signal of RELEASE_SIGNALS) process.on(signal, onSignal);
|
|
4761
4900
|
try {
|
|
4762
4901
|
await assertOwned();
|
|
4763
4902
|
const result = await operation(assertOwned, persist);
|
|
4764
4903
|
await assertOwned();
|
|
4765
4904
|
return result;
|
|
4766
4905
|
} finally {
|
|
4767
|
-
|
|
4906
|
+
releaseOnce();
|
|
4768
4907
|
}
|
|
4769
4908
|
}
|
|
4770
4909
|
function renewalFailure(error) {
|
|
@@ -6657,6 +6796,7 @@ async function readGooseRegistrations(path) {
|
|
|
6657
6796
|
continue;
|
|
6658
6797
|
}
|
|
6659
6798
|
if (inExtensions && /^\S/.test(rawLine) && rawLine.trim()) {
|
|
6799
|
+
if (current?.command) registrations.push(current);
|
|
6660
6800
|
inExtensions = false;
|
|
6661
6801
|
inTopolo = false;
|
|
6662
6802
|
current = void 0;
|
|
@@ -6664,6 +6804,7 @@ async function readGooseRegistrations(path) {
|
|
|
6664
6804
|
if (!inExtensions) continue;
|
|
6665
6805
|
const extension = rawLine.match(/^ ([A-Za-z0-9_-]+):\s*$/);
|
|
6666
6806
|
if (extension) {
|
|
6807
|
+
if (current?.command) registrations.push(current);
|
|
6667
6808
|
inTopolo = true;
|
|
6668
6809
|
current = isPublicRegistrationName(extension[1]) ? { name: extension[1], command: "", args: [], env: {} } : void 0;
|
|
6669
6810
|
continue;
|
|
@@ -6841,7 +6982,7 @@ function message(error) {
|
|
|
6841
6982
|
|
|
6842
6983
|
// src/commands/agent-config.ts
|
|
6843
6984
|
import { existsSync as existsSync5 } from "fs";
|
|
6844
|
-
import { mkdir as mkdir3, readFile as readFile5, rename as
|
|
6985
|
+
import { mkdir as mkdir3, readFile as readFile5, rename as rename2, writeFile as writeFile3 } from "fs/promises";
|
|
6845
6986
|
import { dirname as dirname5 } from "path";
|
|
6846
6987
|
async function installAgentSkill(agent, source, homeDir, projectDir) {
|
|
6847
6988
|
const path = agentSkillPath(agent, homeDir, projectDir);
|
|
@@ -6877,6 +7018,7 @@ async function unregisterAgentConfig(agent, homeDir, name, projectDir) {
|
|
|
6877
7018
|
function registrationEnvironment(agent, registration) {
|
|
6878
7019
|
return {
|
|
6879
7020
|
TOPOLO_AGENT_NAME: AGENT_NAME_FOR[agent],
|
|
7021
|
+
...registration.configPath ? { TOPOLO_CONFIG_PATH: registration.configPath } : {},
|
|
6880
7022
|
...registration.installationId ? { TOPOLO_AGENT_INSTALLATION_ID: registration.installationId } : {},
|
|
6881
7023
|
...registration.context ? { TOPOLO_CONTEXT: registration.context, TOPOLO_ENV: registration.environment } : {}
|
|
6882
7024
|
};
|
|
@@ -7008,7 +7150,7 @@ async function atomicWrite(path, contents) {
|
|
|
7008
7150
|
await mkdir3(dirname5(path), { recursive: true });
|
|
7009
7151
|
const temporaryPath = path + ".topolo-" + process.pid + ".tmp";
|
|
7010
7152
|
await writeFile3(temporaryPath, contents, { encoding: "utf8", mode: 384 });
|
|
7011
|
-
await
|
|
7153
|
+
await rename2(temporaryPath, path);
|
|
7012
7154
|
}
|
|
7013
7155
|
function withoutMcpServerSections(text2, name) {
|
|
7014
7156
|
const section = "[mcp_servers." + name + "]";
|
|
@@ -7072,7 +7214,14 @@ function resolveMcpRegistration(options) {
|
|
|
7072
7214
|
if (installationId && /[\r\n]/.test(installationId)) {
|
|
7073
7215
|
throw new Error("Installation id must be a single-line identifier.");
|
|
7074
7216
|
}
|
|
7075
|
-
|
|
7217
|
+
const configPath2 = process.env.TOPOLO_CONFIG_PATH;
|
|
7218
|
+
return {
|
|
7219
|
+
name,
|
|
7220
|
+
...context ? { context } : {},
|
|
7221
|
+
environment,
|
|
7222
|
+
...installationId ? { installationId } : {},
|
|
7223
|
+
...configPath2 ? { configPath: configPath2 } : {}
|
|
7224
|
+
};
|
|
7076
7225
|
}
|
|
7077
7226
|
async function installForAgent(agent, sourceDir, mcp, opts) {
|
|
7078
7227
|
const source = await readFile6(join7(sourceDir, "topolo", "SKILL.md"), "utf8");
|
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.13.
|
|
5
|
-
"topoloPublishedVersion": "0.13.
|
|
4
|
+
"version": "0.13.8",
|
|
5
|
+
"topoloPublishedVersion": "0.13.8",
|
|
6
6
|
"description": "Public command-line interface for Topolo customer organizations, developers, and agents.",
|
|
7
7
|
"license": "UNLICENSED",
|
|
8
8
|
"author": "Topolo",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
34
|
-
"@topolo/mcp": "0.14.
|
|
35
|
-
"@topolo/sdk": "0.23.
|
|
34
|
+
"@topolo/mcp": "0.14.25",
|
|
35
|
+
"@topolo/sdk": "0.23.20",
|
|
36
36
|
"commander": "^12.1.0",
|
|
37
37
|
"undici": "6.29.0"
|
|
38
38
|
},
|