bla-agents 1.1.0 → 1.6.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.
- package/.agents/skills/blind-llm-agents/SKILL.md +20 -14
- package/DISCLAIMER.md +15 -7
- package/README.md +153 -198
- package/dist/bundler/index.d.ts +19 -2
- package/dist/bundler/index.d.ts.map +1 -1
- package/dist/bundler/index.js +47 -4
- package/dist/bundler/index.js.map +1 -1
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +123 -18
- package/dist/cli.js.map +1 -1
- package/dist/core/config.d.ts +54 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +160 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/coverage-contract.d.ts +97 -0
- package/dist/core/coverage-contract.d.ts.map +1 -0
- package/dist/core/coverage-contract.js +164 -0
- package/dist/core/coverage-contract.js.map +1 -0
- package/dist/core/debug-posture.d.ts +40 -0
- package/dist/core/debug-posture.d.ts.map +1 -0
- package/dist/core/debug-posture.js +197 -0
- package/dist/core/debug-posture.js.map +1 -0
- package/dist/core/detector.d.ts.map +1 -1
- package/dist/core/detector.js +58 -6
- package/dist/core/detector.js.map +1 -1
- package/dist/core/identifiers.d.ts +32 -0
- package/dist/core/identifiers.d.ts.map +1 -0
- package/dist/core/identifiers.js +263 -0
- package/dist/core/identifiers.js.map +1 -0
- package/dist/core/llm-directives.d.ts +12 -0
- package/dist/core/llm-directives.d.ts.map +1 -0
- package/dist/core/llm-directives.js +23 -0
- package/dist/core/llm-directives.js.map +1 -0
- package/dist/core/prng.d.ts +10 -1
- package/dist/core/prng.d.ts.map +1 -1
- package/dist/core/prng.js +27 -2
- package/dist/core/prng.js.map +1 -1
- package/dist/core/release-exposure.d.ts.map +1 -1
- package/dist/core/release-exposure.js +13 -2
- package/dist/core/release-exposure.js.map +1 -1
- package/dist/core/release-plan.d.ts +68 -0
- package/dist/core/release-plan.d.ts.map +1 -0
- package/dist/core/release-plan.js +244 -0
- package/dist/core/release-plan.js.map +1 -0
- package/dist/core/sarif.d.ts +53 -0
- package/dist/core/sarif.d.ts.map +1 -0
- package/dist/core/sarif.js +95 -0
- package/dist/core/sarif.js.map +1 -0
- package/dist/core/sqlite-runtime.d.ts +13 -0
- package/dist/core/sqlite-runtime.d.ts.map +1 -1
- package/dist/core/sqlite-runtime.js +38 -8
- package/dist/core/sqlite-runtime.js.map +1 -1
- package/dist/core/types.d.ts +7 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/engine/auditor.d.ts.map +1 -1
- package/dist/engine/auditor.js +380 -19
- package/dist/engine/auditor.js.map +1 -1
- package/dist/engine/benchmark.d.ts +1 -1
- package/dist/engine/benchmark.d.ts.map +1 -1
- package/dist/engine/benchmark.js +121 -41
- package/dist/engine/benchmark.js.map +1 -1
- package/dist/engine/defender.d.ts +1 -0
- package/dist/engine/defender.d.ts.map +1 -1
- package/dist/engine/defender.js +7 -1
- package/dist/engine/defender.js.map +1 -1
- package/dist/engine/harden.d.ts +61 -0
- package/dist/engine/harden.d.ts.map +1 -0
- package/dist/engine/harden.js +294 -0
- package/dist/engine/harden.js.map +1 -0
- package/dist/engine/protector.d.ts.map +1 -1
- package/dist/engine/protector.js +242 -26
- package/dist/engine/protector.js.map +1 -1
- package/dist/identity.d.ts +112 -2
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +112 -2
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +26 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -9
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +59 -152
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +466 -50
- package/dist/mcp/server.js.map +1 -1
- package/dist/setup/clients.d.ts +49 -1
- package/dist/setup/clients.d.ts.map +1 -1
- package/dist/setup/clients.js +1193 -20
- package/dist/setup/clients.js.map +1 -1
- package/dist/setup/doctor.d.ts +58 -14
- package/dist/setup/doctor.d.ts.map +1 -1
- package/dist/setup/doctor.js +316 -22
- package/dist/setup/doctor.js.map +1 -1
- package/dist/setup/setup.d.ts +77 -7
- package/dist/setup/setup.d.ts.map +1 -1
- package/dist/setup/setup.js +591 -62
- package/dist/setup/setup.js.map +1 -1
- package/dist/shields/chaff/chaff-generator.d.ts.map +1 -1
- package/dist/shields/chaff/chaff-generator.js +12 -3
- package/dist/shields/chaff/chaff-generator.js.map +1 -1
- package/dist/shields/deception/architecture-mirage.js +1 -1
- package/dist/shields/deception/architecture-mirage.js.map +1 -1
- package/dist/shields/deception/canary-honeypot.d.ts +33 -0
- package/dist/shields/deception/canary-honeypot.d.ts.map +1 -0
- package/dist/shields/deception/canary-honeypot.js +95 -0
- package/dist/shields/deception/canary-honeypot.js.map +1 -0
- package/dist/shields/deception/cognitive-armor-trigger.d.ts +43 -0
- package/dist/shields/deception/cognitive-armor-trigger.d.ts.map +1 -0
- package/dist/shields/deception/cognitive-armor-trigger.js +204 -0
- package/dist/shields/deception/cognitive-armor-trigger.js.map +1 -0
- package/dist/shields/deception/crash-trace-decoy.js +1 -1
- package/dist/shields/deception/crash-trace-decoy.js.map +1 -1
- package/dist/shields/deception/decompiler-illusion.js +1 -1
- package/dist/shields/deception/decompiler-illusion.js.map +1 -1
- package/dist/shields/deception/diff-noise-injector.js +1 -1
- package/dist/shields/deception/diff-noise-injector.js.map +1 -1
- package/dist/shields/deception/evidence-contract-poison.js +5 -5
- package/dist/shields/deception/evidence-contract-poison.js.map +1 -1
- package/dist/shields/deception/format-payloads.d.ts +21 -0
- package/dist/shields/deception/format-payloads.d.ts.map +1 -0
- package/dist/shields/deception/format-payloads.js +24 -0
- package/dist/shields/deception/format-payloads.js.map +1 -0
- package/dist/shields/deception/index.d.ts +10 -2
- package/dist/shields/deception/index.d.ts.map +1 -1
- package/dist/shields/deception/index.js +20 -3
- package/dist/shields/deception/index.js.map +1 -1
- package/dist/shields/deception/prompt-completion-poison.d.ts +3 -2
- package/dist/shields/deception/prompt-completion-poison.d.ts.map +1 -1
- package/dist/shields/deception/prompt-completion-poison.js +6 -3
- package/dist/shields/deception/prompt-completion-poison.js.map +1 -1
- package/dist/shields/deception/shadow-business-logic.js +4 -4
- package/dist/shields/deception/shadow-business-logic.js.map +1 -1
- package/dist/shields/deception/system-diagnostic-decoy.d.ts +36 -0
- package/dist/shields/deception/system-diagnostic-decoy.d.ts.map +1 -0
- package/dist/shields/deception/system-diagnostic-decoy.js +101 -0
- package/dist/shields/deception/system-diagnostic-decoy.js.map +1 -0
- package/dist/shields/exhaustion/asar-header-amplifier.d.ts +12 -3
- package/dist/shields/exhaustion/asar-header-amplifier.d.ts.map +1 -1
- package/dist/shields/exhaustion/asar-header-amplifier.js +30 -8
- package/dist/shields/exhaustion/asar-header-amplifier.js.map +1 -1
- package/dist/shields/exhaustion/bpe-token-amplifier.js +2 -2
- package/dist/shields/exhaustion/bpe-token-amplifier.js.map +1 -1
- package/dist/shields/exhaustion/callgraph-labyrinth.js +2 -2
- package/dist/shields/exhaustion/callgraph-labyrinth.js.map +1 -1
- package/dist/shields/exhaustion/cdp-shape-budget-breaker.js +1 -1
- package/dist/shields/exhaustion/cdp-shape-budget-breaker.js.map +1 -1
- package/dist/shields/exhaustion/frida-capture-saturation.d.ts +10 -5
- package/dist/shields/exhaustion/frida-capture-saturation.d.ts.map +1 -1
- package/dist/shields/exhaustion/frida-capture-saturation.js +21 -8
- package/dist/shields/exhaustion/frida-capture-saturation.js.map +1 -1
- package/dist/shields/exhaustion/index.d.ts +2 -2
- package/dist/shields/exhaustion/index.d.ts.map +1 -1
- package/dist/shields/exhaustion/index.js +2 -2
- package/dist/shields/exhaustion/index.js.map +1 -1
- package/dist/shields/exhaustion/obligation-flooder.d.ts +23 -6
- package/dist/shields/exhaustion/obligation-flooder.d.ts.map +1 -1
- package/dist/shields/exhaustion/obligation-flooder.js +29 -12
- package/dist/shields/exhaustion/obligation-flooder.js.map +1 -1
- package/dist/shields/exhaustion/rea-budget-matrix.d.ts +8 -2
- package/dist/shields/exhaustion/rea-budget-matrix.d.ts.map +1 -1
- package/dist/shields/exhaustion/rea-budget-matrix.js +139 -16
- package/dist/shields/exhaustion/rea-budget-matrix.js.map +1 -1
- package/dist/shields/exhaustion/regex-worker-tarpit.d.ts.map +1 -1
- package/dist/shields/exhaustion/regex-worker-tarpit.js +2 -1
- package/dist/shields/exhaustion/regex-worker-tarpit.js.map +1 -1
- package/dist/shields/exhaustion/wasm-interface-exhaustion.d.ts +7 -4
- package/dist/shields/exhaustion/wasm-interface-exhaustion.d.ts.map +1 -1
- package/dist/shields/exhaustion/wasm-interface-exhaustion.js +16 -7
- package/dist/shields/exhaustion/wasm-interface-exhaustion.js.map +1 -1
- package/dist/shields/formats/android-dex-xref.d.ts +19 -0
- package/dist/shields/formats/android-dex-xref.d.ts.map +1 -0
- package/dist/shields/formats/android-dex-xref.js +427 -0
- package/dist/shields/formats/android-dex-xref.js.map +1 -0
- package/dist/shields/formats/android-manifest.d.ts +44 -0
- package/dist/shields/formats/android-manifest.d.ts.map +1 -0
- package/dist/shields/formats/android-manifest.js +240 -0
- package/dist/shields/formats/android-manifest.js.map +1 -0
- package/dist/shields/formats/android-r8.d.ts +44 -0
- package/dist/shields/formats/android-r8.d.ts.map +1 -0
- package/dist/shields/formats/android-r8.js +246 -0
- package/dist/shields/formats/android-r8.js.map +1 -0
- package/dist/shields/formats/apple-plist.d.ts +15 -0
- package/dist/shields/formats/apple-plist.d.ts.map +1 -1
- package/dist/shields/formats/apple-plist.js +59 -1
- package/dist/shields/formats/apple-plist.js.map +1 -1
- package/dist/shields/formats/archive.d.ts +27 -6
- package/dist/shields/formats/archive.d.ts.map +1 -1
- package/dist/shields/formats/archive.js +247 -45
- package/dist/shields/formats/archive.js.map +1 -1
- package/dist/shields/formats/asar.d.ts +50 -10
- package/dist/shields/formats/asar.d.ts.map +1 -1
- package/dist/shields/formats/asar.js +265 -39
- package/dist/shields/formats/asar.js.map +1 -1
- package/dist/shields/formats/bun-standalone.d.ts +78 -0
- package/dist/shields/formats/bun-standalone.d.ts.map +1 -0
- package/dist/shields/formats/bun-standalone.js +272 -0
- package/dist/shields/formats/bun-standalone.js.map +1 -0
- package/dist/shields/formats/dotnet-managed.d.ts +9 -0
- package/dist/shields/formats/dotnet-managed.d.ts.map +1 -1
- package/dist/shields/formats/dotnet-managed.js +26 -1
- package/dist/shields/formats/dotnet-managed.js.map +1 -1
- package/dist/shields/formats/electron-fuses.d.ts +63 -8
- package/dist/shields/formats/electron-fuses.d.ts.map +1 -1
- package/dist/shields/formats/electron-fuses.js +297 -65
- package/dist/shields/formats/electron-fuses.js.map +1 -1
- package/dist/shields/formats/erlang-beam.d.ts +34 -0
- package/dist/shields/formats/erlang-beam.d.ts.map +1 -0
- package/dist/shields/formats/erlang-beam.js +319 -0
- package/dist/shields/formats/erlang-beam.js.map +1 -0
- package/dist/shields/formats/evm-bytecode.d.ts +28 -0
- package/dist/shields/formats/evm-bytecode.d.ts.map +1 -0
- package/dist/shields/formats/evm-bytecode.js +124 -0
- package/dist/shields/formats/evm-bytecode.js.map +1 -0
- package/dist/shields/formats/go-flutter-exhaustion.d.ts +55 -0
- package/dist/shields/formats/go-flutter-exhaustion.d.ts.map +1 -0
- package/dist/shields/formats/go-flutter-exhaustion.js +346 -0
- package/dist/shields/formats/go-flutter-exhaustion.js.map +1 -0
- package/dist/shields/formats/gpu-ptx.d.ts +40 -0
- package/dist/shields/formats/gpu-ptx.d.ts.map +1 -0
- package/dist/shields/formats/gpu-ptx.js +62 -0
- package/dist/shields/formats/gpu-ptx.js.map +1 -0
- package/dist/shields/formats/headers.d.ts.map +1 -1
- package/dist/shields/formats/headers.js +54 -14
- package/dist/shields/formats/headers.js.map +1 -1
- package/dist/shields/formats/jvm-bytecode.d.ts +20 -0
- package/dist/shields/formats/jvm-bytecode.d.ts.map +1 -0
- package/dist/shields/formats/jvm-bytecode.js +134 -0
- package/dist/shields/formats/jvm-bytecode.js.map +1 -0
- package/dist/shields/formats/mobile-game.d.ts +4 -0
- package/dist/shields/formats/mobile-game.d.ts.map +1 -1
- package/dist/shields/formats/mobile-game.js +16 -0
- package/dist/shields/formats/mobile-game.js.map +1 -1
- package/dist/shields/formats/model.d.ts +22 -8
- package/dist/shields/formats/model.d.ts.map +1 -1
- package/dist/shields/formats/model.js +231 -59
- package/dist/shields/formats/model.js.map +1 -1
- package/dist/shields/formats/native.d.ts +74 -11
- package/dist/shields/formats/native.d.ts.map +1 -1
- package/dist/shields/formats/native.js +557 -109
- package/dist/shields/formats/native.js.map +1 -1
- package/dist/shields/formats/pcap-capture.d.ts +27 -0
- package/dist/shields/formats/pcap-capture.d.ts.map +1 -0
- package/dist/shields/formats/pcap-capture.js +201 -0
- package/dist/shields/formats/pcap-capture.js.map +1 -0
- package/dist/shields/formats/python.d.ts +22 -1
- package/dist/shields/formats/python.d.ts.map +1 -1
- package/dist/shields/formats/python.js +568 -27
- package/dist/shields/formats/python.js.map +1 -1
- package/dist/shields/formats/sourcemap.d.ts.map +1 -1
- package/dist/shields/formats/sourcemap.js +2 -0
- package/dist/shields/formats/sourcemap.js.map +1 -1
- package/dist/shields/formats/sqlite.d.ts +87 -15
- package/dist/shields/formats/sqlite.d.ts.map +1 -1
- package/dist/shields/formats/sqlite.js +377 -42
- package/dist/shields/formats/sqlite.js.map +1 -1
- package/dist/shields/formats/wasm.d.ts +6 -0
- package/dist/shields/formats/wasm.d.ts.map +1 -1
- package/dist/shields/formats/wasm.js +118 -3
- package/dist/shields/formats/wasm.js.map +1 -1
- package/dist/shields/javascript/ast-shield.d.ts +26 -5
- package/dist/shields/javascript/ast-shield.d.ts.map +1 -1
- package/dist/shields/javascript/ast-shield.js +748 -104
- package/dist/shields/javascript/ast-shield.js.map +1 -1
- package/dist/shields/runtime/memory-diff-shield.d.ts +56 -0
- package/dist/shields/runtime/memory-diff-shield.d.ts.map +1 -0
- package/dist/shields/runtime/memory-diff-shield.js +170 -0
- package/dist/shields/runtime/memory-diff-shield.js.map +1 -0
- package/dist/shields/runtime/runtime-shield.d.ts +12 -16
- package/dist/shields/runtime/runtime-shield.d.ts.map +1 -1
- package/dist/shields/runtime/runtime-shield.js +30 -24
- package/dist/shields/runtime/runtime-shield.js.map +1 -1
- package/dist/shields/tarpit/opaque-predicates.d.ts.map +1 -1
- package/dist/shields/tarpit/opaque-predicates.js +3 -3
- package/dist/shields/tarpit/opaque-predicates.js.map +1 -1
- package/dist/shields/tarpit/tarpit-generator.d.ts +9 -4
- package/dist/shields/tarpit/tarpit-generator.d.ts.map +1 -1
- package/dist/shields/tarpit/tarpit-generator.js +30 -23
- package/dist/shields/tarpit/tarpit-generator.js.map +1 -1
- package/package.json +13 -6
package/dist/setup/setup.js
CHANGED
|
@@ -1,9 +1,107 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmdirSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
2
4
|
import { dirname, join, resolve } from "node:path";
|
|
3
5
|
import { fileURLToPath } from "node:url";
|
|
4
6
|
import { BLA_IDENTITY } from "../identity.js";
|
|
5
|
-
import { resolveSupportedAgentClients, } from "./clients.js";
|
|
6
|
-
function
|
|
7
|
+
import { getClientConfigAdapter, normalizeClientId, resolveSupportedAgentClients, } from "./clients.js";
|
|
8
|
+
function sha256Hex(content) {
|
|
9
|
+
return createHash("sha256").update(content).digest("hex");
|
|
10
|
+
}
|
|
11
|
+
function hashMcpEntry(entry) {
|
|
12
|
+
return sha256Hex(JSON.stringify({
|
|
13
|
+
command: entry.command,
|
|
14
|
+
args: [...entry.args],
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
function isBlaOwnedMcpEntry(entry) {
|
|
18
|
+
const cmd = entry.command.trim();
|
|
19
|
+
const joinedArgs = entry.args.join(" ");
|
|
20
|
+
if (cmd === "bla" ||
|
|
21
|
+
cmd === "bla-agents" ||
|
|
22
|
+
cmd === "bla-shield" ||
|
|
23
|
+
cmd.endsWith("/bla") ||
|
|
24
|
+
cmd.endsWith("\\bla") ||
|
|
25
|
+
cmd.endsWith("/bla-agents") ||
|
|
26
|
+
cmd.endsWith("\\bla-agents") ||
|
|
27
|
+
cmd.endsWith("/bla-shield") ||
|
|
28
|
+
cmd.endsWith("\\bla-shield")) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
if (joinedArgs.includes(BLA_IDENTITY.packageName) ||
|
|
32
|
+
joinedArgs.includes(BLA_IDENTITY.packageAlias) ||
|
|
33
|
+
joinedArgs.includes("dist/cli.js") ||
|
|
34
|
+
joinedArgs.includes("src/cli.ts")) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
function writeFileAtomicSync(targetPath, content) {
|
|
40
|
+
const dir = dirname(targetPath);
|
|
41
|
+
mkdirSync(dir, { recursive: true });
|
|
42
|
+
const tmpPath = `${targetPath}.bla-tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
|
|
43
|
+
try {
|
|
44
|
+
writeFileSync(tmpPath, content);
|
|
45
|
+
renameSync(tmpPath, targetPath);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
try {
|
|
49
|
+
if (existsSync(tmpPath)) {
|
|
50
|
+
unlinkSync(tmpPath);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Ignore cleanup failure
|
|
55
|
+
}
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function getInstallManifestPath(home = homedir()) {
|
|
60
|
+
return join(home, ".bla", "install-manifest.json");
|
|
61
|
+
}
|
|
62
|
+
export function readInstallManifest(home = homedir()) {
|
|
63
|
+
const manifestPath = getInstallManifestPath(home);
|
|
64
|
+
if (!existsSync(manifestPath)) {
|
|
65
|
+
return {
|
|
66
|
+
version: 1,
|
|
67
|
+
updatedAt: new Date(0).toISOString(),
|
|
68
|
+
clients: {},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
73
|
+
if (parsed &&
|
|
74
|
+
parsed.version === 1 &&
|
|
75
|
+
typeof parsed.clients === "object" &&
|
|
76
|
+
parsed.clients !== null &&
|
|
77
|
+
!Array.isArray(parsed.clients)) {
|
|
78
|
+
return {
|
|
79
|
+
version: 1,
|
|
80
|
+
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : new Date().toISOString(),
|
|
81
|
+
clients: { ...parsed.clients },
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Corrupted manifest falls back to empty state
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
version: 1,
|
|
90
|
+
updatedAt: new Date(0).toISOString(),
|
|
91
|
+
clients: {},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function writeInstallManifest(home, manifest) {
|
|
95
|
+
const manifestPath = getInstallManifestPath(home);
|
|
96
|
+
const updated = {
|
|
97
|
+
version: 1,
|
|
98
|
+
updatedAt: new Date().toISOString(),
|
|
99
|
+
clients: manifest.clients,
|
|
100
|
+
};
|
|
101
|
+
writeFileAtomicSync(manifestPath, JSON.stringify(updated, null, 2) + "\n");
|
|
102
|
+
return manifestPath;
|
|
103
|
+
}
|
|
104
|
+
export function resolveBundledSkillContent() {
|
|
7
105
|
try {
|
|
8
106
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
9
107
|
const candidate = resolve(thisDir, "../../.agents/skills/blind-llm-agents/SKILL.md");
|
|
@@ -28,96 +126,527 @@ function resolveBundledSkillContent() {
|
|
|
28
126
|
"3. Verify defense effectiveness and calculate attacker Token & Cost Amplification.",
|
|
29
127
|
].join("\n");
|
|
30
128
|
}
|
|
129
|
+
export const generateBlaSkillMarkdown = resolveBundledSkillContent;
|
|
31
130
|
export function runSetup(options = {}) {
|
|
32
|
-
const
|
|
131
|
+
const home = options.homeDir ?? homedir();
|
|
132
|
+
const allClients = resolveSupportedAgentClients(home, options.platform, options.env);
|
|
133
|
+
const discoveredClients = allClients.filter((c) => c.detected).map((c) => c.id);
|
|
134
|
+
const requestedList = options.clientIds ?? options.clients;
|
|
135
|
+
const explicitClients = [];
|
|
136
|
+
if (requestedList && requestedList.length > 0) {
|
|
137
|
+
for (const rawId of requestedList) {
|
|
138
|
+
const norm = normalizeClientId(String(rawId));
|
|
139
|
+
if (norm && !explicitClients.includes(norm)) {
|
|
140
|
+
explicitClients.push(norm);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
33
144
|
let selected;
|
|
34
|
-
|
|
35
|
-
|
|
145
|
+
let selectionMode;
|
|
146
|
+
let selectionSource;
|
|
147
|
+
if (requestedList && requestedList.length > 0) {
|
|
148
|
+
const set = new Set(explicitClients);
|
|
36
149
|
selected = allClients.filter((c) => set.has(c.id));
|
|
150
|
+
selectionMode = "explicit";
|
|
151
|
+
selectionSource = "explicit";
|
|
37
152
|
}
|
|
38
|
-
else if (options.
|
|
39
|
-
selected = allClients
|
|
153
|
+
else if (options.includeUndetected) {
|
|
154
|
+
selected = [...allClients];
|
|
155
|
+
selectionMode = "all-default";
|
|
156
|
+
selectionSource = "default-all";
|
|
40
157
|
}
|
|
41
158
|
else {
|
|
42
159
|
selected = allClients.filter((c) => c.detected);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
160
|
+
selectionMode = "discovered";
|
|
161
|
+
selectionSource = "auto-discovered";
|
|
162
|
+
}
|
|
163
|
+
const mcpCommand = options.mcpCommand ?? "npx";
|
|
164
|
+
const mcpArgs = options.mcpArgs ? [...options.mcpArgs] : ["-y", `${BLA_IDENTITY.packageName}@latest`, "mcp"];
|
|
165
|
+
const isDryRunOrPlanOnly = options.dryRun === true || options.yes === false;
|
|
166
|
+
if (isDryRunOrPlanOnly) {
|
|
167
|
+
const plannedActions = selected.map((c) => ({
|
|
168
|
+
clientId: c.id,
|
|
169
|
+
displayName: c.displayName,
|
|
170
|
+
format: c.format,
|
|
171
|
+
configPath: c.configPath,
|
|
172
|
+
backupPath: existsSync(c.configPath) ? `${c.configPath}.bla-backup` : null,
|
|
173
|
+
skillPath: join(c.skillDirectory, "SKILL.md"),
|
|
174
|
+
mcpCommand,
|
|
175
|
+
mcpArgs,
|
|
176
|
+
clientDetected: c.detected,
|
|
177
|
+
selectionSource,
|
|
178
|
+
status: "planned",
|
|
179
|
+
applied: false,
|
|
180
|
+
}));
|
|
57
181
|
return {
|
|
58
182
|
status: "planned",
|
|
59
|
-
actions,
|
|
183
|
+
actions: plannedActions,
|
|
184
|
+
results: plannedActions,
|
|
185
|
+
discoveredClients,
|
|
186
|
+
explicitClients,
|
|
187
|
+
selectionMode,
|
|
188
|
+
manifestPath: null,
|
|
60
189
|
message: options.dryRun
|
|
61
|
-
? `Dry-run complete: ${
|
|
62
|
-
: `Planned setup for ${
|
|
190
|
+
? `Dry-run complete: ${plannedActions.length} agent client(s) planned. Run with --yes to apply.`
|
|
191
|
+
: `Planned setup for ${plannedActions.length} agent client(s). Pass --yes to write MCP registrations and skills.`,
|
|
63
192
|
};
|
|
64
193
|
}
|
|
65
194
|
const skillContent = resolveBundledSkillContent();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
195
|
+
const expectedSkillSha256 = sha256Hex(skillContent);
|
|
196
|
+
const manifest = readInstallManifest(home);
|
|
197
|
+
const updatedManifestClients = { ...manifest.clients };
|
|
198
|
+
const completedActions = [];
|
|
199
|
+
let anyApplied = false;
|
|
200
|
+
let anyError = false;
|
|
201
|
+
let anyConflict = false;
|
|
202
|
+
for (const client of selected) {
|
|
203
|
+
const adapter = getClientConfigAdapter(client.format);
|
|
204
|
+
const skillPath = join(client.skillDirectory, "SKILL.md");
|
|
205
|
+
const backupPathCandidate = `${client.configPath}.bla-backup`;
|
|
206
|
+
const configExistedBefore = existsSync(client.configPath);
|
|
207
|
+
const skillDirectoryExistedBefore = existsSync(client.skillDirectory);
|
|
208
|
+
const skillExistedBefore = existsSync(skillPath);
|
|
209
|
+
let existingRawContent = null;
|
|
210
|
+
if (configExistedBefore) {
|
|
211
|
+
try {
|
|
212
|
+
existingRawContent = readFileSync(client.configPath, "utf8");
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
anyError = true;
|
|
216
|
+
completedActions.push({
|
|
217
|
+
clientId: client.id,
|
|
218
|
+
displayName: client.displayName,
|
|
219
|
+
format: client.format,
|
|
220
|
+
configPath: client.configPath,
|
|
221
|
+
backupPath: null,
|
|
222
|
+
skillPath,
|
|
223
|
+
mcpCommand,
|
|
224
|
+
mcpArgs,
|
|
225
|
+
clientDetected: client.detected,
|
|
226
|
+
selectionSource,
|
|
227
|
+
status: "error",
|
|
228
|
+
applied: false,
|
|
229
|
+
error: `Permission or I/O error reading '${client.configPath}': ${err instanceof Error ? err.message : String(err)}`,
|
|
230
|
+
});
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const readRes = adapter.readServer(existingRawContent, BLA_IDENTITY.mcpServerName);
|
|
234
|
+
if (readRes.status === "malformed") {
|
|
235
|
+
anyError = true;
|
|
236
|
+
completedActions.push({
|
|
237
|
+
clientId: client.id,
|
|
238
|
+
displayName: client.displayName,
|
|
239
|
+
format: client.format,
|
|
240
|
+
configPath: client.configPath,
|
|
241
|
+
backupPath: null,
|
|
242
|
+
skillPath,
|
|
243
|
+
mcpCommand,
|
|
244
|
+
mcpArgs,
|
|
245
|
+
clientDetected: client.detected,
|
|
246
|
+
selectionSource,
|
|
247
|
+
status: "error",
|
|
248
|
+
applied: false,
|
|
249
|
+
error: readRes.error ?? `Malformed configuration file: '${client.configPath}'`,
|
|
250
|
+
});
|
|
251
|
+
continue;
|
|
72
252
|
}
|
|
253
|
+
if (readRes.status === "invalid_entry" && !options.force) {
|
|
254
|
+
const prevManifestRecord = updatedManifestClients[client.id];
|
|
255
|
+
if (!prevManifestRecord) {
|
|
256
|
+
anyError = true;
|
|
257
|
+
completedActions.push({
|
|
258
|
+
clientId: client.id,
|
|
259
|
+
displayName: client.displayName,
|
|
260
|
+
format: client.format,
|
|
261
|
+
configPath: client.configPath,
|
|
262
|
+
backupPath: null,
|
|
263
|
+
skillPath,
|
|
264
|
+
mcpCommand,
|
|
265
|
+
mcpArgs,
|
|
266
|
+
clientDetected: client.detected,
|
|
267
|
+
selectionSource,
|
|
268
|
+
status: "error",
|
|
269
|
+
applied: false,
|
|
270
|
+
error: readRes.error ??
|
|
271
|
+
`Invalid existing '${BLA_IDENTITY.mcpServerName}' MCP entry in '${client.configPath}'`,
|
|
272
|
+
});
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (readRes.status === "valid" && readRes.entry && !options.force) {
|
|
277
|
+
const prevManifestRecord = updatedManifestClients[client.id];
|
|
278
|
+
const entryHash = hashMcpEntry(readRes.entry);
|
|
279
|
+
const ownedByManifest = prevManifestRecord?.writtenEntryHash === entryHash;
|
|
280
|
+
const ownedBySignature = isBlaOwnedMcpEntry(readRes.entry);
|
|
281
|
+
const matchesRequested = readRes.entry.command === mcpCommand &&
|
|
282
|
+
JSON.stringify(readRes.entry.args) === JSON.stringify(mcpArgs);
|
|
283
|
+
if (!ownedByManifest && !ownedBySignature && !matchesRequested) {
|
|
284
|
+
anyConflict = true;
|
|
285
|
+
completedActions.push({
|
|
286
|
+
clientId: client.id,
|
|
287
|
+
displayName: client.displayName,
|
|
288
|
+
format: client.format,
|
|
289
|
+
configPath: client.configPath,
|
|
290
|
+
backupPath: null,
|
|
291
|
+
skillPath,
|
|
292
|
+
mcpCommand,
|
|
293
|
+
mcpArgs,
|
|
294
|
+
clientDetected: client.detected,
|
|
295
|
+
selectionSource,
|
|
296
|
+
status: "conflict",
|
|
297
|
+
applied: false,
|
|
298
|
+
error: `Existing non-BLA MCP server '${BLA_IDENTITY.mcpServerName}' in '${client.configPath}'; refusing to overwrite without force`,
|
|
299
|
+
});
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// Check skillPath ownership BEFORE writing config or skill
|
|
305
|
+
if (skillExistedBefore && !options.force) {
|
|
306
|
+
let existingSkillContent;
|
|
73
307
|
try {
|
|
74
|
-
|
|
308
|
+
existingSkillContent = readFileSync(skillPath);
|
|
75
309
|
}
|
|
76
|
-
catch {
|
|
77
|
-
|
|
310
|
+
catch (err) {
|
|
311
|
+
anyError = true;
|
|
312
|
+
completedActions.push({
|
|
313
|
+
clientId: client.id,
|
|
314
|
+
displayName: client.displayName,
|
|
315
|
+
format: client.format,
|
|
316
|
+
configPath: client.configPath,
|
|
317
|
+
backupPath: null,
|
|
318
|
+
skillPath,
|
|
319
|
+
mcpCommand,
|
|
320
|
+
mcpArgs,
|
|
321
|
+
clientDetected: client.detected,
|
|
322
|
+
selectionSource,
|
|
323
|
+
status: "error",
|
|
324
|
+
applied: false,
|
|
325
|
+
error: `Failed to read existing skill file '${skillPath}': ${err instanceof Error ? err.message : String(err)}`,
|
|
326
|
+
});
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const currentSkillHash = sha256Hex(existingSkillContent);
|
|
330
|
+
const ownedByAnyManifestClient = Object.values(updatedManifestClients).some((rec) => rec.skillPath === skillPath && rec.writtenSkillSha256 === currentSkillHash);
|
|
331
|
+
if (currentSkillHash !== expectedSkillSha256 && !ownedByAnyManifestClient) {
|
|
332
|
+
anyConflict = true;
|
|
333
|
+
completedActions.push({
|
|
334
|
+
clientId: client.id,
|
|
335
|
+
displayName: client.displayName,
|
|
336
|
+
format: client.format,
|
|
337
|
+
configPath: client.configPath,
|
|
338
|
+
backupPath: null,
|
|
339
|
+
skillPath,
|
|
340
|
+
mcpCommand,
|
|
341
|
+
mcpArgs,
|
|
342
|
+
clientDetected: client.detected,
|
|
343
|
+
selectionSource,
|
|
344
|
+
status: "conflict",
|
|
345
|
+
applied: false,
|
|
346
|
+
error: `Existing custom or user-modified SKILL.md at '${skillPath}'; refusing to overwrite without force`,
|
|
347
|
+
});
|
|
348
|
+
continue;
|
|
78
349
|
}
|
|
79
350
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
:
|
|
83
|
-
|
|
84
|
-
command: action.mcpCommand,
|
|
85
|
-
args: [...action.mcpArgs],
|
|
351
|
+
// Prepare new config content via client adapter before touching disk
|
|
352
|
+
const registration = {
|
|
353
|
+
command: mcpCommand,
|
|
354
|
+
args: mcpArgs,
|
|
86
355
|
};
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
356
|
+
let nextConfigContent;
|
|
357
|
+
try {
|
|
358
|
+
nextConfigContent = adapter.upsertServer(existingRawContent, BLA_IDENTITY.mcpServerName, registration);
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
anyError = true;
|
|
362
|
+
completedActions.push({
|
|
363
|
+
clientId: client.id,
|
|
364
|
+
displayName: client.displayName,
|
|
365
|
+
format: client.format,
|
|
366
|
+
configPath: client.configPath,
|
|
367
|
+
backupPath: null,
|
|
368
|
+
skillPath,
|
|
369
|
+
mcpCommand,
|
|
370
|
+
mcpArgs,
|
|
371
|
+
clientDetected: client.detected,
|
|
372
|
+
selectionSource,
|
|
373
|
+
status: "error",
|
|
374
|
+
applied: false,
|
|
375
|
+
error: err instanceof Error ? err.message : String(err),
|
|
376
|
+
});
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
// Non-clobbering backup: ONLY create `${configPath}.bla-backup` if it does not already exist!
|
|
380
|
+
let recordedBackupPath = null;
|
|
381
|
+
let recordedBackupSha256 = null;
|
|
382
|
+
try {
|
|
383
|
+
if (configExistedBefore && existingRawContent !== null) {
|
|
384
|
+
recordedBackupPath = backupPathCandidate;
|
|
385
|
+
if (!existsSync(backupPathCandidate)) {
|
|
386
|
+
writeFileAtomicSync(backupPathCandidate, existingRawContent);
|
|
387
|
+
recordedBackupSha256 = sha256Hex(existingRawContent);
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
recordedBackupSha256 = sha256Hex(readFileSync(backupPathCandidate));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
writeFileAtomicSync(client.configPath, nextConfigContent);
|
|
394
|
+
writeFileAtomicSync(skillPath, skillContent);
|
|
395
|
+
}
|
|
396
|
+
catch (err) {
|
|
397
|
+
anyError = true;
|
|
398
|
+
completedActions.push({
|
|
399
|
+
clientId: client.id,
|
|
400
|
+
displayName: client.displayName,
|
|
401
|
+
format: client.format,
|
|
402
|
+
configPath: client.configPath,
|
|
403
|
+
backupPath: recordedBackupPath,
|
|
404
|
+
skillPath,
|
|
405
|
+
mcpCommand,
|
|
406
|
+
mcpArgs,
|
|
407
|
+
clientDetected: client.detected,
|
|
408
|
+
selectionSource,
|
|
409
|
+
status: "error",
|
|
410
|
+
applied: false,
|
|
411
|
+
error: `Failed to write configuration or skill for '${client.id}': ${err instanceof Error ? err.message : String(err)}`,
|
|
412
|
+
});
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
const prevRecord = updatedManifestClients[client.id];
|
|
416
|
+
updatedManifestClients[client.id] = {
|
|
417
|
+
clientId: client.id,
|
|
418
|
+
configPath: client.configPath,
|
|
419
|
+
format: client.format,
|
|
420
|
+
configExistedBefore: prevRecord ? prevRecord.configExistedBefore : configExistedBefore,
|
|
421
|
+
backupPath: recordedBackupPath ?? prevRecord?.backupPath ?? null,
|
|
422
|
+
backupSha256: recordedBackupSha256 ?? prevRecord?.backupSha256 ?? null,
|
|
423
|
+
writtenConfigSha256: sha256Hex(nextConfigContent),
|
|
424
|
+
writtenEntry: registration,
|
|
425
|
+
writtenEntryHash: hashMcpEntry(registration),
|
|
426
|
+
skillPath,
|
|
427
|
+
skillDirectory: client.skillDirectory,
|
|
428
|
+
skillExistedBefore: prevRecord ? prevRecord.skillExistedBefore : skillExistedBefore,
|
|
429
|
+
skillDirectoryExistedBefore: prevRecord
|
|
430
|
+
? prevRecord.skillDirectoryExistedBefore
|
|
431
|
+
: skillDirectoryExistedBefore,
|
|
432
|
+
writtenSkillSha256: expectedSkillSha256,
|
|
433
|
+
installedAt: new Date().toISOString(),
|
|
434
|
+
};
|
|
435
|
+
anyApplied = true;
|
|
436
|
+
completedActions.push({
|
|
437
|
+
clientId: client.id,
|
|
438
|
+
displayName: client.displayName,
|
|
439
|
+
format: client.format,
|
|
440
|
+
configPath: client.configPath,
|
|
441
|
+
backupPath: recordedBackupPath,
|
|
442
|
+
skillPath,
|
|
443
|
+
mcpCommand,
|
|
444
|
+
mcpArgs,
|
|
445
|
+
clientDetected: client.detected,
|
|
446
|
+
selectionSource,
|
|
447
|
+
status: "applied",
|
|
448
|
+
applied: true,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
let manifestPath = null;
|
|
452
|
+
if (anyApplied) {
|
|
453
|
+
manifestPath = writeInstallManifest(home, {
|
|
454
|
+
version: 1,
|
|
455
|
+
updatedAt: new Date().toISOString(),
|
|
456
|
+
clients: updatedManifestClients,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
let overallStatus = "applied";
|
|
460
|
+
if (anyError || anyConflict) {
|
|
461
|
+
if (anyApplied) {
|
|
462
|
+
overallStatus = "partial";
|
|
463
|
+
}
|
|
464
|
+
else if (anyConflict && !anyError) {
|
|
465
|
+
overallStatus = "conflict";
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
overallStatus = "error";
|
|
469
|
+
}
|
|
91
470
|
}
|
|
471
|
+
const appliedCount = completedActions.filter((a) => a.applied).length;
|
|
92
472
|
return {
|
|
93
|
-
status:
|
|
94
|
-
actions,
|
|
95
|
-
|
|
473
|
+
status: overallStatus,
|
|
474
|
+
actions: completedActions,
|
|
475
|
+
results: completedActions,
|
|
476
|
+
discoveredClients,
|
|
477
|
+
explicitClients,
|
|
478
|
+
selectionMode,
|
|
479
|
+
manifestPath,
|
|
480
|
+
message: overallStatus === "applied"
|
|
481
|
+
? `Configured BLA MCP server and skill for ${appliedCount} agent client(s). Restart your agent to load BLA tools, then run bla_harden . (or: Harden my software against REA).`
|
|
482
|
+
: `Completed setup with status '${overallStatus}' (${appliedCount}/${completedActions.length} applied).`,
|
|
96
483
|
};
|
|
97
484
|
}
|
|
98
485
|
export function runUninstall(options = {}) {
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
|
|
486
|
+
const home = options.homeDir ?? homedir();
|
|
487
|
+
const allClients = resolveSupportedAgentClients(home, options.platform, options.env);
|
|
488
|
+
const requestedList = options.clientIds ?? options.clients;
|
|
489
|
+
let targetClients = allClients;
|
|
490
|
+
if (requestedList && requestedList.length > 0) {
|
|
491
|
+
const normalizedSet = new Set();
|
|
492
|
+
for (const rawId of requestedList) {
|
|
493
|
+
const norm = normalizeClientId(String(rawId));
|
|
494
|
+
if (norm)
|
|
495
|
+
normalizedSet.add(norm);
|
|
496
|
+
}
|
|
497
|
+
targetClients = allClients.filter((c) => normalizedSet.has(c.id));
|
|
498
|
+
}
|
|
499
|
+
const manifest = readInstallManifest(home);
|
|
500
|
+
const remainingManifestClients = { ...manifest.clients };
|
|
501
|
+
const bundledSkillHash = sha256Hex(resolveBundledSkillContent());
|
|
502
|
+
const removedClients = [];
|
|
503
|
+
const results = [];
|
|
504
|
+
const conflicts = [];
|
|
505
|
+
for (const client of targetClients) {
|
|
506
|
+
const adapter = getClientConfigAdapter(client.format);
|
|
507
|
+
const skillPath = join(client.skillDirectory, "SKILL.md");
|
|
508
|
+
const manifestRecord = remainingManifestClients[client.id];
|
|
509
|
+
let mcpRemoved = false;
|
|
510
|
+
let skillRemoved = false;
|
|
511
|
+
let conflictReason;
|
|
512
|
+
let errorMsg;
|
|
513
|
+
// 1. Check and remove MCP registration if owned by BLA
|
|
102
514
|
if (existsSync(client.configPath)) {
|
|
515
|
+
let rawConfig = null;
|
|
516
|
+
try {
|
|
517
|
+
rawConfig = readFileSync(client.configPath, "utf8");
|
|
518
|
+
}
|
|
519
|
+
catch (err) {
|
|
520
|
+
errorMsg = `Failed to read '${client.configPath}': ${err instanceof Error ? err.message : String(err)}`;
|
|
521
|
+
}
|
|
522
|
+
if (rawConfig !== null) {
|
|
523
|
+
const readRes = adapter.readServer(rawConfig, BLA_IDENTITY.mcpServerName);
|
|
524
|
+
if (readRes.status === "malformed") {
|
|
525
|
+
conflictReason =
|
|
526
|
+
readRes.error ?? `Malformed configuration at '${client.configPath}'; refusing to modify`;
|
|
527
|
+
}
|
|
528
|
+
else if (readRes.status === "invalid_entry") {
|
|
529
|
+
if (manifestRecord || options.force) {
|
|
530
|
+
try {
|
|
531
|
+
const rem = adapter.removeServer(rawConfig, BLA_IDENTITY.mcpServerName);
|
|
532
|
+
if (rem.removed) {
|
|
533
|
+
writeFileAtomicSync(client.configPath, rem.content);
|
|
534
|
+
mcpRemoved = true;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
catch (err) {
|
|
538
|
+
errorMsg = err instanceof Error ? err.message : String(err);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
conflictReason =
|
|
543
|
+
readRes.error ??
|
|
544
|
+
`Invalid non-BLA entry '${BLA_IDENTITY.mcpServerName}' in '${client.configPath}'; refusing to remove`;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
else if (readRes.status === "valid" && readRes.entry) {
|
|
548
|
+
const entryHash = hashMcpEntry(readRes.entry);
|
|
549
|
+
const ownedByManifest = manifestRecord?.writtenEntryHash === entryHash;
|
|
550
|
+
const ownedBySignature = isBlaOwnedMcpEntry(readRes.entry);
|
|
551
|
+
if (!ownedByManifest && !ownedBySignature && !options.force) {
|
|
552
|
+
conflictReason = `MCP entry '${BLA_IDENTITY.mcpServerName}' in '${client.configPath}' was not installed by BLA; preserving custom entry`;
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
try {
|
|
556
|
+
const rem = adapter.removeServer(rawConfig, BLA_IDENTITY.mcpServerName);
|
|
557
|
+
if (rem.removed) {
|
|
558
|
+
writeFileAtomicSync(client.configPath, rem.content);
|
|
559
|
+
mcpRemoved = true;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
catch (err) {
|
|
563
|
+
errorMsg = err instanceof Error ? err.message : String(err);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
// If MCP removal succeeded (or config had no conflicting BLA entry), check skill removal
|
|
570
|
+
const canRemoveManifestRecord = !conflictReason && !errorMsg;
|
|
571
|
+
if (canRemoveManifestRecord && manifestRecord) {
|
|
572
|
+
delete remainingManifestClients[client.id];
|
|
573
|
+
}
|
|
574
|
+
// Check if another still-installed client in manifest (or another client not targeted in this uninstall) shares `skillPath`
|
|
575
|
+
const targetIdsSet = new Set(targetClients.map((c) => c.id));
|
|
576
|
+
const sharedByOtherRemainingManifestClient = Object.values(remainingManifestClients).some((rec) => rec.clientId !== client.id && rec.skillPath === skillPath && !targetIdsSet.has(rec.clientId));
|
|
577
|
+
if (!sharedByOtherRemainingManifestClient && existsSync(skillPath)) {
|
|
103
578
|
try {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
579
|
+
const currentSkillSha = sha256Hex(readFileSync(skillPath));
|
|
580
|
+
const ownedByThisManifest = manifestRecord !== undefined && manifestRecord.writtenSkillSha256 === currentSkillSha;
|
|
581
|
+
const ownedByAnyManifest = Object.values(manifest.clients).some((rec) => rec.skillPath === skillPath && rec.writtenSkillSha256 === currentSkillSha);
|
|
582
|
+
const ownedByFallbackUnmodified = manifestRecord === undefined && mcpRemoved && currentSkillSha === bundledSkillHash;
|
|
583
|
+
if (ownedByThisManifest || ownedByAnyManifest || ownedByFallbackUnmodified || options.force) {
|
|
584
|
+
unlinkSync(skillPath);
|
|
585
|
+
skillRemoved = true;
|
|
586
|
+
// Only remove `client.skillDirectory` if it is now completely empty!
|
|
587
|
+
if (existsSync(client.skillDirectory)) {
|
|
588
|
+
const remainingFiles = readdirSync(client.skillDirectory);
|
|
589
|
+
if (remainingFiles.length === 0) {
|
|
590
|
+
rmdirSync(client.skillDirectory);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
else if (manifestRecord !== undefined || mcpRemoved) {
|
|
595
|
+
conflictReason =
|
|
596
|
+
conflictReason ??
|
|
597
|
+
`Skill file '${skillPath}' was modified after installation; preserving user changes`;
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
conflictReason =
|
|
601
|
+
conflictReason ??
|
|
602
|
+
`Skill file '${skillPath}' is not owned by BLA; preserving custom skill`;
|
|
111
603
|
}
|
|
112
604
|
}
|
|
113
|
-
catch {
|
|
114
|
-
|
|
605
|
+
catch (err) {
|
|
606
|
+
errorMsg =
|
|
607
|
+
errorMsg ??
|
|
608
|
+
`Failed to inspect or remove skill '${skillPath}': ${err instanceof Error ? err.message : String(err)}`;
|
|
115
609
|
}
|
|
116
610
|
}
|
|
117
|
-
if (
|
|
118
|
-
|
|
611
|
+
if (mcpRemoved) {
|
|
612
|
+
removedClients.push(client.id);
|
|
119
613
|
}
|
|
614
|
+
const itemStatus = errorMsg
|
|
615
|
+
? "error"
|
|
616
|
+
: conflictReason
|
|
617
|
+
? "conflict"
|
|
618
|
+
: mcpRemoved || skillRemoved
|
|
619
|
+
? "removed"
|
|
620
|
+
: "noop";
|
|
621
|
+
const itemResult = {
|
|
622
|
+
clientId: client.id,
|
|
623
|
+
configPath: client.configPath,
|
|
624
|
+
skillPath,
|
|
625
|
+
mcpRemoved,
|
|
626
|
+
skillRemoved,
|
|
627
|
+
status: itemStatus,
|
|
628
|
+
...(conflictReason ? { conflictReason } : {}),
|
|
629
|
+
...(errorMsg ? { error: errorMsg } : {}),
|
|
630
|
+
};
|
|
631
|
+
results.push(itemResult);
|
|
632
|
+
if (itemStatus === "conflict" || itemStatus === "error") {
|
|
633
|
+
conflicts.push(itemResult);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
// Persist updated manifest if it existed or has remaining records
|
|
637
|
+
const manifestPath = getInstallManifestPath(home);
|
|
638
|
+
if (existsSync(manifestPath) || Object.keys(remainingManifestClients).length > 0) {
|
|
639
|
+
writeInstallManifest(home, {
|
|
640
|
+
version: 1,
|
|
641
|
+
updatedAt: new Date().toISOString(),
|
|
642
|
+
clients: remainingManifestClients,
|
|
643
|
+
});
|
|
120
644
|
}
|
|
121
|
-
return {
|
|
645
|
+
return {
|
|
646
|
+
removedClients,
|
|
647
|
+
results,
|
|
648
|
+
conflicts,
|
|
649
|
+
};
|
|
122
650
|
}
|
|
651
|
+
export const uninstallSetup = runUninstall;
|
|
123
652
|
//# sourceMappingURL=setup.js.map
|