blun-king-cli 9.0.0 → 9.0.1

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 (55) hide show
  1. package/LIESMICH.txt +1 -7
  2. package/README.md +4 -16
  3. package/bin/blun.js +248 -160
  4. package/bin/core-bootstrap.js +47 -0
  5. package/bin/king.js +277 -1
  6. package/bin/launcher-mode.js +2 -1
  7. package/bin/launcher-runtime.js +221 -0
  8. package/bin/plugin-bootstrap.js +0 -0
  9. package/bin/private-paths.js +0 -0
  10. package/bin/update-lease.js +399 -0
  11. package/bin/update-notice.js +1094 -0
  12. package/blun.mjs +4060 -6667
  13. package/package.json +3 -10
  14. package/skills/screenshot-lesen/SKILL.md +0 -1
  15. package/skills/web-lesen/SKILL.md +0 -1
  16. package/telegram-plugin/dist/bridge.mjs +1 -21
  17. package/mnemo/access_routes.js +0 -692
  18. package/mnemo/agent_governance.js +0 -4242
  19. package/mnemo/agent_mail.js +0 -901
  20. package/mnemo/bootstrap_auto.js +0 -137
  21. package/mnemo/brief_coordination.js +0 -226
  22. package/mnemo/code_read_tools.js +0 -375
  23. package/mnemo/context_preview_tools.js +0 -603
  24. package/mnemo/embeddings.js +0 -66
  25. package/mnemo/external_repo_ops.js +0 -575
  26. package/mnemo/facts/example-project-rules.json +0 -90
  27. package/mnemo/facts/example.json +0 -34
  28. package/mnemo/identity_schema.sql +0 -139
  29. package/mnemo/journal_schema.js +0 -561
  30. package/mnemo/loop_doctor_tools.js +0 -661
  31. package/mnemo/mail_secret_refs.js +0 -150
  32. package/mnemo/mcp.js +0 -9309
  33. package/mnemo/memory_consolidation.js +0 -1914
  34. package/mnemo/memory_health_tools.js +0 -165
  35. package/mnemo/package.json +0 -79
  36. package/mnemo/protected_scope_gate.js +0 -627
  37. package/mnemo/resource_access_control.js +0 -684
  38. package/mnemo/runtime_governance.js +0 -1256
  39. package/mnemo/runtime_turn_gate.js +0 -862
  40. package/mnemo/sandbox.js +0 -143
  41. package/mnemo/schema.sql +0 -389
  42. package/mnemo/shared_utils.js +0 -763
  43. package/mnemo/skills/agent-auto-resume/SKILL.md +0 -56
  44. package/mnemo/skills/agent_hand/SKILL.md +0 -43
  45. package/mnemo/skills/agent_hand/run.js +0 -63
  46. package/mnemo/skills/book_flight/SKILL.md +0 -34
  47. package/mnemo/skills/external_repo_review/SKILL.md +0 -43
  48. package/mnemo/skills/external_repo_review/run.js +0 -73
  49. package/mnemo/skills/pay_invoice/SKILL.md +0 -34
  50. package/mnemo/team_quality_ops.js +0 -944
  51. package/mnemo/timeline_report_tools.js +0 -810
  52. package/mnemo/write_gate_risk.js +0 -80
  53. package/mnemo/writer_health.js +0 -152
  54. package/skills/doku-ingestion/SKILL.md +0 -48
  55. package/skills/doku-ingestion/ingest_docs.py +0 -133
package/LIESMICH.txt CHANGED
@@ -18,7 +18,7 @@ Start
18
18
  king Dieselbe Konsole mit Telegram
19
19
 
20
20
  Beim ersten Start in der Konsole /login eingeben. Der Browser öffnet den
21
- BLUN-OAuth-Login. Nach der Freigabe stehen King, Queen und Prince bereit.
21
+ BLUN-OAuth-Login.
22
22
  Ein API-Schlüssel wird weder abgefragt noch in die Konfiguration geschrieben.
