blun-king-cli 9.1.624 → 9.1.626

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 (142) hide show
  1. package/CHANGELOG.md +62 -213
  2. package/LIESMICH.txt +57 -74
  3. package/README.md +76 -119
  4. package/agent-spine-plugin/scripts/check-install.js +4 -4
  5. package/agent-spine-plugin/src/hook.js +0 -4
  6. package/agent-spine-plugin/src/lib/documents.js +1 -1
  7. package/agent-spine-plugin/src/lib/hook-context.js +0 -1
  8. package/agent-spine-plugin/src/lib/host-instruction-budget.js +2 -1
  9. package/agent-spine-plugin/src/lib/preflight.js +9 -38
  10. package/agent-spine-plugin/src/lib/source-roots.js +1 -1
  11. package/bin/agentspine-king-goal-inbox.mjs +127 -127
  12. package/bin/agentspine-king-goal-intake.mjs +106 -106
  13. package/bin/agentspine-king-host-runner.mjs +109 -109
  14. package/bin/agentspine-king-snapshot-policy.cjs +80 -80
  15. package/bin/agentspine-king-status-policy.mjs +226 -226
  16. package/bin/agentspine-king-worker-host.mjs +160 -160
  17. package/bin/core-bootstrap.js +83 -97
  18. package/bin/launcher-mode.js +93 -93
  19. package/bin/launcher-restart-policy.cjs +131 -131
  20. package/bin/launcher-runtime.js +91 -254
  21. package/bin/managed-node.js +186 -186
  22. package/bin/managed-plugin-selection.cjs +109 -116
  23. package/bin/native-module-repair.js +201 -201
  24. package/bin/node-runtime.js +34 -34
  25. package/bin/node-version.js +49 -49
  26. package/bin/plugin-bootstrap.js +149 -172
  27. package/bin/private-paths.js +1578 -1624
  28. package/bin/runtime-exit-ledger.cjs +144 -144
  29. package/bin/runtime-exit-ledger.d.cts +23 -23
  30. package/bin/standard-tools-bootstrap.js +277 -272
  31. package/bin/turn-thinking-policy.cjs +82 -82
  32. package/bin/update-copy.js +200 -200
  33. package/bin/update-lease.js +597 -599
  34. package/bin/update-notice.js +1568 -1568
  35. package/bin/verify-agent-behavior.cjs +122 -122
  36. package/bin/verify-agent-components.cjs +104 -104
  37. package/bin/verify-bundled-agent-sources.cjs +91 -96
  38. package/bin/windows-node-crash-dump.cjs +289 -289
  39. package/blun.mjs +187640 -180475
  40. package/bundled-agent-sources.json +26 -30
  41. package/codebase-index/codebase_index.py +462 -470
  42. package/dist-web/assets/{CodeBlockNode-TKKkJX8L.js → CodeBlockNode-BMbuG85x.js} +1 -1
  43. package/dist-web/assets/{CodeBlockShell.vue_vue_type_style_index_0_lang-CUj2Ds5x.js → CodeBlockShell.vue_vue_type_style_index_0_lang-C1fv7l_Z.js} +1 -1
  44. package/dist-web/assets/DesignSystemView-6HgAUEmC.css +1 -0
  45. package/dist-web/assets/DesignSystemView-CEEBqXSX.js +12 -0
  46. package/dist-web/assets/{Tooltip-CTWv7WCU.js → Tooltip--YoauJ24.js} +1 -1
  47. package/dist-web/assets/{abnfDiagram-N423BO3Z-DA8V8Zjh.js → abnfDiagram-N423BO3Z-DZvBWkRg.js} +1 -1
  48. package/dist-web/assets/{arc-YtddKbdY.js → arc-B04JATeX.js} +1 -1
  49. package/dist-web/assets/{architectureDiagram-T3A2C74G-CGEiObmo.js → architectureDiagram-T3A2C74G-DmYnMep2.js} +1 -1
  50. package/dist-web/assets/{blockDiagram-VBNYF7ZC-Bd5Vt50e.js → blockDiagram-VBNYF7ZC-DJ1gPS1C.js} +1 -1
  51. package/dist-web/assets/{c4Diagram-5PPSVZJV-D2ApPM54.js → c4Diagram-5PPSVZJV-DmjxvUbd.js} +1 -1
  52. package/dist-web/assets/channel-472fcEvw.js +1 -0
  53. package/dist-web/assets/{chunk-2GRJ4B5K-BDcIEZSl.js → chunk-2GRJ4B5K-AB0raj3I.js} +1 -1
  54. package/dist-web/assets/{chunk-2Q5K7J3B-6aSabCX7.js → chunk-2Q5K7J3B-Drf039vM.js} +1 -1
  55. package/dist-web/assets/{chunk-5RXB4S5H-BFRQvIJS.js → chunk-5RXB4S5H-D5ILttmz.js} +1 -1
  56. package/dist-web/assets/{chunk-5VM5RSS4-ChAa-DNY.js → chunk-5VM5RSS4-DrLSdi8y.js} +1 -1
  57. package/dist-web/assets/{chunk-6Q2QTUOP-BIfrb2Jc.js → chunk-6Q2QTUOP-LiaqdEOQ.js} +1 -1
  58. package/dist-web/assets/{chunk-GF5L2VYU-BR0gvtWI.js → chunk-GF5L2VYU-AIYkb--o.js} +1 -1
  59. package/dist-web/assets/{chunk-JWPE2WC7-BgXKsx5E.js → chunk-JWPE2WC7-CogdNl0i.js} +1 -1
  60. package/dist-web/assets/{chunk-KBJHAD2P-I8mrZhpz.js → chunk-KBJHAD2P-DHjpNtZv.js} +1 -1
  61. package/dist-web/assets/{chunk-RYQCIY6F-BMPaem92.js → chunk-RYQCIY6F-BUSOqH2X.js} +1 -1
  62. package/dist-web/assets/{chunk-XXDRQBXY-COy0J-0j.js → chunk-XXDRQBXY-BpoMV0bh.js} +1 -1
  63. package/dist-web/assets/classDiagram-JCYQIIEL-BTZeLngj.js +1 -0
  64. package/dist-web/assets/classDiagram-v2-OCEON4UE-BTZeLngj.js +1 -0
  65. package/dist-web/assets/{cose-bilkent-JH36ORCC-MosTTRdo.js → cose-bilkent-JH36ORCC-BJr2I61o.js} +1 -1
  66. package/dist-web/assets/{cynefin-VYW2F7L2-5tjPgrkP.js → cynefin-VYW2F7L2-HtysQZ4A.js} +1 -1
  67. package/dist-web/assets/{cynefinDiagram-MW4NZA55-CbDcdInR.js → cynefinDiagram-MW4NZA55-CcpC_2uv.js} +1 -1
  68. package/dist-web/assets/{dagre-VZM6K2ZE-YaBom1ZH.js → dagre-VZM6K2ZE-D8Ao0LUg.js} +1 -1
  69. package/dist-web/assets/{diagram-7IWD3JNH-Da4m-Hgz.js → diagram-7IWD3JNH-BITiLhjX.js} +1 -1
  70. package/dist-web/assets/{diagram-B4RE2ZJO-CAUUegxg.js → diagram-B4RE2ZJO-CJtwfy9f.js} +1 -1
  71. package/dist-web/assets/{diagram-LBJQPF4R-o21mWKG4.js → diagram-LBJQPF4R-BsVTh6w8.js} +1 -1
  72. package/dist-web/assets/{diagram-Q27KOJAE-BA3z3sXf.js → diagram-Q27KOJAE-pA_MUCUf.js} +1 -1
  73. package/dist-web/assets/{diagram-UB23O5K3-CGRdSSDK.js → diagram-UB23O5K3-CIY8z7vu.js} +1 -1
  74. package/dist-web/assets/{ebnfDiagram-BXEA7PRR-Bw-iU9FP.js → ebnfDiagram-BXEA7PRR-C3FMioqp.js} +1 -1
  75. package/dist-web/assets/{erDiagram-JOGREHBK-BwM1yiR0.js → erDiagram-JOGREHBK-CzoyKmKc.js} +1 -1
  76. package/dist-web/assets/{flowDiagram-UKHOOZJN-z05eOR1Q.js → flowDiagram-UKHOOZJN-DOqfushy.js} +1 -1
  77. package/dist-web/assets/{ganttDiagram-PKOTCBZU-BMUubNTl.js → ganttDiagram-PKOTCBZU-Cf9XQSWl.js} +1 -1
  78. package/dist-web/assets/{gitGraphDiagram-DS77QQ5N-Dccbi1wU.js → gitGraphDiagram-DS77QQ5N-DHUDofE5.js} +1 -1
  79. package/dist-web/assets/{index-h0KEOmkU.js → index-B6qEMQMx.js} +14 -14
  80. package/dist-web/assets/{index-C9eDH3u-.css → index-BmKj7CB1.css} +1 -1
  81. package/dist-web/assets/{index-BBbC5LRF.js → index-DokxDX1w.js} +1 -1
  82. package/dist-web/assets/{index-D48p0M2S.js → index-auojtiAA.js} +2 -2
  83. package/dist-web/assets/{index10-Qbuw1N6u.js → index10-Drb2n5e5.js} +1 -1
  84. package/dist-web/assets/{index11-g8v025wN.js → index11-Cx-aV4G2.js} +1 -1
  85. package/dist-web/assets/{index5-BYNcV8BY.js → index5-Dg3qGTyH.js} +1 -1
  86. package/dist-web/assets/{index6-BetANtnV.js → index6-BgvtIICP.js} +1 -1
  87. package/dist-web/assets/{index7-BcPbgCKN.js → index7-uEnniPQG.js} +1 -1
  88. package/dist-web/assets/{index8-B1lp-RvF.js → index8-h4IKc0ek.js} +2 -2
  89. package/dist-web/assets/{infoDiagram-6WML65LV-BF_pbUnN.js → infoDiagram-6WML65LV-D-3tvEiJ.js} +1 -1
  90. package/dist-web/assets/{ishikawaDiagram-WSZJBQD7-Dedv4urS.js → ishikawaDiagram-WSZJBQD7-DIIBNv7a.js} +1 -1
  91. package/dist-web/assets/{journeyDiagram-NVQOT4AX-B1Ex3b6o.js → journeyDiagram-NVQOT4AX-DP4r1Vlx.js} +1 -1
  92. package/dist-web/assets/{kanban-definition-27J2QSJJ-BtCGliBH.js → kanban-definition-27J2QSJJ-C3_Ztgxv.js} +1 -1
  93. package/dist-web/assets/{linear-ByItc8Qr.js → linear-DnqhIw-y.js} +1 -1
  94. package/dist-web/assets/{mermaid.core-B1M2O7nN.js → mermaid.core-xYUgBa3T.js} +4 -4
  95. package/dist-web/assets/{mindmap-definition-FAOFIHXS-D0UHKUyj.js → mindmap-definition-FAOFIHXS-l-dq-zfr.js} +1 -1
  96. package/dist-web/assets/{pegDiagram-VL7TDLO6-D7zYCVmG.js → pegDiagram-VL7TDLO6-C8vXQiaJ.js} +1 -1
  97. package/dist-web/assets/{pieDiagram-7S7Q4E2Y-Bd0i9CHa.js → pieDiagram-7S7Q4E2Y-CyNRSsI6.js} +1 -1
  98. package/dist-web/assets/{quadrantDiagram-CIZ2JOQS-MG-Ug47d.js → quadrantDiagram-CIZ2JOQS-D-w8AD_g.js} +1 -1
  99. package/dist-web/assets/{railroadDiagram-AXF67PYL-AZdl90Gj.js → railroadDiagram-AXF67PYL-BLkyQCWy.js} +1 -1
  100. package/dist-web/assets/{requirementDiagram-LRYGKXZP-DjplF9kt.js → requirementDiagram-LRYGKXZP-CEWO7XfT.js} +1 -1
  101. package/dist-web/assets/{sankeyDiagram-W5VNT64P-Bi9Zgfjs.js → sankeyDiagram-W5VNT64P-ByGoAaGj.js} +1 -1
  102. package/dist-web/assets/{sequenceDiagram-SI44F4Z6-ChEpdCvb.js → sequenceDiagram-SI44F4Z6-Bhn9GQtd.js} +1 -1
  103. package/dist-web/assets/{sizeCapture-X5ZJPWSS-B5RFUdaa.js → sizeCapture-X5ZJPWSS-jVKR9CTu.js} +1 -1
  104. package/dist-web/assets/{stateDiagram-OKZ733FA-BkRblSKp.js → stateDiagram-OKZ733FA-wW3XbegA.js} +1 -1
  105. package/dist-web/assets/stateDiagram-v2-UEYNNEHI-B8aids-Z.js +1 -0
  106. package/dist-web/assets/{swimlanes-SLNWSIFB-DLXH6Zhc.js → swimlanes-SLNWSIFB-B8QyFS7G.js} +2 -2
  107. package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-B8XAkdSm.js +8 -0
  108. package/dist-web/assets/{timeline-definition-Z64GVDOM-C_N88siO.js → timeline-definition-Z64GVDOM-DpvxS-1A.js} +1 -1
  109. package/dist-web/assets/{vennDiagram-T6HMQDX7-DMsnvDt0.js → vennDiagram-T6HMQDX7-BzGM0ihY.js} +1 -1
  110. package/dist-web/assets/{vue.runtime.esm-bundler-B-3uIy85.js → vue.runtime.esm-bundler-BOcpwIX1.js} +1 -1
  111. package/dist-web/assets/{wardleyDiagram-T6FBY63Y-C9SJIPzq.js → wardleyDiagram-T6FBY63Y-2DKAOs5e.js} +1 -1
  112. package/dist-web/assets/{xychartDiagram-ELKLHX3M-DZLWA6Go.js → xychartDiagram-ELKLHX3M-B0SP0TXd.js} +1 -1
  113. package/dist-web/index.html +32 -32
  114. package/dist-web/vis/index.html.gz +0 -0
  115. package/package.json +1 -1
  116. package/scripts/fix-node-pty-perms.js +53 -53
  117. package/standard-skills/blun-app-design-system/SKILL.md +30 -30
  118. package/standard-skills/blun-app-design-system/provenance.json +17 -17
  119. package/standard-skills/blun-app-design-system/references/tokens.md +48 -48
  120. package/standard-skills/blun-app-design-system/scripts/check-blun-design.mjs +49 -49
  121. package/standard-skills/translate-native/README.md +1 -1
  122. package/standard-skills/translate-native/scripts/blun_language_guard.py +2 -0
  123. package/standard-skills/translate-native/scripts/language_gateway.py +2 -0
  124. package/standard-skills/translate-native/scripts/pre_output_guard.py +2 -0
  125. package/standard-tools/language-guard/blun_language_guard.py +2 -0
  126. package/standard-tools/language-guard/language_gateway.py +2 -0
  127. package/standard-tools/language-guard/pre_output_guard.py +2 -0
  128. package/telegram-plugin/blun.plugin.json +20 -20
  129. package/telegram-plugin/commands/access.md +37 -37
  130. package/telegram-plugin/dist/bridge.mjs +310 -187
  131. package/telegram-plugin/dist/mcp-server.mjs +174 -261
  132. package/telegram-plugin/dist/noise.mjs +1 -19
  133. package/worker-host.mjs +13747 -16824
  134. package/bin/profile-handoff.cjs +0 -152
  135. package/bin/profile-launch.cjs +0 -120
  136. package/dist-web/assets/DesignSystemView-DwK1aQMo.js +0 -12
  137. package/dist-web/assets/DesignSystemView-szjTj7ns.css +0 -1
  138. package/dist-web/assets/channel-BnluexuN.js +0 -1
  139. package/dist-web/assets/classDiagram-JCYQIIEL-BliFdSD9.js +0 -1
  140. package/dist-web/assets/classDiagram-v2-OCEON4UE-BliFdSD9.js +0 -1
  141. package/dist-web/assets/stateDiagram-v2-UEYNNEHI-fVkQcACW.js +0 -1
  142. package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-Bg-BiZJQ.js +0 -8
