blun-king-cli 9.1.58 → 9.1.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LIESMICH.txt CHANGED
@@ -9,7 +9,7 @@ Installation
9
9
  ------------
10
10
  Die geprüfte Version exakt global installieren:
11
11
 
12
- npm install -g blun-king-cli@9.1.57
12
+ npm install -g blun-king-cli@9.1.61
13
13
 
14
14
  Start
15
15
  -----
package/README.md CHANGED
@@ -9,7 +9,7 @@ Voraussetzung ist Node.js 24.15 oder neuer. Die geprüfte Version wird exakt
9
9
  installiert:
10
10
 
11
11
  ```powershell
12
- npm install -g blun-king-cli@9.1.57
12
+ npm install -g blun-king-cli@9.1.61
13
13
  ```
14
14
 
15
15
  ## Reproduzierbares Staging und Packen
@@ -5,6 +5,7 @@ const path = require('node:path');
5
5
  const { pathToFileURL } = require('node:url');
6
6
 
7
7
  const { acquireSharedRuntimeLease } = require('./update-lease');
8
+ const { RUNTIME_READY_MESSAGE } = require('./running-update.cjs');
8
9
 
9
10
  const CORE_LOADED_MESSAGE = 'blun-core-bootstrap-loaded';
10
11
 
@@ -31,11 +32,7 @@ async function runCoreBootstrap() {
31
32
 
32
33
  if (process.connected) {
33
34
  try {
34
- process.send({ type: CORE_LOADED_MESSAGE }, () => {
35
- try {
36
- process.disconnect();
37
- } catch {}
38
- });
35
+ process.send({ type: CORE_LOADED_MESSAGE });
39
36
  } catch {}
40
37
  }
41
38
  }
@@ -49,4 +46,4 @@ if (require.main === module) {
49
46
  });
50
47
  }
51
48
 
