blun-king-cli 9.1.601 → 9.1.603

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 (124) hide show
  1. package/CHANGELOG.md +105 -94
  2. package/LIESMICH.txt +57 -57
  3. package/README.md +90 -90
  4. package/bin/agentspine-king-goal-inbox.mjs +127 -127
  5. package/bin/agentspine-king-goal-intake.mjs +106 -106
  6. package/bin/agentspine-king-host-runner.mjs +109 -109
  7. package/bin/agentspine-king-snapshot-policy.cjs +80 -80
  8. package/bin/agentspine-king-status-policy.mjs +226 -226
  9. package/bin/agentspine-king-worker-host.mjs +160 -160
  10. package/bin/core-bootstrap.js +77 -77
  11. package/bin/launcher-mode.js +93 -93
  12. package/bin/launcher-restart-policy.cjs +131 -131
  13. package/bin/launcher-runtime.js +491 -491
  14. package/bin/managed-node.js +186 -186
  15. package/bin/managed-plugin-selection.cjs +115 -115
  16. package/bin/native-module-repair.js +201 -201
  17. package/bin/node-runtime.js +34 -34
  18. package/bin/node-version.js +49 -49
  19. package/bin/plugin-bootstrap.js +162 -162
  20. package/bin/private-paths.js +1606 -1606
  21. package/bin/runtime-exit-ledger.cjs +144 -144
  22. package/bin/runtime-exit-ledger.d.cts +23 -23
  23. package/bin/standard-tools-bootstrap.js +272 -272
  24. package/bin/turn-thinking-policy.cjs +82 -82
  25. package/bin/update-copy.js +200 -200
  26. package/bin/update-lease.js +597 -597
  27. package/bin/update-notice.js +1568 -1568
  28. package/bin/verify-agent-behavior.cjs +122 -122
  29. package/bin/verify-agent-components.cjs +104 -104
  30. package/bin/verify-bundled-agent-sources.cjs +83 -83
  31. package/bin/windows-node-crash-dump.cjs +289 -289
  32. package/blun.mjs +1709 -1601
  33. package/codebase-index/codebase_index.py +470 -470
  34. package/dist-web/assets/{CodeBlockNode-CXnTLMNr.js → CodeBlockNode-TKKkJX8L.js} +1 -1
  35. package/dist-web/assets/{CodeBlockShell.vue_vue_type_style_index_0_lang-CCF8Ntnq.js → CodeBlockShell.vue_vue_type_style_index_0_lang-CUj2Ds5x.js} +1 -1
  36. package/dist-web/assets/DesignSystemView-DwK1aQMo.js +12 -0
  37. package/dist-web/assets/DesignSystemView-szjTj7ns.css +1 -0
  38. package/dist-web/assets/{Tooltip-CFQEzqC-.js → Tooltip-CTWv7WCU.js} +1 -1
  39. package/dist-web/assets/{abnfDiagram-N423BO3Z-Z1APZG5m.js → abnfDiagram-N423BO3Z-DA8V8Zjh.js} +1 -1
  40. package/dist-web/assets/{arc-C4VyWloT.js → arc-YtddKbdY.js} +1 -1
  41. package/dist-web/assets/{architectureDiagram-T3A2C74G-DpddbUcM.js → architectureDiagram-T3A2C74G-CGEiObmo.js} +1 -1
  42. package/dist-web/assets/{blockDiagram-VBNYF7ZC-BqccOSMr.js → blockDiagram-VBNYF7ZC-Bd5Vt50e.js} +1 -1
  43. package/dist-web/assets/{c4Diagram-5PPSVZJV-OHAcJrTy.js → c4Diagram-5PPSVZJV-D2ApPM54.js} +1 -1
  44. package/dist-web/assets/channel-BnluexuN.js +1 -0
  45. package/dist-web/assets/{chunk-2GRJ4B5K-DqJHs8Fw.js → chunk-2GRJ4B5K-BDcIEZSl.js} +1 -1
  46. package/dist-web/assets/{chunk-2Q5K7J3B-B1nCd91o.js → chunk-2Q5K7J3B-6aSabCX7.js} +1 -1
  47. package/dist-web/assets/{chunk-5RXB4S5H-neG70kZx.js → chunk-5RXB4S5H-BFRQvIJS.js} +1 -1
  48. package/dist-web/assets/{chunk-5VM5RSS4-l7biGqeH.js → chunk-5VM5RSS4-ChAa-DNY.js} +1 -1
  49. package/dist-web/assets/{chunk-6Q2QTUOP-BO-F11Gs.js → chunk-6Q2QTUOP-BIfrb2Jc.js} +1 -1
  50. package/dist-web/assets/{chunk-GF5L2VYU-BtC69fw0.js → chunk-GF5L2VYU-BR0gvtWI.js} +1 -1
  51. package/dist-web/assets/{chunk-JWPE2WC7-DIOubEqr.js → chunk-JWPE2WC7-BgXKsx5E.js} +1 -1
  52. package/dist-web/assets/{chunk-KBJHAD2P-DDUeH_Ba.js → chunk-KBJHAD2P-I8mrZhpz.js} +1 -1
  53. package/dist-web/assets/{chunk-RYQCIY6F-B9gFMVbS.js → chunk-RYQCIY6F-BMPaem92.js} +1 -1
  54. package/dist-web/assets/{chunk-XXDRQBXY-Bn1Xz6TQ.js → chunk-XXDRQBXY-COy0J-0j.js} +1 -1
  55. package/dist-web/assets/classDiagram-JCYQIIEL-BliFdSD9.js +1 -0
  56. package/dist-web/assets/classDiagram-v2-OCEON4UE-BliFdSD9.js +1 -0
  57. package/dist-web/assets/{cose-bilkent-JH36ORCC-DqigO3T4.js → cose-bilkent-JH36ORCC-MosTTRdo.js} +1 -1
  58. package/dist-web/assets/{cynefin-VYW2F7L2-rpD93tQ-.js → cynefin-VYW2F7L2-5tjPgrkP.js} +1 -1
  59. package/dist-web/assets/{cynefinDiagram-MW4NZA55-DbTqnkVJ.js → cynefinDiagram-MW4NZA55-CbDcdInR.js} +1 -1
  60. package/dist-web/assets/{dagre-VZM6K2ZE-DcM8aEVY.js → dagre-VZM6K2ZE-YaBom1ZH.js} +1 -1
  61. package/dist-web/assets/{diagram-7IWD3JNH-UymgsBNa.js → diagram-7IWD3JNH-Da4m-Hgz.js} +1 -1
  62. package/dist-web/assets/{diagram-B4RE2ZJO-BiYu5WPF.js → diagram-B4RE2ZJO-CAUUegxg.js} +1 -1
  63. package/dist-web/assets/{diagram-LBJQPF4R-BD3kkgOO.js → diagram-LBJQPF4R-o21mWKG4.js} +1 -1
  64. package/dist-web/assets/{diagram-Q27KOJAE-D1mYQ_cI.js → diagram-Q27KOJAE-BA3z3sXf.js} +1 -1
  65. package/dist-web/assets/{diagram-UB23O5K3-BtX8u6HG.js → diagram-UB23O5K3-CGRdSSDK.js} +1 -1
  66. package/dist-web/assets/{ebnfDiagram-BXEA7PRR-CBAnFkRF.js → ebnfDiagram-BXEA7PRR-Bw-iU9FP.js} +1 -1
  67. package/dist-web/assets/{erDiagram-JOGREHBK-3vKcWsxm.js → erDiagram-JOGREHBK-BwM1yiR0.js} +1 -1
  68. package/dist-web/assets/{flowDiagram-UKHOOZJN-D9wBbHm2.js → flowDiagram-UKHOOZJN-z05eOR1Q.js} +1 -1
  69. package/dist-web/assets/{ganttDiagram-PKOTCBZU-Cd_dViF-.js → ganttDiagram-PKOTCBZU-BMUubNTl.js} +1 -1
  70. package/dist-web/assets/{gitGraphDiagram-DS77QQ5N-BiEEtTo7.js → gitGraphDiagram-DS77QQ5N-Dccbi1wU.js} +1 -1
  71. package/dist-web/assets/{index-DdEakFJM.js → index-BBbC5LRF.js} +1 -1
  72. package/dist-web/assets/{index-BmKj7CB1.css → index-C9eDH3u-.css} +1 -1
  73. package/dist-web/assets/{index-Ba1OvLjf.js → index-D48p0M2S.js} +2 -2
  74. package/dist-web/assets/{index-CiPA8Kex.js → index-h0KEOmkU.js} +14 -14
  75. package/dist-web/assets/{index10-CNr4U-Nw.js → index10-Qbuw1N6u.js} +1 -1
  76. package/dist-web/assets/{index11-Dto_yZNz.js → index11-g8v025wN.js} +1 -1
  77. package/dist-web/assets/{index5-B2PldaO2.js → index5-BYNcV8BY.js} +1 -1
  78. package/dist-web/assets/{index6-CBdZmmyx.js → index6-BetANtnV.js} +1 -1
  79. package/dist-web/assets/{index7-Dz9oCsUF.js → index7-BcPbgCKN.js} +1 -1
  80. package/dist-web/assets/{index8-B6k25jtU.js → index8-B1lp-RvF.js} +2 -2
  81. package/dist-web/assets/{infoDiagram-6WML65LV-D4ph2sIH.js → infoDiagram-6WML65LV-BF_pbUnN.js} +1 -1
  82. package/dist-web/assets/{ishikawaDiagram-WSZJBQD7-DjKjbMm9.js → ishikawaDiagram-WSZJBQD7-Dedv4urS.js} +1 -1
  83. package/dist-web/assets/{journeyDiagram-NVQOT4AX-DttHDfPQ.js → journeyDiagram-NVQOT4AX-B1Ex3b6o.js} +1 -1
  84. package/dist-web/assets/{kanban-definition-27J2QSJJ-C5gB-W0d.js → kanban-definition-27J2QSJJ-BtCGliBH.js} +1 -1
  85. package/dist-web/assets/{linear-BRGe4-d6.js → linear-ByItc8Qr.js} +1 -1
  86. package/dist-web/assets/{mermaid.core-D-9Sx1No.js → mermaid.core-B1M2O7nN.js} +4 -4
  87. package/dist-web/assets/{mindmap-definition-FAOFIHXS-Cg_hoVIq.js → mindmap-definition-FAOFIHXS-D0UHKUyj.js} +1 -1
  88. package/dist-web/assets/{pegDiagram-VL7TDLO6-CSX9hitD.js → pegDiagram-VL7TDLO6-D7zYCVmG.js} +1 -1
  89. package/dist-web/assets/{pieDiagram-7S7Q4E2Y-0L0mxahZ.js → pieDiagram-7S7Q4E2Y-Bd0i9CHa.js} +1 -1
  90. package/dist-web/assets/{quadrantDiagram-CIZ2JOQS-U483z0er.js → quadrantDiagram-CIZ2JOQS-MG-Ug47d.js} +1 -1
  91. package/dist-web/assets/{railroadDiagram-AXF67PYL-D80a2_DJ.js → railroadDiagram-AXF67PYL-AZdl90Gj.js} +1 -1
  92. package/dist-web/assets/{requirementDiagram-LRYGKXZP-CeyHfq8t.js → requirementDiagram-LRYGKXZP-DjplF9kt.js} +1 -1
  93. package/dist-web/assets/{sankeyDiagram-W5VNT64P-CCISkkrR.js → sankeyDiagram-W5VNT64P-Bi9Zgfjs.js} +1 -1
  94. package/dist-web/assets/{sequenceDiagram-SI44F4Z6-jQfm2XS7.js → sequenceDiagram-SI44F4Z6-ChEpdCvb.js} +1 -1
  95. package/dist-web/assets/{sizeCapture-X5ZJPWSS-lRZsmCoS.js → sizeCapture-X5ZJPWSS-B5RFUdaa.js} +1 -1
  96. package/dist-web/assets/{stateDiagram-OKZ733FA-ClCgTdUm.js → stateDiagram-OKZ733FA-BkRblSKp.js} +1 -1
  97. package/dist-web/assets/stateDiagram-v2-UEYNNEHI-fVkQcACW.js +1 -0
  98. package/dist-web/assets/{swimlanes-SLNWSIFB-BCdOUN05.js → swimlanes-SLNWSIFB-DLXH6Zhc.js} +2 -2
  99. package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-Bg-BiZJQ.js +8 -0
  100. package/dist-web/assets/{timeline-definition-Z64GVDOM-DpWutSKa.js → timeline-definition-Z64GVDOM-C_N88siO.js} +1 -1
  101. package/dist-web/assets/{vennDiagram-T6HMQDX7-DVC5JVsc.js → vennDiagram-T6HMQDX7-DMsnvDt0.js} +1 -1
  102. package/dist-web/assets/{vue.runtime.esm-bundler-BwwRtQwQ.js → vue.runtime.esm-bundler-B-3uIy85.js} +1 -1
  103. package/dist-web/assets/{wardleyDiagram-T6FBY63Y-BK2mrq7L.js → wardleyDiagram-T6FBY63Y-C9SJIPzq.js} +1 -1
  104. package/dist-web/assets/{xychartDiagram-ELKLHX3M-DUJu9F7-.js → xychartDiagram-ELKLHX3M-DZLWA6Go.js} +1 -1
  105. package/dist-web/index.html +32 -32
  106. package/dist-web/vis/index.html.gz +0 -0
  107. package/package.json +1 -1
  108. package/scripts/fix-node-pty-perms.js +53 -53
  109. package/standard-skills/blun-app-design-system/SKILL.md +30 -30
  110. package/standard-skills/blun-app-design-system/provenance.json +17 -17
  111. package/standard-skills/blun-app-design-system/references/tokens.md +48 -48
  112. package/standard-skills/blun-app-design-system/scripts/check-blun-design.mjs +49 -49
  113. package/telegram-plugin/blun.plugin.json +20 -20
  114. package/telegram-plugin/commands/access.md +37 -37
  115. package/telegram-plugin/dist/bridge.mjs +81 -81
  116. package/telegram-plugin/dist/mcp-server.mjs +158 -158
  117. package/worker-host.mjs +932 -932
  118. package/dist-web/assets/DesignSystemView-6HgAUEmC.css +0 -1
  119. package/dist-web/assets/DesignSystemView-BbMOu-XO.js +0 -12
  120. package/dist-web/assets/channel-B2VOQOI-.js +0 -1
  121. package/dist-web/assets/classDiagram-JCYQIIEL-BA1TjAxj.js +0 -1
  122. package/dist-web/assets/classDiagram-v2-OCEON4UE-BA1TjAxj.js +0 -1
  123. package/dist-web/assets/stateDiagram-v2-UEYNNEHI-fi_lrTAx.js +0 -1
  124. package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-BUd_uYK3.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,115 +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) {
9
- const entries = [];
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);
17
- const stat = fs.lstatSync(absolutePath);
18
- if (stat.isSymbolicLink()) return false;
19
- if (stat.isDirectory()) {
20
- entries.push({ kind: 'directory', path: relativePath });
21
- if (!visit(absolutePath, relativePath)) return false;
22
- } else if (stat.isFile()) {
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;
29
- }
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 };
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
+ const entries = [];
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);
17
+ const stat = fs.lstatSync(absolutePath);
18
+ if (stat.isSymbolicLink()) return false;
19
+ if (stat.isDirectory()) {
20
+ entries.push({ kind: 'directory', path: relativePath });
21
+ if (!visit(absolutePath, relativePath)) return false;
22
+ } else if (stat.isFile()) {
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;
29
+ }
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 };