@@ -1,186 +1,186 @@
1
- 'use strict';
2
-
3
- const crypto = require('node:crypto');
4
- const fs = require('node:fs');
5
- const https = require('node:https');
6
- const os = require('node:os');
7
- const path = require('node:path');
8
- const { spawnSync } = require('node:child_process');
9
-
10
- const NODE_VERSION = '24.15.0';
11
- const MAX_ARCHIVE_BYTES = 100 * 1024 * 1024;
12
- const DISTRIBUTIONS = {
13
- 'darwin-arm64': {
14
- archive: `node-v${NODE_VERSION}-darwin-arm64.tar.gz`,
15
- binary: path.join('bin', 'node'),
16
- format: 'tar.gz',
17
- sha256: '372331b969779ab5d15b949884fc6eaf88d5afe87bde8ba881d6400b9100ffc4',
18
- },
19
- 'darwin-x64': {
20
- archive: `node-v${NODE_VERSION}-darwin-x64.tar.gz`,
21
- binary: path.join('bin', 'node'),
22
- format: 'tar.gz',
23
- sha256: 'ffd5ee293467927f3ee731a553eb88fd1f48cf74eebc2d74a6babe4af228673b',
24
- },
25
- 'win32-arm64': {
26
- archive: `node-v${NODE_VERSION}-win-arm64.zip`,
27
- binary: 'node.exe',
28
- format: 'zip',
29
- sha256: 'c9eb7402eda26e2ba7e44b6727fc85a8de56c5095b1f71ebd3062892211aa116',
30
- },
31
- 'win32-x64': {
32
- archive: `node-v${NODE_VERSION}-win-x64.zip`,
33
- binary: 'node.exe',
34
- format: 'zip',
35
- sha256: 'cc5149eabd53779ce1e7bdc5401643622d0c7e6800ade18928a767e940bb0e62',
36
- },
37
- };
38
-
39
- function distributionFor(platform, arch) {
40
- return DISTRIBUTIONS[`${platform}-${arch}`] || null;
41
- }
42
-
43
- function sha256File(file) {
44
- const hash = crypto.createHash('sha256');
45
- hash.update(fs.readFileSync(file));
46
- return hash.digest('hex');
47
- }
48
-
49
- function downloadFile(url, destination, redirectsLeft = 3) {
50
- return new Promise((resolve, reject) => {
51
- const request = https.get(url, { timeout: 30_000 }, (response) => {
52
- if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
53
- response.resume();
54
- if (redirectsLeft <= 0) {
55
- reject(new Error('Zu viele Weiterleitungen beim Node.js-Download.'));
56
- return;
57
- }
58
- const redirect = new URL(response.headers.location, url);
59
- if (redirect.protocol !== 'https:') {
60
- reject(new Error('Unsichere Weiterleitung beim Node.js-Download abgelehnt.'));
61
- return;
62
- }
63
- downloadFile(redirect.href, destination, redirectsLeft - 1).then(resolve, reject);
64
- return;
65
- }
66
-
67
- if (response.statusCode !== 200) {
68
- response.resume();
69
- reject(new Error(`Node.js-Download antwortete mit HTTP ${response.statusCode}.`));
70
- return;
71
- }
72
-
73
- const contentLength = Number(response.headers['content-length'] || 0);
74
- if (contentLength > MAX_ARCHIVE_BYTES) {
75
- response.destroy();
76
- reject(new Error('Node.js-Download ist groesser als erwartet.'));
77
- return;
78
- }
79
-
80
- let received = 0;
81
- const output = fs.createWriteStream(destination, { flags: 'wx' });
82
- response.on('data', (chunk) => {
83
- received += chunk.length;
84
- if (received > MAX_ARCHIVE_BYTES) {
85
- response.destroy(new Error('Node.js-Download ist groesser als erwartet.'));
86
- }
87
- });
88
- response.on('error', reject);
89
- output.on('error', reject);
90
- output.on('finish', () => output.close(resolve));
91
- response.pipe(output);
92
- });
93
- request.on('error', reject);
94
- request.on('timeout', () => request.destroy(new Error('Zeitlimit beim Node.js-Download erreicht.')));
95
- });
96
- }
97
-
98
- function usableNode(binary) {
99
- if (!fs.existsSync(binary)) return false;
100
- const result = spawnSync(binary, ['--version'], { encoding: 'utf8', timeout: 5_000 });
101
- return result.status === 0 && result.stdout.trim() === `v${NODE_VERSION}`;
102
- }
103
-
104
- function archiveRootName(distribution) {
105
- const suffix = distribution.format === 'zip' ? '.zip' : '.tar.gz';
106
- if (!distribution.archive.endsWith(suffix)) {
107
- throw new Error('Unbekanntes Format der Node.js-Laufzeit.');
108
- }
109
- return distribution.archive.slice(0, -suffix.length);
110
- }
111
-
112
- function unpackArchive(archive, destination, distribution, platform) {
113
- const command = platform === 'win32'
114
- ? path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'tar.exe')
115
- : '/usr/bin/tar';
116
- const args = distribution.format === 'zip'
117
- ? ['-xf', archive, '-C', destination]
118
- : ['-xzf', archive, '-C', destination];
119
- return spawnSync(command, args, {
120
- encoding: 'utf8',
121
- timeout: 120_000,
122
- });
123
- }
124
-
125
- async function ensureManagedNode(blunHome, platform, arch) {
126
- const distribution = distributionFor(platform, arch);
127
- if (!distribution) {
128
- throw new Error(
129
- `Automatische Node.js-Einrichtung wird fuer ${platform}-${arch} noch nicht unterstuetzt.`,
130
- );
131
- }
132
-
133
- const installDir = path.join(
134
- blunHome,
135
- 'runtime',
136
- 'node',
137
- `v${NODE_VERSION}-${platform}-${arch}`,
138
- );
139
- const binary = path.join(installDir, distribution.binary);
140
- if (usableNode(binary)) return binary;
141
-
142
- const staging = fs.mkdtempSync(path.join(os.tmpdir(), 'blun-node-runtime-'));
143
- try {
144
- const archive = path.join(staging, distribution.archive);
145
- await downloadFile(
146
- `https://nodejs.org/dist/v${NODE_VERSION}/${distribution.archive}`,
147
- archive,
148
- );
149
-
150
- const actualSha = sha256File(archive);
151
- if (actualSha !== distribution.sha256) {
152
- throw new Error('Die SHA-256-Pruefsumme der Node.js-Laufzeit stimmt nicht.');
153
- }
154
-
155
- const extractedName = archiveRootName(distribution);
156
- const extractedDir = path.join(staging, extractedName);
157
- const unpacked = unpackArchive(archive, staging, distribution, platform);
158
- if (unpacked.status !== 0 || !usableNode(path.join(extractedDir, distribution.binary))) {
159
- const detail = (unpacked.stderr || '').trim();
160
- throw new Error(
161
- `Die Node.js-Laufzeit konnte nicht entpackt werden${detail ? `: ${detail}` : '.'}`,
162
- );
163
- }
164
-
165
- fs.mkdirSync(path.dirname(installDir), { recursive: true });
166
- if (usableNode(binary)) return binary;
167
- fs.rmSync(installDir, { recursive: true, force: true });
168
- fs.renameSync(extractedDir, installDir);
169
- if (!usableNode(binary)) throw new Error('Die installierte Node.js-Laufzeit startet nicht.');
170
- return binary;
171
- } finally {
172
- fs.rmSync(staging, { recursive: true, force: true });
173
- }
174
- }
175
-
176
- module.exports = {
177
- DISTRIBUTIONS,
178
- MAX_ARCHIVE_BYTES,
179
- NODE_VERSION,
180
- distributionFor,
181
- ensureManagedNode,
182
- archiveRootName,
183
- sha256File,
184
- unpackArchive,
185
- usableNode,
186
- };
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const https = require('node:https');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+ const { spawnSync } = require('node:child_process');
9
+
10
+ const NODE_VERSION = '24.15.0';
11
+ const MAX_ARCHIVE_BYTES = 100 * 1024 * 1024;
12
+ const DISTRIBUTIONS = {
13
+ 'darwin-arm64': {
14
+ archive: `node-v${NODE_VERSION}-darwin-arm64.tar.gz`,
15
+ binary: path.join('bin', 'node'),
16
+ format: 'tar.gz',
17
+ sha256: '372331b969779ab5d15b949884fc6eaf88d5afe87bde8ba881d6400b9100ffc4',
18
+ },
19
+ 'darwin-x64': {
20
+ archive: `node-v${NODE_VERSION}-darwin-x64.tar.gz`,
21
+ binary: path.join('bin', 'node'),
22
+ format: 'tar.gz',
23
+ sha256: 'ffd5ee293467927f3ee731a553eb88fd1f48cf74eebc2d74a6babe4af228673b',
24
+ },
25
+ 'win32-arm64': {
26
+ archive: `node-v${NODE_VERSION}-win-arm64.zip`,
27
+ binary: 'node.exe',
28
+ format: 'zip',
29
+ sha256: 'c9eb7402eda26e2ba7e44b6727fc85a8de56c5095b1f71ebd3062892211aa116',
30
+ },
31
+ 'win32-x64': {
32
+ archive: `node-v${NODE_VERSION}-win-x64.zip`,
33
+ binary: 'node.exe',
34
+ format: 'zip',
35
+ sha256: 'cc5149eabd53779ce1e7bdc5401643622d0c7e6800ade18928a767e940bb0e62',
36
+ },
37
+ };
38
+
39
+ function distributionFor(platform, arch) {
40
+ return DISTRIBUTIONS[`${platform}-${arch}`] || null;
41
+ }
42
+
43
+ function sha256File(file) {
44
+ const hash = crypto.createHash('sha256');
45
+ hash.update(fs.readFileSync(file));
46
+ return hash.digest('hex');
47
+ }
48
+
49
+ function downloadFile(url, destination, redirectsLeft = 3) {
50
+ return new Promise((resolve, reject) => {
51
+ const request = https.get(url, { timeout: 30_000 }, (response) => {
52
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
53
+ response.resume();
54
+ if (redirectsLeft <= 0) {
55
+ reject(new Error('Zu viele Weiterleitungen beim Node.js-Download.'));
56
+ return;
57
+ }
58
+ const redirect = new URL(response.headers.location, url);
59
+ if (redirect.protocol !== 'https:') {
60
+ reject(new Error('Unsichere Weiterleitung beim Node.js-Download abgelehnt.'));
61
+ return;
62
+ }
63
+ downloadFile(redirect.href, destination, redirectsLeft - 1).then(resolve, reject);
64
+ return;
65
+ }
66
+
67
+ if (response.statusCode !== 200) {
68
+ response.resume();
69
+ reject(new Error(`Node.js-Download antwortete mit HTTP ${response.statusCode}.`));
70
+ return;
71
+ }
72
+
73
+ const contentLength = Number(response.headers['content-length'] || 0);
74
+ if (contentLength > MAX_ARCHIVE_BYTES) {
75
+ response.destroy();
76
+ reject(new Error('Node.js-Download ist groesser als erwartet.'));
77
+ return;
78
+ }
79
+
80
+ let received = 0;
81
+ const output = fs.createWriteStream(destination, { flags: 'wx' });
82
+ response.on('data', (chunk) => {
83
+ received += chunk.length;
84
+ if (received > MAX_ARCHIVE_BYTES) {
85
+ response.destroy(new Error('Node.js-Download ist groesser als erwartet.'));
86
+ }
87
+ });
88
+ response.on('error', reject);
89
+ output.on('error', reject);
90
+ output.on('finish', () => output.close(resolve));
91
+ response.pipe(output);
92
+ });
93
+ request.on('error', reject);
94
+ request.on('timeout', () => request.destroy(new Error('Zeitlimit beim Node.js-Download erreicht.')));
95
+ });
96
+ }
97
+
98
+ function usableNode(binary) {
99
+ if (!fs.existsSync(binary)) return false;
100
+ const result = spawnSync(binary, ['--version'], { encoding: 'utf8', timeout: 5_000 });
101
+ return result.status === 0 && result.stdout.trim() === `v${NODE_VERSION}`;
102
+ }
103
+
104
+ function archiveRootName(distribution) {
105
+ const suffix = distribution.format === 'zip' ? '.zip' : '.tar.gz';
106
+ if (!distribution.archive.endsWith(suffix)) {
107
+ throw new Error('Unbekanntes Format der Node.js-Laufzeit.');
108
+ }
109
+ return distribution.archive.slice(0, -suffix.length);
110
+ }
111
+
112
+ function unpackArchive(archive, destination, distribution, platform) {
113
+ const command = platform === 'win32'
114
+ ? path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'tar.exe')
115
+ : '/usr/bin/tar';
116
+ const args = distribution.format === 'zip'
117
+ ? ['-xf', archive, '-C', destination]
118
+ : ['-xzf', archive, '-C', destination];
119
+ return spawnSync(command, args, {
120
+ encoding: 'utf8',
121
+ timeout: 120_000,
122
+ });
123
+ }
124
+
125
+ async function ensureManagedNode(blunHome, platform, arch) {
126
+ const distribution = distributionFor(platform, arch);
127
+ if (!distribution) {
128
+ throw new Error(
129
+ `Automatische Node.js-Einrichtung wird fuer ${platform}-${arch} noch nicht unterstuetzt.`,
130
+ );
131
+ }
132
+
133
+ const installDir = path.join(
134
+ blunHome,
135
+ 'runtime',
136
+ 'node',
137
+ `v${NODE_VERSION}-${platform}-${arch}`,
138
+ );
139
+ const binary = path.join(installDir, distribution.binary);
140
+ if (usableNode(binary)) return binary;
141
+
142
+ const staging = fs.mkdtempSync(path.join(os.tmpdir(), 'blun-node-runtime-'));
143
+ try {
144
+ const archive = path.join(staging, distribution.archive);
145
+ await downloadFile(
146
+ `https://nodejs.org/dist/v${NODE_VERSION}/${distribution.archive}`,
147
+ archive,
148
+ );
149
+
150
+ const actualSha = sha256File(archive);
151
+ if (actualSha !== distribution.sha256) {
152
+ throw new Error('Die SHA-256-Pruefsumme der Node.js-Laufzeit stimmt nicht.');
153
+ }
154
+
155
+ const extractedName = archiveRootName(distribution);
156
+ const extractedDir = path.join(staging, extractedName);
157
+ const unpacked = unpackArchive(archive, staging, distribution, platform);
158
+ if (unpacked.status !== 0 || !usableNode(path.join(extractedDir, distribution.binary))) {
159
+ const detail = (unpacked.stderr || '').trim();
160
+ throw new Error(
161
+ `Die Node.js-Laufzeit konnte nicht entpackt werden${detail ? `: ${detail}` : '.'}`,
162
+ );
163
+ }
164
+
165
+ fs.mkdirSync(path.dirname(installDir), { recursive: true });
166
+ if (usableNode(binary)) return binary;
167
+ fs.rmSync(installDir, { recursive: true, force: true });
168
+ fs.renameSync(extractedDir, installDir);
169
+ if (!usableNode(binary)) throw new Error('Die installierte Node.js-Laufzeit startet nicht.');
170
+ return binary;
171
+ } finally {
172
+ fs.rmSync(staging, { recursive: true, force: true });
173
+ }
174
+ }
175
+
176
+ module.exports = {
177
+ DISTRIBUTIONS,
178
+ MAX_ARCHIVE_BYTES,
179
+ NODE_VERSION,
180
+ distributionFor,
181
+ ensureManagedNode,
182
+ archiveRootName,
183
+ sha256File,
184
+ unpackArchive,
185
+ usableNode,
186
+ };
@@ -1,122 +1,115 @@
1
- 'use strict';
2
- const { createHash, randomUUID } = require('node:crypto');
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
- const { ensurePrivateDirectory, securePrivateTree } = require('./private-paths');
6
- const SCHEMA = 'blun.managed-plugin-selection/v1';
7
-
8
- function snapshotPluginTree(root, expectedEntries = null) {
1
+ 'use strict';
2
+ const { createHash, randomUUID } = require('node:crypto');
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { ensurePrivateDirectory, securePrivateTree } = require('./private-paths');
6
+ const SCHEMA = 'blun.managed-plugin-selection/v1';
7
+
8
+ function snapshotPluginTree(root) {
9
9
  const entries = [];
10
- const expectedByPath = expectedEntries === null ? null
11
- : new Map(expectedEntries.map(entry => [entry.path, entry]));
12
- const rootStat = fs.lstatSync(root);
13
- if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) return null;
14
- const visit = (directory, relativeDirectory = '') => {
15
- for (const entry of fs.readdirSync(directory, { withFileTypes: true })
16
- .sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0)) {
17
- const relativePath = path.join(relativeDirectory, entry.name);
18
- const absolutePath = path.join(directory, entry.name);
10
+ const rootStat = fs.lstatSync(root);
11
+ if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) return null;
12
+ const visit = (directory, relativeDirectory = '') => {
13
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })
14
+ .sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0)) {
15
+ const relativePath = path.join(relativeDirectory, entry.name);
16
+ const absolutePath = path.join(directory, entry.name);
19
17
  const stat = fs.lstatSync(absolutePath);
20
18
  if (stat.isSymbolicLink()) return false;
21
- const expected = expectedByPath?.get(relativePath);
22
- if (expectedByPath && !expected) return false;
23
19
  if (stat.isDirectory()) {
24
- if (expected && expected.kind !== 'directory') return false;
25
- entries.push({ kind: 'directory', path: relativePath });
26
- if (!visit(absolutePath, relativePath)) return false;
20
+ entries.push({ kind: 'directory', path: relativePath });
21
+ if (!visit(absolutePath, relativePath)) return false;
27
22
  } else if (stat.isFile()) {
28
- if (expected && (expected.kind !== 'file' || stat.size !== expected.content.length)) return false;
29
- entries.push({ kind: 'file', path: relativePath, content: fs.readFileSync(absolutePath) });
30
- } else return false;
31
- }
32
- return true;
33
- };
34
- if (!visit(root) || (expectedByPath && entries.length !== expectedByPath.size)) return null;
35
- return entries;
23
+ entries.push({ kind: 'file', path: relativePath, content: fs.readFileSync(absolutePath) });
24
+ } else return false;
25
+ }
26
+ return true;
27
+ };
28
+ return visit(root) ? entries : null;
36
29
  }
