bla-agents 1.1.0 → 1.5.3

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 (276) hide show
  1. package/.agents/skills/blind-llm-agents/SKILL.md +20 -14
  2. package/DISCLAIMER.md +15 -7
  3. package/README.md +153 -198
  4. package/dist/bundler/index.d.ts +19 -2
  5. package/dist/bundler/index.d.ts.map +1 -1
  6. package/dist/bundler/index.js +47 -4
  7. package/dist/bundler/index.js.map +1 -1
  8. package/dist/cli.d.ts +5 -0
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +123 -18
  11. package/dist/cli.js.map +1 -1
  12. package/dist/core/config.d.ts +54 -0
  13. package/dist/core/config.d.ts.map +1 -0
  14. package/dist/core/config.js +160 -0
  15. package/dist/core/config.js.map +1 -0
  16. package/dist/core/coverage-contract.d.ts +97 -0
  17. package/dist/core/coverage-contract.d.ts.map +1 -0
  18. package/dist/core/coverage-contract.js +164 -0
  19. package/dist/core/coverage-contract.js.map +1 -0
  20. package/dist/core/debug-posture.d.ts +40 -0
  21. package/dist/core/debug-posture.d.ts.map +1 -0
  22. package/dist/core/debug-posture.js +197 -0
  23. package/dist/core/debug-posture.js.map +1 -0
  24. package/dist/core/detector.d.ts.map +1 -1
  25. package/dist/core/detector.js +58 -6
  26. package/dist/core/detector.js.map +1 -1
  27. package/dist/core/identifiers.d.ts +32 -0
  28. package/dist/core/identifiers.d.ts.map +1 -0
  29. package/dist/core/identifiers.js +263 -0
  30. package/dist/core/identifiers.js.map +1 -0
  31. package/dist/core/llm-directives.d.ts +12 -0
  32. package/dist/core/llm-directives.d.ts.map +1 -0
  33. package/dist/core/llm-directives.js +23 -0
  34. package/dist/core/llm-directives.js.map +1 -0
  35. package/dist/core/prng.d.ts +10 -1
  36. package/dist/core/prng.d.ts.map +1 -1
  37. package/dist/core/prng.js +27 -2
  38. package/dist/core/prng.js.map +1 -1
  39. package/dist/core/release-plan.d.ts +68 -0
  40. package/dist/core/release-plan.d.ts.map +1 -0
  41. package/dist/core/release-plan.js +235 -0
  42. package/dist/core/release-plan.js.map +1 -0
  43. package/dist/core/sarif.d.ts +53 -0
  44. package/dist/core/sarif.d.ts.map +1 -0
  45. package/dist/core/sarif.js +91 -0
  46. package/dist/core/sarif.js.map +1 -0
  47. package/dist/core/sqlite-runtime.d.ts +13 -0
  48. package/dist/core/sqlite-runtime.d.ts.map +1 -1
  49. package/dist/core/sqlite-runtime.js +38 -8
  50. package/dist/core/sqlite-runtime.js.map +1 -1
  51. package/dist/core/types.d.ts +7 -2
  52. package/dist/core/types.d.ts.map +1 -1
  53. package/dist/engine/auditor.d.ts.map +1 -1
  54. package/dist/engine/auditor.js +251 -8
  55. package/dist/engine/auditor.js.map +1 -1
  56. package/dist/engine/benchmark.d.ts +1 -1
  57. package/dist/engine/benchmark.d.ts.map +1 -1
  58. package/dist/engine/benchmark.js +121 -41
  59. package/dist/engine/benchmark.js.map +1 -1
  60. package/dist/engine/defender.d.ts +1 -0
  61. package/dist/engine/defender.d.ts.map +1 -1
  62. package/dist/engine/defender.js +7 -1
  63. package/dist/engine/defender.js.map +1 -1
  64. package/dist/engine/harden.d.ts +61 -0
  65. package/dist/engine/harden.d.ts.map +1 -0
  66. package/dist/engine/harden.js +292 -0
  67. package/dist/engine/harden.js.map +1 -0
  68. package/dist/engine/protector.d.ts.map +1 -1
  69. package/dist/engine/protector.js +162 -21
  70. package/dist/engine/protector.js.map +1 -1
  71. package/dist/identity.d.ts +112 -2
  72. package/dist/identity.d.ts.map +1 -1
  73. package/dist/identity.js +112 -2
  74. package/dist/identity.js.map +1 -1
  75. package/dist/index.d.ts +26 -9
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +26 -9
  78. package/dist/index.js.map +1 -1
  79. package/dist/mcp/server.d.ts +59 -152
  80. package/dist/mcp/server.d.ts.map +1 -1
  81. package/dist/mcp/server.js +466 -50
  82. package/dist/mcp/server.js.map +1 -1
  83. package/dist/setup/clients.d.ts +49 -1
  84. package/dist/setup/clients.d.ts.map +1 -1
  85. package/dist/setup/clients.js +1193 -20
  86. package/dist/setup/clients.js.map +1 -1
  87. package/dist/setup/doctor.d.ts +58 -14
  88. package/dist/setup/doctor.d.ts.map +1 -1
  89. package/dist/setup/doctor.js +316 -22
  90. package/dist/setup/doctor.js.map +1 -1
  91. package/dist/setup/setup.d.ts +77 -7
  92. package/dist/setup/setup.d.ts.map +1 -1
  93. package/dist/setup/setup.js +591 -62
  94. package/dist/setup/setup.js.map +1 -1
  95. package/dist/shields/chaff/chaff-generator.d.ts.map +1 -1
  96. package/dist/shields/chaff/chaff-generator.js +12 -3
  97. package/dist/shields/chaff/chaff-generator.js.map +1 -1
  98. package/dist/shields/deception/architecture-mirage.js +1 -1
  99. package/dist/shields/deception/architecture-mirage.js.map +1 -1
  100. package/dist/shields/deception/canary-honeypot.d.ts +33 -0
  101. package/dist/shields/deception/canary-honeypot.d.ts.map +1 -0
  102. package/dist/shields/deception/canary-honeypot.js +95 -0
  103. package/dist/shields/deception/canary-honeypot.js.map +1 -0
  104. package/dist/shields/deception/cognitive-armor-trigger.d.ts +43 -0
  105. package/dist/shields/deception/cognitive-armor-trigger.d.ts.map +1 -0
  106. package/dist/shields/deception/cognitive-armor-trigger.js +204 -0
  107. package/dist/shields/deception/cognitive-armor-trigger.js.map +1 -0
  108. package/dist/shields/deception/crash-trace-decoy.js +1 -1
  109. package/dist/shields/deception/crash-trace-decoy.js.map +1 -1
  110. package/dist/shields/deception/decompiler-illusion.js +1 -1
  111. package/dist/shields/deception/decompiler-illusion.js.map +1 -1
  112. package/dist/shields/deception/diff-noise-injector.js +1 -1
  113. package/dist/shields/deception/diff-noise-injector.js.map +1 -1
  114. package/dist/shields/deception/evidence-contract-poison.js +1 -1
  115. package/dist/shields/deception/evidence-contract-poison.js.map +1 -1
  116. package/dist/shields/deception/format-payloads.d.ts +21 -0
  117. package/dist/shields/deception/format-payloads.d.ts.map +1 -0
  118. package/dist/shields/deception/format-payloads.js +24 -0
  119. package/dist/shields/deception/format-payloads.js.map +1 -0
  120. package/dist/shields/deception/index.d.ts +10 -2
  121. package/dist/shields/deception/index.d.ts.map +1 -1
  122. package/dist/shields/deception/index.js +20 -3
  123. package/dist/shields/deception/index.js.map +1 -1
  124. package/dist/shields/deception/prompt-completion-poison.d.ts +3 -2
  125. package/dist/shields/deception/prompt-completion-poison.d.ts.map +1 -1
  126. package/dist/shields/deception/prompt-completion-poison.js +6 -3
  127. package/dist/shields/deception/prompt-completion-poison.js.map +1 -1
  128. package/dist/shields/deception/shadow-business-logic.js +4 -4
  129. package/dist/shields/deception/shadow-business-logic.js.map +1 -1
  130. package/dist/shields/deception/system-diagnostic-decoy.d.ts +36 -0
  131. package/dist/shields/deception/system-diagnostic-decoy.d.ts.map +1 -0
  132. package/dist/shields/deception/system-diagnostic-decoy.js +101 -0
  133. package/dist/shields/deception/system-diagnostic-decoy.js.map +1 -0
  134. package/dist/shields/exhaustion/asar-header-amplifier.d.ts +12 -3
  135. package/dist/shields/exhaustion/asar-header-amplifier.d.ts.map +1 -1
  136. package/dist/shields/exhaustion/asar-header-amplifier.js +30 -8
  137. package/dist/shields/exhaustion/asar-header-amplifier.js.map +1 -1
  138. package/dist/shields/exhaustion/bpe-token-amplifier.js +2 -2
  139. package/dist/shields/exhaustion/bpe-token-amplifier.js.map +1 -1
  140. package/dist/shields/exhaustion/callgraph-labyrinth.js +2 -2
  141. package/dist/shields/exhaustion/callgraph-labyrinth.js.map +1 -1
  142. package/dist/shields/exhaustion/cdp-shape-budget-breaker.js +1 -1
  143. package/dist/shields/exhaustion/cdp-shape-budget-breaker.js.map +1 -1
  144. package/dist/shields/exhaustion/frida-capture-saturation.d.ts +10 -5
  145. package/dist/shields/exhaustion/frida-capture-saturation.d.ts.map +1 -1
  146. package/dist/shields/exhaustion/frida-capture-saturation.js +21 -8
  147. package/dist/shields/exhaustion/frida-capture-saturation.js.map +1 -1
  148. package/dist/shields/exhaustion/index.d.ts +2 -2
  149. package/dist/shields/exhaustion/index.d.ts.map +1 -1
  150. package/dist/shields/exhaustion/index.js +2 -2
  151. package/dist/shields/exhaustion/index.js.map +1 -1
  152. package/dist/shields/exhaustion/obligation-flooder.d.ts +17 -5
  153. package/dist/shields/exhaustion/obligation-flooder.d.ts.map +1 -1
  154. package/dist/shields/exhaustion/obligation-flooder.js +20 -8
  155. package/dist/shields/exhaustion/obligation-flooder.js.map +1 -1
  156. package/dist/shields/exhaustion/rea-budget-matrix.d.ts +8 -2
  157. package/dist/shields/exhaustion/rea-budget-matrix.d.ts.map +1 -1
  158. package/dist/shields/exhaustion/rea-budget-matrix.js +139 -16
  159. package/dist/shields/exhaustion/rea-budget-matrix.js.map +1 -1
  160. package/dist/shields/exhaustion/regex-worker-tarpit.d.ts.map +1 -1
  161. package/dist/shields/exhaustion/regex-worker-tarpit.js +2 -1
  162. package/dist/shields/exhaustion/regex-worker-tarpit.js.map +1 -1
  163. package/dist/shields/exhaustion/wasm-interface-exhaustion.d.ts +7 -4
  164. package/dist/shields/exhaustion/wasm-interface-exhaustion.d.ts.map +1 -1
  165. package/dist/shields/exhaustion/wasm-interface-exhaustion.js +16 -7
  166. package/dist/shields/exhaustion/wasm-interface-exhaustion.js.map +1 -1
  167. package/dist/shields/formats/android-dex-xref.d.ts +19 -0
  168. package/dist/shields/formats/android-dex-xref.d.ts.map +1 -0
  169. package/dist/shields/formats/android-dex-xref.js +427 -0
  170. package/dist/shields/formats/android-dex-xref.js.map +1 -0
  171. package/dist/shields/formats/android-manifest.d.ts +44 -0
  172. package/dist/shields/formats/android-manifest.d.ts.map +1 -0
  173. package/dist/shields/formats/android-manifest.js +240 -0
  174. package/dist/shields/formats/android-manifest.js.map +1 -0
  175. package/dist/shields/formats/android-r8.d.ts +44 -0
  176. package/dist/shields/formats/android-r8.d.ts.map +1 -0
  177. package/dist/shields/formats/android-r8.js +246 -0
  178. package/dist/shields/formats/android-r8.js.map +1 -0
  179. package/dist/shields/formats/apple-plist.d.ts +15 -0
  180. package/dist/shields/formats/apple-plist.d.ts.map +1 -1
  181. package/dist/shields/formats/apple-plist.js +59 -1
  182. package/dist/shields/formats/apple-plist.js.map +1 -1
  183. package/dist/shields/formats/archive.d.ts +27 -6
  184. package/dist/shields/formats/archive.d.ts.map +1 -1
  185. package/dist/shields/formats/archive.js +247 -45
  186. package/dist/shields/formats/archive.js.map +1 -1
  187. package/dist/shields/formats/asar.d.ts +50 -10
  188. package/dist/shields/formats/asar.d.ts.map +1 -1
  189. package/dist/shields/formats/asar.js +265 -39
  190. package/dist/shields/formats/asar.js.map +1 -1
  191. package/dist/shields/formats/bun-standalone.d.ts +78 -0
  192. package/dist/shields/formats/bun-standalone.d.ts.map +1 -0
  193. package/dist/shields/formats/bun-standalone.js +272 -0
  194. package/dist/shields/formats/bun-standalone.js.map +1 -0
  195. package/dist/shields/formats/dotnet-managed.d.ts +9 -0
  196. package/dist/shields/formats/dotnet-managed.d.ts.map +1 -1
  197. package/dist/shields/formats/dotnet-managed.js +26 -1
  198. package/dist/shields/formats/dotnet-managed.js.map +1 -1
  199. package/dist/shields/formats/electron-fuses.d.ts +63 -8
  200. package/dist/shields/formats/electron-fuses.d.ts.map +1 -1
  201. package/dist/shields/formats/electron-fuses.js +297 -65
  202. package/dist/shields/formats/electron-fuses.js.map +1 -1
  203. package/dist/shields/formats/erlang-beam.d.ts +34 -0
  204. package/dist/shields/formats/erlang-beam.d.ts.map +1 -0
  205. package/dist/shields/formats/erlang-beam.js +319 -0
  206. package/dist/shields/formats/erlang-beam.js.map +1 -0
  207. package/dist/shields/formats/evm-bytecode.d.ts +28 -0
  208. package/dist/shields/formats/evm-bytecode.d.ts.map +1 -0
  209. package/dist/shields/formats/evm-bytecode.js +124 -0
  210. package/dist/shields/formats/evm-bytecode.js.map +1 -0
  211. package/dist/shields/formats/go-flutter-exhaustion.d.ts +55 -0
  212. package/dist/shields/formats/go-flutter-exhaustion.d.ts.map +1 -0
  213. package/dist/shields/formats/go-flutter-exhaustion.js +346 -0
  214. package/dist/shields/formats/go-flutter-exhaustion.js.map +1 -0
  215. package/dist/shields/formats/gpu-ptx.d.ts +40 -0
  216. package/dist/shields/formats/gpu-ptx.d.ts.map +1 -0
  217. package/dist/shields/formats/gpu-ptx.js +62 -0
  218. package/dist/shields/formats/gpu-ptx.js.map +1 -0
  219. package/dist/shields/formats/headers.d.ts.map +1 -1
  220. package/dist/shields/formats/headers.js +54 -14
  221. package/dist/shields/formats/headers.js.map +1 -1
  222. package/dist/shields/formats/jvm-bytecode.d.ts +20 -0
  223. package/dist/shields/formats/jvm-bytecode.d.ts.map +1 -0
  224. package/dist/shields/formats/jvm-bytecode.js +134 -0
  225. package/dist/shields/formats/jvm-bytecode.js.map +1 -0
  226. package/dist/shields/formats/mobile-game.d.ts +4 -0
  227. package/dist/shields/formats/mobile-game.d.ts.map +1 -1
  228. package/dist/shields/formats/mobile-game.js +16 -0
  229. package/dist/shields/formats/mobile-game.js.map +1 -1
  230. package/dist/shields/formats/model.d.ts +22 -8
  231. package/dist/shields/formats/model.d.ts.map +1 -1
  232. package/dist/shields/formats/model.js +231 -59
  233. package/dist/shields/formats/model.js.map +1 -1
  234. package/dist/shields/formats/native.d.ts +65 -11
  235. package/dist/shields/formats/native.d.ts.map +1 -1
  236. package/dist/shields/formats/native.js +413 -108
  237. package/dist/shields/formats/native.js.map +1 -1
  238. package/dist/shields/formats/pcap-capture.d.ts +27 -0
  239. package/dist/shields/formats/pcap-capture.d.ts.map +1 -0
  240. package/dist/shields/formats/pcap-capture.js +201 -0
  241. package/dist/shields/formats/pcap-capture.js.map +1 -0
  242. package/dist/shields/formats/python.d.ts +22 -1
  243. package/dist/shields/formats/python.d.ts.map +1 -1
  244. package/dist/shields/formats/python.js +568 -27
  245. package/dist/shields/formats/python.js.map +1 -1
  246. package/dist/shields/formats/sourcemap.d.ts.map +1 -1
  247. package/dist/shields/formats/sourcemap.js +2 -0
  248. package/dist/shields/formats/sourcemap.js.map +1 -1
  249. package/dist/shields/formats/sqlite.d.ts +87 -15
  250. package/dist/shields/formats/sqlite.d.ts.map +1 -1
  251. package/dist/shields/formats/sqlite.js +377 -42
  252. package/dist/shields/formats/sqlite.js.map +1 -1
  253. package/dist/shields/formats/wasm.d.ts +6 -0
  254. package/dist/shields/formats/wasm.d.ts.map +1 -1
  255. package/dist/shields/formats/wasm.js +118 -3
  256. package/dist/shields/formats/wasm.js.map +1 -1
  257. package/dist/shields/javascript/ast-shield.d.ts +25 -5
  258. package/dist/shields/javascript/ast-shield.d.ts.map +1 -1
  259. package/dist/shields/javascript/ast-shield.js +428 -58
  260. package/dist/shields/javascript/ast-shield.js.map +1 -1
  261. package/dist/shields/runtime/memory-diff-shield.d.ts +56 -0
  262. package/dist/shields/runtime/memory-diff-shield.d.ts.map +1 -0
  263. package/dist/shields/runtime/memory-diff-shield.js +170 -0
  264. package/dist/shields/runtime/memory-diff-shield.js.map +1 -0
  265. package/dist/shields/runtime/runtime-shield.d.ts +12 -16
  266. package/dist/shields/runtime/runtime-shield.d.ts.map +1 -1
  267. package/dist/shields/runtime/runtime-shield.js +30 -24
  268. package/dist/shields/runtime/runtime-shield.js.map +1 -1
  269. package/dist/shields/tarpit/opaque-predicates.d.ts.map +1 -1
  270. package/dist/shields/tarpit/opaque-predicates.js +3 -3
  271. package/dist/shields/tarpit/opaque-predicates.js.map +1 -1
  272. package/dist/shields/tarpit/tarpit-generator.d.ts +9 -4
  273. package/dist/shields/tarpit/tarpit-generator.d.ts.map +1 -1
  274. package/dist/shields/tarpit/tarpit-generator.js +30 -23
  275. package/dist/shields/tarpit/tarpit-generator.js.map +1 -1
  276. package/package.json +13 -6