52
- module.exports = { CORE_LOADED_MESSAGE, runCoreBootstrap };
49
+ module.exports = { CORE_LOADED_MESSAGE, RUNTIME_READY_MESSAGE, runCoreBootstrap };
@@ -10,6 +10,8 @@ const LAUNCHER_HELP_TEXT = [
10
10
  'Launchers:',
11
11
  ' blun Starts the CLI without automatically attaching Telegram.',
12
12
  ' king Starts the same CLI and automatically attaches the configured Telegram channel.',
13
+ ' king <profile> starts an isolated named King profile.',
14
+ ' king -c <profile> continues the last session in that profile.',
13
15
  ' Both use the same version, account, model, and commands.',
14
16
  ' blun tools list shows optional tools; enable writes only explicit choices.',
15
17
  '',
@@ -21,20 +21,38 @@ const {
21
21
  seedStandardDesignSkill,
22
22
  seedStandardTools,
23
23
  } = require('./standard-tools-bootstrap');
24
- const { CORE_LOADED_MESSAGE } = require('./core-bootstrap');
24
+ const { CORE_LOADED_MESSAGE, RUNTIME_READY_MESSAGE } = require('./core-bootstrap');
25
25
  const { prepareManagedNodeRuntime } = require('./node-runtime');
26
26
  const { repairConfiguredNativeModules } = require('./native-module-repair');
27
27
  const { acquireSharedRuntimeLease, tryAcquireUpdateLease } = require('./update-lease');
28
- const { runExplicitUpdate, runUpdateNotice } = require('./update-notice');
28
+ const { compareSemver, runExplicitUpdate, runUpdateNotice } = require('./update-notice');
29
+ const {
30
+ RUNNING_UPDATE_HANDOFF_EXIT_CODE,
31
+ RUNNING_UPDATE_HANDOFF_MESSAGE,
32
+ RUNNING_UPDATE_PREPARED_MESSAGE,
33
+ activateRuntime,
34
+ handoffRuntime,
35
+ prepareRunningUpdate,
36
+ readActiveRuntime,
37
+ resumeArgsForHandoff,
38
+ } = require('./running-update.cjs');
29
39
  const {
30
40
  ensurePrivateDirectory,
31
41
  securePrivateFile,
32
42
  writePrivateFile,
33
43
  } = require('./private-paths');
44
+ const {
45
+ parseProfileLaunchArgs,
46
+ resolveProfilePaths,
47
+ seedDefaultProfileFromLegacy,
48
+ } = require('./profile-runtime.cjs');
34
49
 
35
50
  const PKG = path.resolve(__dirname, '..');
36
- const ARGS = process.argv.slice(2);
51
+ const RAW_ARGS = process.argv.slice(2);
52
+ const PROFILE = parseProfileLaunchArgs(RAW_ARGS, launcherModeFromArgv(process.argv));
53
+ const ARGS = PROFILE.args;
37
54
  const CORE_LOAD_TIMEOUT_MS = 30_000;
55
+ const RUNTIME_READY_TIMEOUT_MS = 60_000;
38
56
 
39
57
  function normalizeWindowsPath(value) {
40
58
  return path.win32.normalize(value).replace(/\\+$/u, '').toLowerCase();
@@ -44,10 +62,24 @@ function resolveLauncherPrivatePaths(options = {}) {
44
62
  const env = options.env || process.env;
45
63
  const platform = options.platform || process.platform;
46
64
  const configuredHome = String(env.BLUN_HOME || '').trim();
65
+ const configuredSharedHome = String(env.BLUN_SHARED_HOME || '').trim();
66
+ const profileName = options.profileName || PROFILE.profileName;
47
67
  if (platform !== 'win32') {
48
68
  const userHome = (options.homedir || os.homedir)();
69
+ const sharedHome = configuredSharedHome
70
+ ? path.resolve(configuredSharedHome)
71
+ : configuredHome
72
+ ? path.resolve(configuredHome)
73
+ : path.join(userHome, '.blun');
74
+ const profilePaths = resolveProfilePaths(sharedHome, profileName);
75
+ if (configuredSharedHome && configuredHome
76
+ && path.resolve(configuredHome) !== path.resolve(profilePaths.home)) {
77
+ throw new Error(`BLUN_HOME passt nicht zum Profil ${profileName}: ${profilePaths.home}`);
78
+ }
49
79
  return {
50
- blunHome: configuredHome ? path.resolve(configuredHome) : path.join(userHome, '.blun'),
80
+ blunHome: profilePaths.home,
81
+ profilePaths,
82
+ sharedHome,
51
83
  userHome,
52
84
  };
53
85
  }
@@ -71,12 +103,26 @@ function resolveLauncherPrivatePaths(options = {}) {
71
103
  throw new Error('Das lokale Windows-Benutzerprofil ist kein kanonischer lokaler Pfad.');
72
104
  }
73
105
 
74
- const blunHome = path.win32.join(userHome, '.blun');
106
+ const sharedHome = path.win32.join(userHome, '.blun');
107
+ const profilePaths = resolveProfilePaths(sharedHome, profileName, path.win32);
108
+ if (configuredSharedHome
109
+ && normalizeWindowsPath(path.win32.resolve(configuredSharedHome)) !== normalizeWindowsPath(sharedHome)) {
110
+ throw new Error(`BLUN_SHARED_HOME muss unter Windows exakt im Windows-Benutzerprofil liegen: ${sharedHome}`);
111
+ }
112
+ const configuredHomeIsLegacyShared = !configuredSharedHome
113
+ && configuredHome
114
+ && normalizeWindowsPath(path.win32.resolve(configuredHome)) === normalizeWindowsPath(sharedHome);
75
115
  if (configuredHome
76
- && normalizeWindowsPath(path.win32.resolve(configuredHome)) !== normalizeWindowsPath(blunHome)) {
77
- throw new Error(`BLUN_HOME muss unter Windows exakt im Windows-Benutzerprofil liegen: ${blunHome}`);
116
+ && !configuredHomeIsLegacyShared
117
+ && normalizeWindowsPath(path.win32.resolve(configuredHome)) !== normalizeWindowsPath(profilePaths.home)) {
118
+ throw new Error(`BLUN_HOME passt nicht zum Profil ${profileName}: ${profilePaths.home}`);
78
119
  }
79
- return { blunHome, userHome };
120
+ return {
121
+ blunHome: profilePaths.home,
122
+ profilePaths,
123
+ sharedHome,
124
+ userHome,
125
+ };
80
126
  }
81
127
 
82
128
  function readPackageVersion() {
@@ -95,10 +141,11 @@ function shouldDetachProtectedCore(options = {}) {
95
141
  return platform !== 'win32' || stdinIsTTY !== true;
96
142
  }
97
143
 
98
- function spawnProtectedCore(args, env, cwd) {
144
+ function spawnProtectedCore(args, env, cwd, options = {}) {
145
+ const packageRoot = path.resolve(options.packageRoot || PKG);
99
146
  const child = spawn(
100
147
  process.execPath,
101
- [path.join(PKG, 'bin', 'core-bootstrap.js'), ...args],
148
+ [path.join(packageRoot, 'bin', 'core-bootstrap.js'), ...args],
102
149
  {
103
150
  cwd,
104
151
  detached: shouldDetachProtectedCore(),
@@ -108,8 +155,10 @@ function spawnProtectedCore(args, env, cwd) {
108
155
  },
109
156
  );
110
157
  let loadedSettled = false;
158
+ let readySettled = false;
111
159
  let completedSettled = false;
112
160
  let resolveLoaded;
161
+ let resolveReady;
113
162
  let resolveCompleted;
114
163
  const loadTimer = setTimeout(() => {
115
164
  try {
@@ -117,6 +166,7 @@ function spawnProtectedCore(args, env, cwd) {
117
166
  } catch {}
118
167
  }, CORE_LOAD_TIMEOUT_MS);
119
168
  const loaded = new Promise((resolve) => { resolveLoaded = resolve; });
169
+ const ready = new Promise((resolve) => { resolveReady = resolve; });
120
170
  const completed = new Promise((resolve) => { resolveCompleted = resolve; });
121
171
  const settleLoaded = (value) => {
122
172
  if (loadedSettled) return;
@@ -124,11 +174,17 @@ function spawnProtectedCore(args, env, cwd) {
124
174
  clearTimeout(loadTimer);
125
175
  resolveLoaded(value);
126
176
  };
177
+ const settleReady = (value) => {
178
+ if (readySettled) return;
179
+ readySettled = true;
180
+ resolveReady(value);
181
+ };
127
182
  const settleCompleted = (result) => {
128
183
  if (completedSettled) return;
129
184
  completedSettled = true;
130
185
  for (const [signal, handler] of signalHandlers) process.removeListener(signal, handler);
131
186
  settleLoaded(false);
187
+ settleReady(false);
132
188
  resolveCompleted(result);
133
189
  };
134
190
  const signalHandlers = new Map();
@@ -142,23 +198,93 @@ function spawnProtectedCore(args, env, cwd) {
142
198
  process.once(signal, handler);
143
199
  }
144
200
  child.on('message', (message) => {
145
- if (message?.type !== CORE_LOADED_MESSAGE) return;
146
- settleLoaded(true);
147
- try {
148
- child.disconnect();
149
- } catch {}
201
+ if (message?.type === CORE_LOADED_MESSAGE) settleLoaded(true);
202
+ if (message?.type === RUNTIME_READY_MESSAGE) settleReady(true);
203
+ options.onMessage?.(message, child);
150
204
  });
151
205
  child.once('error', (error) => settleCompleted({ error }));
152
206
  child.once('exit', (code, signal) => settleCompleted({ code, signal }));
153
- return { child, completed, loaded };
207
+ return { child, completed, loaded, packageRoot, ready };
208
+ }
209
+
210
+ async function waitForRuntimeReady(core, timeoutMs = RUNTIME_READY_TIMEOUT_MS) {
211
+ let timer;
212
+ const timedOut = new Promise((resolve) => {
213
+ timer = setTimeout(() => resolve(false), timeoutMs);
214
+ timer.unref?.();
215
+ });
216
+ try {
217
+ return await Promise.race([core.ready, timedOut]);
218
+ } finally {
219
+ clearTimeout(timer);
220
+ }
154
221
  }
155
222
 
156
- async function superviseProtectedCore(args, env, cwd, releaseNotice) {
157
- const core = spawnProtectedCore(args, env, cwd);
223
+ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {}) {
224
+ const packageRoot = path.resolve(options.packageRoot || PKG);
225
+ let preparedTarget;
226
+ let handoffSessionId;
227
+ let updateStarted = false;
228
+ const startPreparation = (child) => {
229
+ if (updateStarted || options.runningUpdate === false) return;
230
+ updateStarted = true;
231
+ const sharedHome = env.BLUN_SHARED_HOME;
232
+ if (typeof sharedHome !== 'string' || sharedHome.length === 0) return;
233
+ Promise.resolve((options.prepareRunningUpdate || prepareRunningUpdate)({
234
+ currentVersion: JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')).version,
235
+ sharedHome,
236
+ env,
237
+ })).then((target) => {
238
+ if (target === null || child.connected !== true) return;
239
+ preparedTarget = target;
240
+ child.send({ type: RUNNING_UPDATE_PREPARED_MESSAGE, version: target.version });
241
+ }).catch((error) => {
242
+ options.onRunningUpdateError?.(error);
243
+ });
244
+ };
245
+ const core = spawnProtectedCore(args, env, cwd, {
246
+ packageRoot,
247
+ onMessage(message, child) {
248
+ if (message?.type === RUNTIME_READY_MESSAGE) startPreparation(child);
249
+ if (message?.type === RUNNING_UPDATE_HANDOFF_MESSAGE
250
+ && typeof message.sessionId === 'string'
251
+ && message.sessionId.length > 0) {
252
+ handoffSessionId = message.sessionId;
253
+ }
254
+ },
255
+ });
158
256
  const loaded = await core.loaded;
159
257
  await releaseNotice();
160
258
  const result = await core.completed;
161
259
  if (result.error) throw result.error;
260
+ if (result.code === RUNNING_UPDATE_HANDOFF_EXIT_CODE
261
+ && preparedTarget !== undefined
262
+ && handoffSessionId !== undefined) {
263
+ const handoff = await handoffRuntime({
264
+ target: preparedTarget,
265
+ previous: { packageRoot, version: readPackageVersion() },
266
+ sessionId: handoffSessionId,
267
+ args,
268
+ probeTarget: options.probeRunningUpdate || (() => true),
269
+ stopOld: async () => {},
270
+ startCore: async ({ packageRoot: nextRoot, args: nextArgs }) => {
271
+ const nextCore = spawnProtectedCore(nextArgs, env, cwd, { packageRoot: nextRoot });
272
+ const nextLoaded = await nextCore.loaded;
273
+ const ready = nextLoaded && await waitForRuntimeReady(nextCore, options.readyTimeoutMs);
274
+ if (!ready) {
275
+ try { nextCore.child.kill(); } catch {}
276
+ await nextCore.completed;
277
+ }
278
+ return { ...nextCore, ready };
279
+ },
280
+ activateTarget: (target) => (options.activateRuntime || activateRuntime)(env.BLUN_SHARED_HOME, target),
281
+ });
282
+ const resumed = handoff.core;
283
+ if (resumed === undefined || resumed.ready !== true) return 1;
284
+ const resumedResult = await resumed.completed;
285
+ if (resumedResult.error) throw resumedResult.error;
286
+ return exitCodeForChild(resumedResult, true);
287
+ }
162
288
  return exitCodeForChild(result, loaded);
163
289
  }
164
290
 
@@ -211,8 +337,40 @@ function spawnManagedLauncher(binary, cwd) {
211
337
  });
212
338
  }
213
339
 
340
+ function spawnActiveLauncher(packageRoot, cwd) {
341
+ const entryName = path.basename(process.argv[1] || 'king.js').toLowerCase() === 'blun.js'
342
+ ? 'blun.js'
343
+ : 'king.js';
344
+ return new Promise((resolve, reject) => {
345
+ const child = spawn(process.execPath, [path.join(packageRoot, 'bin', entryName), ...RAW_ARGS], {
346
+ cwd,
347
+ env: { ...process.env, BLUN_ACTIVE_RUNTIME_ROOT: packageRoot },
348
+ stdio: 'inherit',
349
+ windowsHide: true,
350
+ });
351
+ child.once('error', reject);
352
+ child.once('exit', (code, signal) => {
353
+ if (Number.isInteger(code)) resolve(code);
354
+ else if (signal === 'SIGINT') resolve(130);
355
+ else if (signal === 'SIGTERM') resolve(143);
356
+ else resolve(1);
357
+ });
358
+ });
359
+ }
360
+
214
361
  async function runLauncher(options = {}) {
215
362
  const callerCwd = process.cwd();
363
+ if (options.skipActiveRuntime !== true) {
364
+ const activeHome = resolveLauncherPrivatePaths().sharedHome;
365
+ const activeRuntime = readActiveRuntime(activeHome);
366
+ const currentVersion = readPackageVersion();
367
+ if (activeRuntime !== null
368
+ && path.resolve(activeRuntime.packageRoot) !== PKG
369
+ && compareSemver(currentVersion, activeRuntime.version) === -1) {
370
+ process.exitCode = await spawnActiveLauncher(activeRuntime.packageRoot, callerCwd);
371
+ return;
372
+ }
373
+ }
216
374
  const mode = options.mode || launcherModeFromArgv(process.argv);
217
375
  const explicitUpdateRequest = isNpmPackageUpdateRequest(ARGS);
218
376
  const explicitToolsRequest = String(ARGS[0] || '').toLowerCase() === 'tools';
@@ -271,12 +429,12 @@ async function runLauncher(options = {}) {
271
429
  if (mcpNames.length > 0) {
272
430
  seedStandardTools({
273
431
  packageRoot: PKG,
274
- blunDir: privatePaths.blunHome,
432
+ blunDir: privatePaths.profilePaths.home,
275
433
  names: mcpNames,
276
434
  });
277
435
  }
278
436
  if (selected.includes('telegram')) {
279
- installTelegramForProfile(PKG, privatePaths.blunHome);
437
+ installTelegramForProfile(PKG, privatePaths.profilePaths.home);
280
438
  }
281
439
  process.stdout.write(`${selected.join('\n')}\n`);
282
440
  return;
@@ -298,7 +456,7 @@ async function runLauncher(options = {}) {
298
456
  stdin: process.stdin,
299
457
  stdout: process.stdout,
300
458
  stderr: process.stderr,
301
- blunDir: privatePaths.blunHome,
459
+ blunDir: privatePaths.sharedHome,
302
460
  packageRoot: PKG,
303
461
  noticeLease,
304
462
  request: ARGS[0],
@@ -307,13 +465,13 @@ async function runLauncher(options = {}) {
307
465
  return;
308
466
  }
309
467
 
310
- let privatePaths;
468
+ let privatePathsCache;
311
469
  const getPrivatePaths = () => {
312
- privatePaths ||= resolveLauncherPrivatePaths();
313
- return privatePaths;
470
+ privatePathsCache ||= resolveLauncherPrivatePaths();
471
+ return privatePathsCache;
314
472
  };
315
473
  const nodeRuntime = await prepareManagedNodeRuntime({
316
- getBlunDir: () => getPrivatePaths().blunHome,
474
+ getBlunDir: () => getPrivatePaths().sharedHome,
317
475
  });
318
476
  if (nodeRuntime.kind === 'failed') {
319
477
  process.stderr.write(`${nodeRuntime.message}\n`);
@@ -332,12 +490,24 @@ async function runLauncher(options = {}) {
332
490
  const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
333
491
  const informationalEnv = { ...process.env };
334
492
  delete informationalEnv.BLUN_HOME;
335
- env.BLUN_HOME = resolveLauncherPrivatePaths({ env: informationalEnv }).blunHome;
493
+ delete informationalEnv.BLUN_SHARED_HOME;
494
+ const privatePaths = resolveLauncherPrivatePaths({ env: informationalEnv });
495
+ const profilePaths = privatePaths.profilePaths;
496
+ env.BLUN_HOME = profilePaths.home;
497
+ env.BLUN_SHARED_HOME = privatePaths.sharedHome;
498
+ env.BLUN_LOG_HOME = privatePaths.sharedHome;
499
+ env.BLUN_PROFILE = PROFILE.profileName;
336
500
  process.exitCode = await superviseProtectedCore(ARGS, env, callerCwd, releaseNotice);
337
501
  return;
338
502
  }
339
503
 
340
- const blunDir = getPrivatePaths().blunHome;
504
+ const privatePaths = getPrivatePaths();
505
+ const profilePaths = privatePaths.profilePaths;
506
+ ensurePrivateDirectory(privatePaths.sharedHome);
507
+ if (PROFILE.profileName === 'default') {
508
+ seedDefaultProfileFromLegacy(privatePaths.sharedHome);
509
+ }
510
+ const blunDir = profilePaths.home;
341
511
  ensurePrivateDirectory(blunDir);
342
512
 
343
513
  // --- 1. Oeffentlicher npm-Update-Dialog. -------------------------------
@@ -350,7 +520,7 @@ async function runLauncher(options = {}) {
350
520
  stdin: process.stdin,
351
521
  stdout: process.stdout,
352
522
  stderr: process.stderr,
353
- blunDir,
523
+ blunDir: privatePaths.sharedHome,
354
524
  packageRoot: PKG,
355
525
  noticeLease,
356
526
  });
@@ -382,7 +552,7 @@ async function runLauncher(options = {}) {
382
552
  });
383
553
 
384
554
  // --- 4. Eigenes BLUN-Grunddesign als einzigen Standard-Skill installieren
385
- seedStandardDesignSkill({ packageRoot: PKG, blunDir });
555
+ seedStandardDesignSkill({ packageRoot: PKG, blunDir: privatePaths.sharedHome });
386
556
 
387
557
  // --- 5. Telegram-Plugin + Token-Vorlage (nur King) -----------------------
388
558
  if (mode === LAUNCHER_MODES.KING) {
@@ -402,7 +572,10 @@ async function runLauncher(options = {}) {
402
572
 
403
573
  // --- 6. Start -----------------------------------------------------------
404
574
  const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
405
- env.BLUN_HOME = blunDir;
575
+ env.BLUN_HOME = profilePaths.home;
576
+ env.BLUN_SHARED_HOME = privatePaths.sharedHome;
577
+ env.BLUN_LOG_HOME = privatePaths.sharedHome;
578
+ env.BLUN_PROFILE = PROFILE.profileName;
406
579
  env.BLUN_MODEL_MAX_COMPLETION_TOKENS = env.BLUN_MODEL_MAX_COMPLETION_TOKENS || '32768';
407
580
  process.exitCode = await superviseProtectedCore(
408
581
  ARGS,