37
-
38
- function digestPluginEntries(entries) {
39
- if (entries === null) throw new Error('Invalid managed plugin tree');
40
- const hash = createHash('sha256');
41
- for (const entry of entries) {
42
- hash.update(Buffer.from(`${entry.kind}\0${entry.path}\0`, 'utf8'));
43
- if (entry.kind === 'file') {
44
- const length = Buffer.allocUnsafe(8);
45
- length.writeBigUInt64BE(BigInt(entry.content.length));
46
- hash.update(length);
47
- hash.update(entry.content);
48
- }
49
- }
50
- return hash.digest('hex');
51
- }
52
-
53
- function samePath(left, right) {
54
- const normalize = value => process.platform === 'win32' ? path.resolve(value).toLowerCase() : path.resolve(value);
55
- return normalize(left) === normalize(right);
56
- }
57
-
58
- function scopedPluginRoot(blunDir, pluginRoot) {
59
- const managed = path.resolve(blunDir, 'plugins', 'managed');
60
- const root = path.resolve(pluginRoot);
61
- if (!samePath(path.dirname(root), managed)) throw new Error('Selected AgentSpine root is outside this profile');
62
- for (const directory of [blunDir, path.dirname(managed), managed, root]) {
63
- const stat = fs.lstatSync(directory);
64
- if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error('Selected AgentSpine root contains a linked directory');
65
- }
66
- if (!samePath(path.dirname(fs.realpathSync(root)), fs.realpathSync(managed))) {
67
- throw new Error('Selected AgentSpine root resolves outside this profile');
68
- }
69
- return root;
70
- }
71
-
72
- function describeAgentSpineSelection({ blunDir, pluginRoot }) {
73
- const root = scopedPluginRoot(blunDir, pluginRoot);
74
- const digest = digestPluginEntries(snapshotPluginTree(root));
75
- if (path.basename(root) !== `agent-spine-${digest}`) throw new Error('Selected AgentSpine digest mismatch');
76
- return { schema: SCHEMA, digest };
77
- }
78
-
79
- function selectedAgentSpineRoot({ installed, blunDir }) {
80
- const records = installed.plugins.filter(record => record.id === 'agent-spine');
81
- if (!records.some(record => Object.hasOwn(record, 'managedSelection'))) return null;
82
- if (records.length !== 1) throw new Error('Ambiguous AgentSpine selection');
83
- const [record] = records;
84
- const selection = record.managedSelection;
85
- if (!selection || selection.schema !== SCHEMA || !/^[a-f0-9]{64}$/.test(selection.digest || '')
86
- || Object.keys(selection).sort().join(',') !== 'digest,schema' || typeof record.root !== 'string') {
87
- throw new Error('Invalid AgentSpine selection');
88
- }
89
- const actual = describeAgentSpineSelection({ blunDir, pluginRoot: record.root });
90
- if (actual.digest !== selection.digest) throw new Error('Selected AgentSpine digest mismatch');
91
- return record.root;
92
- }
93
-
94
- function copySelectedAgentSpinePlugin({ blunDir, sourceRoot }) {
95
- const plugins = path.resolve(blunDir, 'plugins');
96
- const managed = path.join(plugins, 'managed');
97
- ensurePrivateDirectory(plugins);
98
- ensurePrivateDirectory(managed);
99
- const digest = digestPluginEntries(snapshotPluginTree(sourceRoot));
100
- const pluginRoot = path.join(managed, `agent-spine-${digest}`);
101
- if (fs.existsSync(pluginRoot)) {
102
- describeAgentSpineSelection({ blunDir, pluginRoot });
103
- return pluginRoot;
104
- }
105
- const staging = path.join(managed, `.agent-spine-selected-${process.pid}-${randomUUID()}.tmp`);
106
- try {
107
- fs.cpSync(sourceRoot, staging, { recursive: true, errorOnExist: true, force: false });
108
- securePrivateTree(staging);
109
- if (digestPluginEntries(snapshotPluginTree(staging)) !== digest) throw new Error('AgentSpine source changed during installation');
110
- fs.renameSync(staging, pluginRoot);
111
- describeAgentSpineSelection({ blunDir, pluginRoot });
112
- return pluginRoot;
113
- } finally {
114
- if (!samePath(path.dirname(staging), managed) || !path.basename(staging).startsWith('.agent-spine-selected-')) {
115
- throw new Error('Invalid AgentSpine staging cleanup path');
116
- }
117
- fs.rmSync(staging, { recursive: true, force: true });
118
- }
119
- }
120
-
121
- module.exports = { snapshotPluginTree, digestPluginEntries, describeAgentSpineSelection,
122
- selectedAgentSpineRoot, copySelectedAgentSpinePlugin };
30
+
31
+ function digestPluginEntries(entries) {
32
+ if (entries === null) throw new Error('Invalid managed plugin tree');
33
+ const hash = createHash('sha256');
34
+ for (const entry of entries) {
35
+ hash.update(Buffer.from(`${entry.kind}\0${entry.path}\0`, 'utf8'));
36
+ if (entry.kind === 'file') {
37
+ const length = Buffer.allocUnsafe(8);
38
+ length.writeBigUInt64BE(BigInt(entry.content.length));
39
+ hash.update(length);
40
+ hash.update(entry.content);
41
+ }
42
+ }
43
+ return hash.digest('hex');
44
+ }
45
+
46
+ function samePath(left, right) {
47
+ const normalize = value => process.platform === 'win32' ? path.resolve(value).toLowerCase() : path.resolve(value);
48
+ return normalize(left) === normalize(right);
49
+ }
50
+
51
+ function scopedPluginRoot(blunDir, pluginRoot) {
52
+ const managed = path.resolve(blunDir, 'plugins', 'managed');
53
+ const root = path.resolve(pluginRoot);
54
+ if (!samePath(path.dirname(root), managed)) throw new Error('Selected AgentSpine root is outside this profile');
55
+ for (const directory of [blunDir, path.dirname(managed), managed, root]) {
56
+ const stat = fs.lstatSync(directory);
57
+ if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error('Selected AgentSpine root contains a linked directory');
58
+ }
59
+ if (!samePath(path.dirname(fs.realpathSync(root)), fs.realpathSync(managed))) {
60
+ throw new Error('Selected AgentSpine root resolves outside this profile');
61
+ }
62
+ return root;
63
+ }
64
+
65
+ function describeAgentSpineSelection({ blunDir, pluginRoot }) {
66
+ const root = scopedPluginRoot(blunDir, pluginRoot);
67
+ const digest = digestPluginEntries(snapshotPluginTree(root));
68
+ if (path.basename(root) !== `agent-spine-${digest}`) throw new Error('Selected AgentSpine digest mismatch');
69
+ return { schema: SCHEMA, digest };
70
+ }
71
+
72
+ function selectedAgentSpineRoot({ installed, blunDir }) {
73
+ const records = installed.plugins.filter(record => record.id === 'agent-spine');
74
+ if (!records.some(record => Object.hasOwn(record, 'managedSelection'))) return null;
75
+ if (records.length !== 1) throw new Error('Ambiguous AgentSpine selection');
76
+ const [record] = records;
77
+ const selection = record.managedSelection;
78
+ if (!selection || selection.schema !== SCHEMA || !/^[a-f0-9]{64}$/.test(selection.digest || '')
79
+ || Object.keys(selection).sort().join(',') !== 'digest,schema' || typeof record.root !== 'string') {
80
+ throw new Error('Invalid AgentSpine selection');
81
+ }
82
+ const actual = describeAgentSpineSelection({ blunDir, pluginRoot: record.root });
83
+ if (actual.digest !== selection.digest) throw new Error('Selected AgentSpine digest mismatch');
84
+ return record.root;
85
+ }
86
+
87
+ function copySelectedAgentSpinePlugin({ blunDir, sourceRoot }) {
88
+ const plugins = path.resolve(blunDir, 'plugins');
89
+ const managed = path.join(plugins, 'managed');
90
+ ensurePrivateDirectory(plugins);
91
+ ensurePrivateDirectory(managed);
92
+ const digest = digestPluginEntries(snapshotPluginTree(sourceRoot));
93
+ const pluginRoot = path.join(managed, `agent-spine-${digest}`);
94
+ if (fs.existsSync(pluginRoot)) {
95
+ describeAgentSpineSelection({ blunDir, pluginRoot });
96
+ return pluginRoot;
97
+ }
98
+ const staging = path.join(managed, `.agent-spine-selected-${process.pid}-${randomUUID()}.tmp`);
99
+ try {
100
+ fs.cpSync(sourceRoot, staging, { recursive: true, errorOnExist: true, force: false });
101
+ securePrivateTree(staging);
102
+ if (digestPluginEntries(snapshotPluginTree(staging)) !== digest) throw new Error('AgentSpine source changed during installation');
103
+ fs.renameSync(staging, pluginRoot);
104
+ describeAgentSpineSelection({ blunDir, pluginRoot });
105
+ return pluginRoot;
106
+ } finally {
107
+ if (!samePath(path.dirname(staging), managed) || !path.basename(staging).startsWith('.agent-spine-selected-')) {
108
+ throw new Error('Invalid AgentSpine staging cleanup path');
109
+ }
110
+ fs.rmSync(staging, { recursive: true, force: true });
111
+ }
112
+ }
113
+
114
+ module.exports = { snapshotPluginTree, digestPluginEntries, describeAgentSpineSelection,
115
+ selectedAgentSpineRoot, copySelectedAgentSpinePlugin };