23
23
 
24
24
  Telegram einrichten
@@ -30,9 +30,3 @@ Telegram einrichten
30
30
 
31
31
  3. `king` starten und den angezeigten Pairing-Code mit
32
32
  /telegram:access pair CODE bestätigen.
33
-
34
- Lokale Daten
35
- ------------
36
- Konfiguration, Anmeldedaten, Telegram-Zustand und Mnemo liegen unter ~/.blun.
37
- Die privaten Verzeichnisse und Dateien werden auf macOS mit restriktiven
38
- Dateirechten angelegt.
package/README.md CHANGED
@@ -9,26 +9,14 @@ Der Schritt baut nichts, installiert nichts und veröffentlicht nichts. Vorher m
9
9
  `apps/blun-king/dist/main.mjs`, `dist-web`, die Darwin-Natives sowie
10
10
  `plugins/telegram/dist` bereits frisch gebaut sein.
11
11
 
12
- Mnemo ist kein Teil dieses Repos. Seine Quelle muss deshalb immer explizit angegeben
13
- werden; ein benutzerspezifischer Pfad wird nie angenommen:
14
-
15
- ```text
16
- npm run stage:mac -- /pfad/zu/mnemo/packages/core
17
- npm run pack:mac -- /pfad/zu/mnemo/packages/core
18
- ```
19
-
20
- Alternativ kann `BLUN_MNEMO_SOURCE` gesetzt werden. Das Staging landet standardmäßig
12
+ Das Staging landet standardmäßig
21
13
  unter `.stage/package`, das Tarball unter `.stage/artifacts`.
22
14
  `BLUN_NPM_STAGE_DIR` und `BLUN_NPM_ARTIFACT_DIR` können beide Ziele überschreiben;
23
15
  beim direkten Skriptaufruf stehen zusätzlich `--output` und `--artifacts` bereit.
24
16
 
25
17
  Der Schritt validiert alle Pflichtartefakte vor dem Leeren des alten Stagings. Er
26
18
  kopiert nur die Paket-Hülle, `blun.mjs`, `dist-web`, `native`, Telegrams
27
- `dist`/Manifest/Commands, die Repo-Skills und die angegebene Mnemo-Runtime. Lokale
28
- Mnemo-Zustände und Geheimnisse (`mnemo.db*`, `.env*`, `facts`, `logs`, `media`,
29
- `sources`, `tenants`, `tmp`, `node_modules`) werden ausgeschlossen. Die
30
- Mnemo-Abhängigkeiten werden in das gestagte `package.json` übernommen; Konflikte
31
- brechen vor dem Packen ab.
19
+ `dist`/Manifest/Commands und die Repo-Skills.
32
20
 
33
21
  ## Startmodi
34
22
 
@@ -37,7 +25,7 @@ Konsole mit Telegram-Kanal, Pairing-Befehl und Telegram-Werkzeugen. Der
37
25
  Unterschied gilt nur für den laufenden Prozess; die gespeicherte
38
26
  Plugin-Konfiguration wird nicht umgeschrieben.
39
27
 
40
- Beim ersten Start werden das Telegram-Plugin, die lokale Mnemo-Runtime und die
41
- mitgelieferten Skills eingerichtet. Die Anmeldung erfolgt anschließend in der
28
+ Beim ersten Start werden das Telegram-Plugin und die mitgelieferten Skills
29
+ eingerichtet. Die Anmeldung erfolgt anschließend in der
42
30
  Konsole mit `/login` über den BLUN-OAuth-Server. Das Paket erzeugt keine
43
31
  statische Anbieter- oder API-Key-Konfiguration.