@@ -1,9 +1,107 @@
1
- import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
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 resolveBundledSkillContent() {
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 allClients = resolveSupportedAgentClients(options.homeDir);
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
- if (options.clientIds && options.clientIds.length > 0) {
35
- const set = new Set(options.clientIds);
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.allDetected) {
39
- selected = allClients.filter((c) => c.detected);
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
- if (selected.length === 0) {
44
- selected = allClients.filter((c) => c.id === "claude_code" || c.id === "cursor");
45
- }
46
- }
47
- const actions = selected.map((c) => ({
48
- clientId: c.id,
49
- displayName: c.displayName,
50
- configPath: c.configPath,
51
- backupPath: existsSync(c.configPath) ? `${c.configPath}.bla-backup` : null,
52
- skillPath: join(c.skillDirectory, "SKILL.md"),
53
- mcpCommand: "npx",
54
- mcpArgs: ["-y", `${BLA_IDENTITY.packageName}@latest`, "mcp"],
55
- }));
56
- if (options.dryRun || !options.yes) {
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: ${actions.length} agent client(s) planned. Run with --yes to apply.`
62
- : `Planned setup for ${actions.length} detected agent client(s). Pass --yes to write MCP registrations and skills.`,
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
- for (const action of actions) {
67
- mkdirSync(dirname(action.configPath), { recursive: true });
68
- let existingConfig = {};
69
- if (existsSync(action.configPath)) {
70
- if (action.backupPath) {
71
- copyFileSync(action.configPath, action.backupPath);
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
- existingConfig = JSON.parse(readFileSync(action.configPath, "utf8"));
308
+ existingSkillContent = readFileSync(skillPath);
75
309
  }
76
- catch {
77
- existingConfig = {};
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
- const mcpServers = typeof existingConfig.mcpServers === "object" && existingConfig.mcpServers !== null
81
- ? { ...existingConfig.mcpServers }
82
- : {};
83
- mcpServers[BLA_IDENTITY.mcpServerName] = {
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
- existingConfig.mcpServers = mcpServers;
88
- writeFileSync(action.configPath, JSON.stringify(existingConfig, null, 2) + "\n", "utf8");
89
- mkdirSync(dirname(action.skillPath), { recursive: true });
90
- writeFileSync(action.skillPath, skillContent, "utf8");
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: "applied",
94
- actions,
95
- message: `Configured BLA MCP server and skill for ${actions.length} agent client(s). Restart your agent to load BLA tools.`,
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 allClients = resolveSupportedAgentClients(options.homeDir);
100
- const removed = [];
101
- for (const client of allClients) {
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 parsed = JSON.parse(readFileSync(client.configPath, "utf8"));
105
- if (typeof parsed.mcpServers === "object" &&
106
- parsed.mcpServers !== null &&
107
- BLA_IDENTITY.mcpServerName in parsed.mcpServers) {
108
- delete parsed.mcpServers[BLA_IDENTITY.mcpServerName];
109
- writeFileSync(client.configPath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
110
- removed.push(client.id);
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
- // Ignore malformed
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 (existsSync(client.skillDirectory)) {
118
- rmSync(client.skillDirectory, { recursive: true, force: true });
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 { removedClients: removed };
645
+ return {
646
+ removedClients,
647
+ results,
648
+ conflicts,
649
+ };
122
650
  }
651
+ export const uninstallSetup = runUninstall;
123
652
  //# sourceMappingURL=setup.js.map