package/bin/blun.js CHANGED
@@ -1,192 +1,280 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
- /*
4
- * BLUN CLI Launcher (npm) Erststart richtet alles ein:
5
- * 1. King richtet das Telegram-Plugin im BLUN_HOME ein
6
- * 2. Eigene lokale Memory (mnemo, SQLite) unter ~/.blun/mnemo — kein Hub,
7
- * alles privat auf dem Rechner, ueberlebt Sessions und Updates.
8
- * Danach: TUI starten. Anmeldung und Modellkatalog kommen ueber BLUN OAuth.
9
- */
3
+
4
+ // This public entry must stay self-contained. Package-local modules are loaded
5
+ // only after the update owner proves that the package root is not being replaced.
6
+ const { createHash } = require('node:crypto');
10
7
  const fs = require('node:fs');
11
- const path = require('node:path');
8
+ const net = require('node:net');
12
9
  const os = require('node:os');
13
- const { spawnSync, spawn } = require('node:child_process');
14
- const {
15
- LAUNCHER_MODES,
16
- NPM_PACKAGE_UPDATE_COMMAND,
17
- createLauncherEnvironment,
18
- isNpmPackageUpdateRequest,
19
- launcherModeFromArgv,
20
- } = require('./launcher-mode');
21
- const { installManagedTelegramPlugin } = require('./plugin-bootstrap');
22
- const {
23
- ensurePrivateDirectory,
24
- securePrivateFile,
25
- writePrivateFile,
26
- } = require('./private-paths');
27
-
28
- const PKG = path.resolve(__dirname, '..');
29
- const HOME = os.homedir();
30
- const configuredBlunHome = String(process.env.BLUN_HOME || '').trim();
31
- const BLUN = configuredBlunHome ? path.resolve(configuredBlunHome) : path.join(HOME, '.blun');
32
- const MODE = launcherModeFromArgv(process.argv);
33
- const ARGS = process.argv.slice(2);
34
-
35
- if (ARGS.some((arg) => arg === '--version' || arg === '-V')) {
36
- const { version } = JSON.parse(fs.readFileSync(path.join(PKG, 'package.json'), 'utf8'));
37
- if (typeof version !== 'string' || version.length === 0) {
38
- throw new Error('Ungueltige Paketversion in package.json');
10
+ const path = require('node:path');
11
+
12
+ const LEASE_PROTOCOL_VERSION = 'BLUN_UPDATE_LEASE_V3';
13
+ const LEASE_PROBE_TIMEOUT_MS = 500;
14
+ const MAX_POSIX_SOCKET_PATH_BYTES = 96;
15
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
16
+
17
+ function deriveLeaseEndpoint(role) {
18
+ const realpath = fs.realpathSync.native || fs.realpathSync;
19
+ let identity = realpath(PACKAGE_ROOT);
20
+ if (!fs.statSync(identity).isDirectory()) throw new Error('INVALID_PACKAGE_ROOT');
21
+ identity = identity.replaceAll('\\', '/');
22
+ if (process.platform === 'win32') identity = identity.toLowerCase();
23
+ const digest = createHash('sha256')
24
+ .update(`blun-update-v3\0${role}\0${identity}`)
25
+ .digest('hex');
26
+ const magic = `${LEASE_PROTOCOL_VERSION} ${digest}\n`;
27
+ if (process.platform === 'win32') {
28
+ return {
29
+ magic,
30
+ path: `\\\\.\\pipe\\blun-update-v3-${digest.slice(0, 32)}`,
31
+ platform: 'win32',
32
+ };
33
+ }
34
+
35
+ const uid = typeof process.getuid === 'function' ? process.getuid() : undefined;
36
+ const directoryName = `blun-update-${uid ?? digest.slice(0, 12)}`;
37
+ let directory = path.posix.join(os.tmpdir().replaceAll('\\', '/'), directoryName);
38
+ let socketPath = path.posix.join(directory, `lease-${digest.slice(0, 24)}.sock`);
39
+ if (Buffer.byteLength(socketPath, 'utf8') > MAX_POSIX_SOCKET_PATH_BYTES) {
40
+ directory = path.posix.join('/tmp', directoryName);
41
+ socketPath = path.posix.join(directory, `lease-${digest.slice(0, 24)}.sock`);
39
42
  }
40
- process.stdout.write(`${version}\n`);
41
- process.exit(0);
43
+ if (Buffer.byteLength(socketPath, 'utf8') > MAX_POSIX_SOCKET_PATH_BYTES) {
44
+ throw new Error('UPDATE_LEASE_SOCKET_PATH_TOO_LONG');
45
+ }
46
+ return { directory, magic, path: socketPath, platform: process.platform };
42
47
  }
43
48
 
44
- if (isNpmPackageUpdateRequest(ARGS)) {
45
- process.stdout.write(`${NPM_PACKAGE_UPDATE_COMMAND}\n`);
46
- process.exit(0);
49
+ function ensurePrivateSocketDirectory(endpoint) {
50
+ if (endpoint.platform === 'win32') return;
51
+ fs.mkdirSync(endpoint.directory, { recursive: true, mode: 0o700 });
52
+ const stat = fs.lstatSync(endpoint.directory);
53
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
54
+ throw new Error('INVALID_UPDATE_LEASE_DIRECTORY');
55
+ }
56
+ if (typeof process.getuid === 'function'
57
+ && Number.isInteger(stat.uid)
58
+ && stat.uid !== process.getuid()) {
59
+ throw new Error('INVALID_UPDATE_LEASE_DIRECTORY_OWNER');
60
+ }
61
+ fs.chmodSync(endpoint.directory, 0o700);
47
62
  }
48
63
 
49
- /**
50
- * Prueft hoechstens einmal pro Tag, ob eine neuere Version von blun-king-cli
51
- * auf npm liegt, und zeigt einen dezenten Hinweis. Alle Fehler werden
52
- * verschluckt — der Start darf davon NIE abhaengen.
53
- */
54
- async function maybeShowUpdateHint(pkgRoot, blunDir) {
55
- try {
56
- const own = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf8'));
57
- const stamp = path.join(blunDir, 'update-check.json');
58
- let last = 0;
59
- try { last = JSON.parse(fs.readFileSync(stamp, 'utf8')).at || 0; } catch {}
60
- if (Date.now() - last < 24 * 60 * 60 * 1000) return;
61
-
62
- const latest = await new Promise((resolve) => {
63
- const req = require('node:https').get(
64
- 'https://registry.npmjs.org/blun-king-cli/latest',
65
- { timeout: 2500, headers: { accept: 'application/json' } },
66
- (res) => {
67
- let body = '';
68
- res.on('data', (c) => (body += c));
69
- res.on('end', () => {
70
- try { resolve(JSON.parse(body).version); } catch { resolve(null); }
71
- });
72
- },
73
- );
74
- req.on('error', () => resolve(null));
75
- req.on('timeout', () => { req.destroy(); resolve(null); });
64
+ function closeServer(server) {
65
+ return new Promise((resolve) => {
66
+ if (!server.listening) {
67
+ resolve();
68
+ return;
69
+ }
70
+ try {
71
+ server.close(() => resolve());
72
+ } catch {
73
+ resolve();
74
+ }
75
+ });
76
+ }
77
+
78
+ function listenForLease(endpoint) {
79
+ return new Promise((resolve, reject) => {
80
+ const server = net.createServer((socket) => {
81
+ socket.on('error', () => {});
82
+ socket.end(endpoint.magic);
76
83
  });
84
+ const onError = (error) => {
85
+ server.removeListener('error', onError);
86
+ reject(error);
87
+ };
88
+ server.once('error', onError);
89
+ server.listen({ path: endpoint.path, exclusive: true }, () => {
90
+ server.removeListener('error', onError);
91
+ resolve(server);
92
+ });
93
+ });
94
+ }
77
95
 
78
- try { fs.writeFileSync(stamp, JSON.stringify({ at: Date.now() })); } catch {}
79
- if (latest && latest !== own.version && isNewer(latest, own.version)) {
80
- console.log('');
81
- console.log(' Update verfuegbar: ' + own.version + ' -> ' + latest);
82
- console.log(' Aktualisieren mit: npm i -g blun-king-cli');
83
- console.log('');
96
+ function probeLease(endpoint) {
97
+ return new Promise((resolve) => {
98
+ let received = '';
99
+ let settled = false;
100
+ let socket;
101
+ const finish = (state) => {
102
+ if (settled) return;
103
+ settled = true;
104
+ clearTimeout(timer);
105
+ try {
106
+ socket?.destroy();
107
+ } catch {}
108
+ // oxlint-disable-next-line promise/no-multiple-resolved -- settled is the idempotence guard.
109
+ resolve(state);
110
+ };
111
+ const timer = setTimeout(() => finish('foreign'), LEASE_PROBE_TIMEOUT_MS);
112
+ timer.unref?.();
113
+ try {
114
+ socket = net.createConnection({ path: endpoint.path });
115
+ socket.setEncoding('utf8');
116
+ socket.on('data', (chunk) => {
117
+ received += String(chunk);
118
+ if (Buffer.byteLength(received, 'utf8') > Buffer.byteLength(endpoint.magic, 'utf8')) {
119
+ finish('foreign');
120
+ }
121
+ });
122
+ socket.once('end', () => finish(received === endpoint.magic ? 'owner' : 'foreign'));
123
+ socket.once('error', (error) => {
124
+ finish(error?.code === 'ECONNREFUSED' || error?.code === 'ENOENT'
125
+ ? 'stale'
126
+ : 'foreign');
127
+ });
128
+ socket.once('close', () => {
129
+ if (!settled) finish(received === endpoint.magic ? 'owner' : 'foreign');
130
+ });
131
+ } catch (error) {
132
+ finish(error?.code === 'ECONNREFUSED' || error?.code === 'ENOENT'
133
+ ? 'stale'
134
+ : 'foreign');
84
135
  }
85
- } catch {
86
- // Update-Check ist Kosmetik — niemals den Start blockieren.
136
+ });
137
+ }
138
+
139
+ function observeSocket(endpoint) {
140
+ if (endpoint.platform === 'win32') return { state: 'not_applicable' };
141
+ try {
142
+ const stat = fs.lstatSync(endpoint.path);
143
+ if (!stat.isSocket() || stat.isSymbolicLink()) return { state: 'unsafe' };
144
+ return { dev: stat.dev, ino: stat.ino, state: 'socket' };
145
+ } catch (error) {
146
+ return error?.code === 'ENOENT' ? { state: 'missing' } : { state: 'unsafe' };
87
147
  }
88
148
  }
89
149
 
90
- function isNewer(a, b) {
91
- const pa = String(a).split('.').map((n) => parseInt(n, 10) || 0);
92
- const pb = String(b).split('.').map((n) => parseInt(n, 10) || 0);
93
- for (let i = 0; i < 3; i++) {
94
- if ((pa[i] || 0) > (pb[i] || 0)) return true;
95
- if ((pa[i] || 0) < (pb[i] || 0)) return false;
150
+ function removeObservedSocket(endpoint, observed) {
151
+ if (endpoint.platform === 'win32') return false;
152
+ try {
153
+ const stat = fs.lstatSync(endpoint.path);
154
+ if (observed.state !== 'socket'
155
+ || !stat.isSocket()
156
+ || stat.isSymbolicLink()
157
+ || stat.dev !== observed.dev
158
+ || stat.ino !== observed.ino) {
159
+ return false;
160
+ }
161
+ fs.unlinkSync(endpoint.path);
162
+ return true;
163
+ } catch (error) {
164
+ return error?.code === 'ENOENT' && observed.state === 'missing';
96
165
  }
97
- return false;
98
166
  }
99
167
 
100
- (async () => {
101
- ensurePrivateDirectory(BLUN);
102
-
103
- // --- 1. Bestehende Konfiguration absichern -------------------------------
104
- // Ein frischer Start bleibt absichtlich ohne statischen Anbieter. /login
105
- // provisioniert King, Queen und Prince ueber den BLUN-OAuth-Server.
106
- const cfgFile = path.join(BLUN, 'config.toml');
107
- if (fs.existsSync(cfgFile)) securePrivateFile(cfgFile);
108
-
109
- // --- 2. Telegram-Plugin + Token-Vorlage (nur King) -----------------------
110
- if (MODE === LAUNCHER_MODES.KING) {
111
- installManagedTelegramPlugin({ packageRoot: PKG, blunDir: BLUN });
112
- const channelDir = path.join(BLUN, 'channels', 'telegram');
113
- fs.mkdirSync(channelDir, { recursive: true });
114
- const envFile = path.join(channelDir, '.env');
115
- if (!fs.existsSync(envFile)) {
116
- writePrivateFile(envFile, 'BLUN_TELEGRAM_BOT_TOKEN=HIER_DEIN_BOT_TOKEN_EINFUEGEN\n');
117
- } else {
118
- securePrivateFile(envFile);
168
+ function recordSocket(endpoint) {
169
+ if (endpoint.platform === 'win32') return undefined;
170
+ const stat = fs.lstatSync(endpoint.path);
171
+ if (!stat.isSocket() || stat.isSymbolicLink()) throw new Error('INVALID_UPDATE_LEASE_SOCKET');
172
+ fs.chmodSync(endpoint.path, 0o600);
173
+ return { dev: stat.dev, ino: stat.ino };
174
+ }
175
+
176
+ function removeOwnedSocket(endpoint, identity) {
177
+ if (endpoint.platform === 'win32' || !identity) return;
178
+ try {
179
+ const stat = fs.lstatSync(endpoint.path);
180
+ if (stat.isSocket()
181
+ && !stat.isSymbolicLink()
182
+ && stat.dev === identity.dev
183
+ && stat.ino === identity.ino) {
184
+ fs.unlinkSync(endpoint.path);
119
185
  }
186
+ } catch {}
187
+ }
188
+
189
+ async function acquireNoticeLease() {
190
+ let endpoint;
191
+ try {
192
+ endpoint = deriveLeaseEndpoint('notice');
193
+ ensurePrivateSocketDirectory(endpoint);
194
+ } catch {
195
+ return { acquired: false, reason: 'unavailable' };
120
196
  }
121
197
 
122
- // --- 2b. Built-in-Skills installieren (fehlende ergaenzen, nie
123
- // vorhandene ueberschreiben User-Anpassungen bleiben) -----------
124
- const skillsSrc = path.join(PKG, 'skills');
125
- const skillsDst = path.join(BLUN, 'skills');
126
- if (fs.existsSync(skillsSrc)) {
127
- fs.mkdirSync(skillsDst, { recursive: true });
128
- for (const entry of fs.readdirSync(skillsSrc)) {
129
- const dst = path.join(skillsDst, entry);
130
- if (!fs.existsSync(dst)) {
131
- fs.cpSync(path.join(skillsSrc, entry), dst, { recursive: true });
198
+ let server;
199
+ for (let attempt = 0; attempt < 2; attempt += 1) {
200
+ try {
201
+ server = await listenForLease(endpoint);
202
+ break;
203
+ } catch (error) {
204
+ if (error?.code !== 'EADDRINUSE') return { acquired: false, reason: 'unavailable' };
205
+ const observed = observeSocket(endpoint);
206
+ const state = await probeLease(endpoint);
207
+ if (state === 'owner') return { acquired: false, reason: 'busy' };
208
+ if (state !== 'stale' || attempt > 0) return { acquired: false, reason: 'unavailable' };
209
+ if (endpoint.platform === 'win32') continue;
210
+ if (!removeObservedSocket(endpoint, observed)) {
211
+ return { acquired: false, reason: 'unavailable' };
132
212
  }
133
213
  }
134
214
  }
215
+ if (!server) return { acquired: false, reason: 'unavailable' };
216
+
217
+ let identity;
218
+ try {
219
+ identity = recordSocket(endpoint);
220
+ } catch {
221
+ await closeServer(server);
222
+ return { acquired: false, reason: 'unavailable' };
223
+ }
135
224
 
136
- // --- 3. Eigene lokale Memory (einmalig anlegen) --------------------------
137
- const memHome = path.join(BLUN, 'mnemo');
138
- const factsDir = path.join(memHome, 'facts');
139
- fs.mkdirSync(factsDir, { recursive: true });
140
- const dbFile = path.join(memHome, 'mnemo.db');
141
- const agent = (process.env.BLUN_MNEMO_AGENT || os.userInfo().username || 'agent')
142
- .toLowerCase().replaceAll(/[^a-z0-9_-]/g, '-') || 'agent';
143
- if (!fs.existsSync(dbFile)) {
144
- const result = spawnSync(process.execPath, [path.join(PKG, 'mnemo', 'bootstrap_auto.js')], {
145
- env: {
146
- ...process.env,
147
- BLUN_HOME: BLUN,
148
- MNEMO_DB: dbFile,
149
- MNEMO_AGENT: agent,
150
- MNEMO_OWNER_NAME: agent,
151
- MNEMO_FACTS_DIR: factsDir,
152
- MNEMO_ENV_FILE: path.join(memHome, '.env.local'),
153
- MNEMO_HOOK_ENV_FILE: path.join(BLUN, '.mnemo-hook.env'),
154
- MNEMO_HUB_URL: '',
225
+ let owned = true;
226
+ let released = false;
227
+ server.on('error', () => { owned = false; });
228
+ server.on('close', () => { owned = false; });
229
+ return {
230
+ acquired: true,
231
+ lease: Object.freeze({
232
+ assertOwned() {
233
+ if (!owned || !server.listening) throw new Error('UPDATE_LEASE_LOST');
155
234
  },
156
- stdio: 'ignore',
235
+ async release() {
236
+ if (released) return;
237
+ released = true;
238
+ owned = false;
239
+ await closeServer(server);
240
+ removeOwnedSocket(endpoint, identity);
241
+ },
242
+ }),
243
+ };
244
+ }
245
+
246
+ async function installLeaseIsClear() {
247
+ try {
248
+ const endpoint = deriveLeaseEndpoint('install');
249
+ ensurePrivateSocketDirectory(endpoint);
250
+ return await probeLease(endpoint) === 'stale';
251
+ } catch {
252
+ return false;
253
+ }
254
+ }
255
+
256
+ async function main() {
257
+ const result = await acquireNoticeLease();
258
+ if (!result.acquired) return;
259
+ try {
260
+ if (!await installLeaseIsClear()) return;
261
+ result.lease.assertOwned();
262
+ const { runLauncher } = require('./launcher-runtime');
263
+ result.lease.assertOwned();
264
+ const filename = path.basename(__filename).toLowerCase();
265
+ await runLauncher({
266
+ mode: filename.startsWith('king') ? 'king' : 'blun',
267
+ noticeLease: result.lease,
157
268
  });
158
- if (result.status === 0 && fs.existsSync(dbFile)) {
159
- console.log(' Eigene Memory angelegt (Agent: ' + agent + ') — merkt sich alles, Sessions-uebergreifend.');
160
- } else {
161
- console.error(' Warnung: Memory konnte nicht angelegt werden — BLUN laeuft trotzdem, nur ohne Langzeitgedaechtnis.');
162
- }
269
+ } finally {
270
+ await result.lease.release();
163
271
  }
272
+ }
164
273
 
165
- // --- 3b. Update-Hinweis. Best-effort, blockiert NIE und prueft hoechstens
166
- // einmal pro Tag die neueste Version auf npm. ---------------------
167
- await maybeShowUpdateHint(PKG, BLUN);
168
-
169
- // --- 4. Start -------------------------------------------------------------
170
- const env = createLauncherEnvironment(process.env, MODE);
171
- env.BLUN_HOME = BLUN;
172
- env.BLUN_MODEL_MAX_COMPLETION_TOKENS = env.BLUN_MODEL_MAX_COMPLETION_TOKENS || '32768';
173
- env.BLUN_MNEMO_MCP = path.join(PKG, 'mnemo', 'mcp.js');
174
- env.BLUN_MNEMO_AGENT = agent;
175
- env.MNEMO_DB = dbFile;
176
- env.MNEMO_FACTS_DIR = factsDir;
177
- env.MNEMO_HUB_URL = '';
178
-
179
- const workspace = path.join(HOME, 'BLUN-Workspace');
180
- fs.mkdirSync(workspace, { recursive: true });
181
-
182
- const child = spawn(process.execPath, [path.join(PKG, 'blun.mjs'), '--yolo', ...ARGS], {
183
- cwd: workspace,
184
- env,
185
- stdio: 'inherit',
186
- });
187
- child.on('exit', (code) => process.exit(code ?? 0));
188
- })().catch((error) => {
274
+ // oxlint-disable-next-line unicorn/prefer-top-level-await -- Public npm bins are CommonJS.
275
+ void main().catch((error) => {
276
+ // oxlint-disable-next-line no-console -- Fatal launcher errors belong on stderr.
189
277
  console.error('BLUN Start fehlgeschlagen: '
190
278
  + (error && error.message ? error.message : String(error)));
191
- process.exit(1);
279
+ process.exitCode = 1;
192
280
  });
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const { pathToFileURL } = require('node:url');
5
+
6
+ const { tryAcquireUpdateLease } = require('./update-lease');
7
+
8
+ const CORE_LOADED_MESSAGE = 'blun-core-bootstrap-loaded';
9
+
10
+ async function runCoreBootstrap() {
11
+ const packageRoot = path.resolve(__dirname, '..');
12
+ const mainPath = path.join(packageRoot, 'blun.mjs');
13
+ const leaseResult = await tryAcquireUpdateLease({ packageRoot, role: 'install' });
14
+ if (!leaseResult.acquired) process.exit(75);
15
+
16
+ try {
17
+ leaseResult.lease.assertOwned();
18
+ process.argv = [process.execPath, mainPath, ...process.argv.slice(2)];
19
+ await import(pathToFileURL(mainPath).href);
20
+ leaseResult.lease.assertOwned();
21
+ } catch (error) {
22
+ await leaseResult.lease.release();
23
+ throw error;
24
+ }
25
+
26
+ // The bundled CLI loads package assets lazily after module evaluation. Keep
27
+ // the install lease for this process's lifetime, without keeping print/help
28
+ // commands alive solely because of the owner server.
29
+ leaseResult.lease.unref();
30
+
31
+ if (process.connected) {
32
+ try {
33
+ process.send({ type: CORE_LOADED_MESSAGE }, () => {
34
+ try {
35
+ process.disconnect();
36
+ } catch {}
37
+ });
38
+ } catch {}
39
+ }
40
+ }
41
+
42
+ if (require.main === module) {
43
+ // oxlint-disable-next-line unicorn/prefer-top-level-await -- Worker entry is CommonJS.
44
+ void runCoreBootstrap().catch(() => process.exit(1));
45
+ }
46
+
47
+ module.exports = { CORE_LOADED_MESSAGE, runCoreBootstrap };