blun-king-cli 9.1.608 → 9.1.610

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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # BLUN King Changelog
2
2
 
3
+ ## 9.1.610
4
+
5
+ - Mit `@all` erreichst du in einer Telegram-Gruppe alle angebundenen Kings. Nachrichten ohne passende Erwähnung bleiben vom Modellkontext ausgeschlossen, wenn die Erwähnungsregel aktiv ist.
6
+ - Konkrete Aufträge und Fragen bleiben beantwortbar; bloße Informationen und ausdrücklich bestätigungsfreie Nachrichten lösen keine unnötige Gruppenbestätigung aus.
7
+ - Telegram-`/`-Befehle werden vor normalen TUI-Nachrichten zugestellt und nicht in die Arbeitswarteschlange gelegt. Kann ein Fernbefehl während eines laufenden Zugs nicht sicher ausgeführt werden, erhältst du sofort eine Ablehnung und kannst ihn erneut senden; er läuft nicht später überraschend an.
8
+ - Ein leerer oder ausdrücklich stiller Headless-Zug sendet keine automatische Telegram-Antwort. Als eindeutiges Stille-Zeichen gilt `[[BLUN_TELEGRAM_SILENT]]` allein als Ausgabetext; die fünf beobachteten Stille-Formulierungen werden ebenfalls unterdrückt. Echte Antworten ohne `reply` bleiben zustellbar.
9
+ - Auf Linux ist das npm-Paket wieder installierbar. `king --profile serve` und `king serve` öffnen wieder das bestehende Profil; dessen Konfiguration bleibt getrennt, während vorhandene gemeinsame Zugangsdaten weiter genutzt werden.
10
+ - Diese Änderungen belegen weder eine kürzere Modellwartezeit noch geringeren Tokenverbrauch.
11
+
12
+ ## 9.1.609
13
+
14
+ - Open the existing effort picker with Ctrl+L while preserving the editor draft. Reuse the existing effort settings and model restrictions rather than adding a second configuration path.
15
+ - Preserve the existing busy-session rule: an effort change during active work is rejected, not queued for later. Ignore the shortcut during replay or editor takeover.
16
+ - These changes do not establish faster live model responses, lower billed token usage, or complete OpenClaw parity.
17
+
3
18
  ## 9.1.608
4
19
 
5
20
  - Apply Telegram mention admission before unaddressed group messages enter the session context; preserve the explicit opt-out.
package/LIESMICH.txt CHANGED
@@ -26,7 +26,7 @@ Installation
26
26
  ------------
27
27
  Die geprüfte Version exakt global installieren:
28
28
 
29
- npm install -g blun-king-cli@9.1.608
29
+ npm install -g blun-king-cli@9.1.610
30
30
 
31
31
  Start
32
32
  -----
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.608
12
+ npm install -g blun-king-cli@9.1.610
13
13
  ```
14
14
 
15
15
  ### Windows: Node.js, npm und Git Bash
@@ -33,29 +33,68 @@ const { unsupportedNodeMessage } = require('./node-version');
33
33
  const { repairConfiguredNativeModules } = require('./native-module-repair');
34
34
  const { acquireSharedRuntimeLease, tryAcquireUpdateLease } = require('./update-lease');
35
35
  const { runExplicitUpdate, runUpdateNotice } = require('./update-notice');
36
- const {
37
- ensurePrivateDirectory,
38
- securePrivateFile,
39
- writePrivateFile,
40
- } = require('./private-paths');
41
-
42
- const PKG = path.resolve(__dirname, '..');
43
- const ARGS = process.argv.slice(2);
36
+ const {
37
+ ensurePrivateDirectory,
38
+ securePrivateFile,
39
+ writePrivateFile,
40
+ } = require('./private-paths');
41
+ const { parseProfileLaunchArgs, resolveSelectedProfile } = require('./profile-launch.cjs');
42
+
43
+ const PKG = path.resolve(__dirname, '..');
44
+ const PROFILE = parseProfileLaunchArgs(process.argv.slice(2), launcherModeFromArgv(process.argv));
45
+ const ARGS = PROFILE.args;
44
46
  const CORE_LOAD_TIMEOUT_MS = 30_000;
45
47
 
46
- function normalizeWindowsPath(value) {
47
- return path.win32.normalize(value).replace(/\\+$/u, '').toLowerCase();
48
- }
48
+ function normalizeWindowsPath(value) {
49
+ return path.win32.normalize(value).replace(/\\+$/u, '').toLowerCase();
50
+ }
51
+
52
+ function runningTuiPid(blunDir, options = {}) {
53
+ const fsImpl = options.fsImpl || fs;
54
+ const kill = options.kill || process.kill.bind(process);
55
+ const now = options.now ?? Date.now();
56
+ const stateDir = path.join(blunDir, 'channels', 'telegram');
57
+ try {
58
+ const leaseFile = path.join(stateDir, 'tui.pid');
59
+ const owner = JSON.parse(fsImpl.readFileSync(path.join(stateDir, 'tui-owner.json'), 'utf8'));
60
+ const leasePid = Number(fsImpl.readFileSync(leaseFile, 'utf8').trim());
61
+ if (owner?.version !== 1 || !Number.isSafeInteger(owner.pid) || owner.pid <= 1
62
+ || owner.pid !== leasePid || now - fsImpl.statSync(leaseFile).mtimeMs > 90_000) return null;
63
+ kill(owner.pid, 0);
64
+ return owner.pid;
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+
70
+ function isInteractiveKingStart(args) {
71
+ return args.length === 0 || (args.length === 1 && args[0] === '-c');
72
+ }
49
73
 
50
- function resolveLauncherPrivatePaths(options = {}) {
51
- const env = options.env || process.env;
52
- const platform = options.platform || process.platform;
53
- const configuredHome = String(env.BLUN_HOME || '').trim();
54
- if (platform !== 'win32') {
55
- const userHome = (options.homedir || os.homedir)();
56
- return {
57
- blunHome: configuredHome ? path.resolve(configuredHome) : path.join(userHome, '.blun'),
58
- userHome,
74
+ function resolveLauncherPrivatePaths(options = {}) {
75
+ const env = options.env || process.env;
76
+ const platform = options.platform || process.platform;
77
+ const configuredHome = String(env.BLUN_HOME || '').trim();
78
+ const selectedProfile = options.profileName ?? PROFILE.name;
79
+ if (platform !== 'win32') {
80
+ const posix = path.posix;
81
+ const userHome = (options.homedir || os.homedir)();
82
+ if (selectedProfile !== undefined) {
83
+ const configuredShared = String(env.BLUN_SHARED_HOME || '').trim();
84
+ const sharedHome = configuredShared
85
+ ? posix.resolve(configuredShared)
86
+ : configuredHome && posix.basename(posix.dirname(configuredHome)) === 'profile'
87
+ ? posix.resolve(configuredHome, '..', '..')
88
+ : configuredHome ? posix.resolve(configuredHome) : posix.join(userHome, '.blun');
89
+ const blunHome = resolveSelectedProfile(sharedHome, selectedProfile, posix);
90
+ if (configuredShared && configuredHome && posix.resolve(configuredHome) !== blunHome) {
91
+ throw new Error(`BLUN_HOME passt nicht zum Profil ${selectedProfile}: ${blunHome}`);
92
+ }
93
+ return { blunHome, sharedHome, userHome };
94
+ }
95
+ return {
96
+ blunHome: configuredHome ? path.resolve(configuredHome) : path.join(userHome, '.blun'),
97
+ userHome,
59
98
  };
60
99
  }
61
100
 
@@ -78,8 +117,20 @@ function resolveLauncherPrivatePaths(options = {}) {
78
117
  throw new Error('Das lokale Windows-Benutzerprofil ist kein kanonischer lokaler Pfad.');
79
118
  }
80
119
 
81
- const blunHome = path.win32.join(userHome, '.blun');
82
- if (configuredHome
120
+ const blunHome = path.win32.join(userHome, '.blun');
121
+ if (selectedProfile !== undefined) {
122
+ const profileHome = resolveSelectedProfile(blunHome, selectedProfile, path.win32);
123
+ const configuredShared = String(env.BLUN_SHARED_HOME || '').trim();
124
+ if (configuredShared && normalizeWindowsPath(path.win32.resolve(configuredShared)) !== normalizeWindowsPath(blunHome)) {
125
+ throw new Error(`BLUN_SHARED_HOME muss unter Windows exakt im Windows-Benutzerprofil liegen: ${blunHome}`);
126
+ }
127
+ if (configuredHome && normalizeWindowsPath(path.win32.resolve(configuredHome)) !== normalizeWindowsPath(profileHome)
128
+ && normalizeWindowsPath(path.win32.resolve(configuredHome)) !== normalizeWindowsPath(blunHome)) {
129
+ throw new Error(`BLUN_HOME passt nicht zum Profil ${selectedProfile}: ${profileHome}`);
130
+ }
131
+ return { blunHome: profileHome, sharedHome: blunHome, userHome };
132
+ }
133
+ if (configuredHome
83
134
  && normalizeWindowsPath(path.win32.resolve(configuredHome)) !== normalizeWindowsPath(blunHome)) {
84
135
  throw new Error(`BLUN_HOME muss unter Windows exakt im Windows-Benutzerprofil liegen: ${blunHome}`);
85
136
  }
@@ -367,18 +418,29 @@ async function runLauncher(options = {}) {
367
418
  }
368
419
  process.stdout.write(launcherHelpText());
369
420
  const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
370
- const informationalEnv = { ...process.env };
371
- delete informationalEnv.BLUN_HOME;
372
- env.BLUN_HOME = resolveLauncherPrivatePaths({ env: informationalEnv }).blunHome;
421
+ const informationalEnv = { ...process.env };
422
+ delete informationalEnv.BLUN_HOME;
423
+ const paths = resolveLauncherPrivatePaths({ env: informationalEnv });
424
+ env.BLUN_HOME = paths.blunHome;
425
+ if (PROFILE.name !== undefined) {
426
+ env.BLUN_SHARED_HOME = paths.sharedHome;
427
+ env.BLUN_PROFILE = PROFILE.name;
428
+ }
373
429
  process.exitCode = await runInformationalCore(ARGS, env, callerCwd, releaseNotice);
374
430
  return;
375
431
  }
376
432
 
377
- let privatePaths;
378
- const getPrivatePaths = () => {
379
- privatePaths ||= resolveLauncherPrivatePaths();
380
- return privatePaths;
381
- };
433
+ let privatePaths;
434
+ const getPrivatePaths = () => {
435
+ if (!privatePaths) {
436
+ const resolved = resolveLauncherPrivatePaths();
437
+ if (PROFILE.implicit && !fs.existsSync(resolved.blunHome)) {
438
+ throw new Error(`Profil ${PROFILE.name} gibt es nicht. Anlegen mit king --profile ${PROFILE.name}.`);
439
+ }
440
+ privatePaths = resolved;
441
+ }
442
+ return privatePaths;
443
+ };
382
444
  const nodeRuntime = await prepareManagedNodeRuntime({
383
445
  getBlunDir: () => getPrivatePaths().blunHome,
384
446
  });
@@ -397,15 +459,28 @@ async function runLauncher(options = {}) {
397
459
  if (ARGS.some((arg) => arg === '--help' || arg === '-h')) {
398
460
  process.stdout.write(launcherHelpText());
399
461
  const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
400
- const informationalEnv = { ...process.env };
401
- delete informationalEnv.BLUN_HOME;
402
- env.BLUN_HOME = resolveLauncherPrivatePaths({ env: informationalEnv }).blunHome;
462
+ const informationalEnv = { ...process.env };
463
+ delete informationalEnv.BLUN_HOME;
464
+ const paths = resolveLauncherPrivatePaths({ env: informationalEnv });
465
+ env.BLUN_HOME = paths.blunHome;
466
+ if (PROFILE.name !== undefined) {
467
+ env.BLUN_SHARED_HOME = paths.sharedHome;
468
+ env.BLUN_PROFILE = PROFILE.name;
469
+ }
403
470
  process.exitCode = await superviseProtectedCore(ARGS, env, callerCwd, releaseNotice);
404
471
  return;
405
472
  }
406
473
 
407
- const blunDir = getPrivatePaths().blunHome;
408
- ensurePrivateDirectory(blunDir);
474
+ const blunDir = getPrivatePaths().blunHome;
475
+ if (mode === LAUNCHER_MODES.KING && isInteractiveKingStart(ARGS)) {
476
+ const activePid = runningTuiPid(blunDir);
477
+ if (activePid !== null) {
478
+ process.stderr.write(`King läuft bereits (PID ${activePid}).\n`);
479
+ process.exitCode = 1;
480
+ return;
481
+ }
482
+ }
483
+ ensurePrivateDirectory(blunDir);
409
484
 
410
485
  // --- 1. Oeffentlicher npm-Update-Dialog. -------------------------------
411
486
  const updateResult = options.skipUpdateNotice === true
@@ -470,8 +545,12 @@ async function runLauncher(options = {}) {
470
545
  }
471
546
 
472
547
  // --- 6. Start -----------------------------------------------------------
473
- const env = createLauncherEnvironment(process.env, mode, readPackageVersion(), blunDir);
474
- env.BLUN_HOME = blunDir;
548
+ const env = createLauncherEnvironment(process.env, mode, readPackageVersion(), blunDir);
549
+ env.BLUN_HOME = blunDir;
550
+ if (PROFILE.name !== undefined) {
551
+ env.BLUN_SHARED_HOME = getPrivatePaths().sharedHome;
552
+ env.BLUN_PROFILE = PROFILE.name;
553
+ }
475
554
  process.exitCode = await superviseProtectedCore(
476
555
  ARGS,
477
556
  env,
@@ -483,8 +562,10 @@ async function runLauncher(options = {}) {
483
562
  }
484
563
  }
485
564
 
486
- module.exports = {
487
- resolveLauncherPrivatePaths,
565
+ module.exports = {
566
+ resolveLauncherPrivatePaths,
567
+ isInteractiveKingStart,
568
+ runningTuiPid,
488
569
  runLauncher,
489
570
  shouldDetachProtectedCore,
490
571
  spawnManagedLauncher,
@@ -44,7 +44,10 @@ public static class BlunPrivateAclNative
44
44
  private const uint FILE_DIRECTORY_FILE = 0x00000001;
45
45
  private const uint FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020;
46
46
  private const uint FILE_OPEN_REPARSE_POINT = 0x00200000;
47
- private const uint FILE_OPEN_IF = 3;
47
+ private const uint FILE_OPEN_IF = 3;
48
+ private const uint FILE_SHARE_READ = 0x00000001;
49
+ private const uint FILE_SHARE_WRITE = 0x00000002;
50
+ private const uint FILE_SHARE_DELETE = 0x00000004;
48
51
  private const uint OBJ_CASE_INSENSITIVE = 0x00000040;
49
52
  private const uint SE_FILE_OBJECT = 1;
50
53
  private const uint OWNER_SECURITY_INFORMATION = 0x00000001;
@@ -602,17 +605,30 @@ public static class BlunPrivateAclNative
602
605
  IoStatusBlock ioStatus;
603
606
  int status = NtCreateFile(
604
607
  out rawHandle,
605
- GENERIC_READ | READ_CONTROL | WRITE_DAC | DELETE | SYNCHRONIZE,
608
+ GENERIC_READ | READ_CONTROL | WRITE_DAC | DELETE | SYNCHRONIZE,
606
609
  ref attributes,
607
610
  out ioStatus,
608
611
  IntPtr.Zero,
609
612
  0,
610
- 0,
611
- FILE_OPEN_IF,
613
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
614
+ FILE_OPEN_IF,
612
615
  FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT,
613
- IntPtr.Zero,
614
- 0);
615
- if (status < 0)
616
+ IntPtr.Zero,
617
+ 0);
618
+ if (status < 0 && RtlNtStatusToDosError(status) == 32)
619
+ status = NtCreateFile(
620
+ out rawHandle,
621
+ GENERIC_READ | READ_CONTROL | WRITE_DAC | SYNCHRONIZE,
622
+ ref attributes,
623
+ out ioStatus,
624
+ IntPtr.Zero,
625
+ 0,
626
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
627
+ FILE_OPEN_IF,
628
+ FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT,
629
+ IntPtr.Zero,
630
+ 0);
631
+ if (status < 0)
616
632
  throw new Win32Exception((int)RtlNtStatusToDosError(status));
617
633
  return new SafeFileHandle(rawHandle, true);
618
634
  }
@@ -1058,8 +1074,8 @@ public static class BlunPrivateAclNative
1058
1074
  }
1059
1075
  }`;
1060
1076
 
1061
- const WINDOWS_ACL_CSHARP_GZIP = gzipSync(
1062
- Buffer.from(WINDOWS_ACL_CSHARP),
1077
+ const WINDOWS_ACL_CSHARP_GZIP = gzipSync(
1078
+ Buffer.from(WINDOWS_ACL_CSHARP.replace(/\n[ \t]*\n/gu, '\n')),
1063
1079
  { level: 9 },
1064
1080
  ).toString('base64');
1065
1081
 
@@ -1068,9 +1084,9 @@ const WINDOWS_ACL_SCRIPT = [
1068
1084
  "$ProgressPreference = 'SilentlyContinue'",
1069
1085
  'try {',
1070
1086
  'Set-StrictMode -Version 3',
1071
- "$target=[Environment]::GetEnvironmentVariable('BLUN_PRIVATE_ACL_TARGET','Process')",
1072
- "$kind=[Environment]::GetEnvironmentVariable('BLUN_PRIVATE_ACL_KIND','Process')",
1073
- "$sid=[Environment]::GetEnvironmentVariable('BLUN_PRIVATE_ACL_USER_SID','Process')",
1087
+ '$target=$env:BLUN_PRIVATE_ACL_TARGET',
1088
+ '$kind=$env:BLUN_PRIVATE_ACL_KIND',
1089
+ '$sid=$env:BLUN_PRIVATE_ACL_USER_SID',
1074
1090
  "if ([string]::IsNullOrWhiteSpace($target) -or -not [IO.Path]::IsPathRooted($target)) { throw 'Invalid target.' }",
1075
1091
  "if ($kind -ne 'directory' -and $kind -ne 'file' -and $kind -ne 'file-write' -and $kind -ne 'tree') { throw 'Invalid kind.' }",
1076
1092
  `$bytes=[Convert]::FromBase64String('${WINDOWS_ACL_CSHARP_GZIP}')`,
@@ -1105,15 +1121,18 @@ const WINDOWS_ACL_SCRIPT = [
1105
1121
  '$mem.Dispose()',
1106
1122
  '}',
1107
1123
  '}',
1108
- "[Console]::Out.Write('BLUN_PRIVATE_ACL_OK_V1')",
1109
- 'exit 0',
1110
- '} catch {',
1111
- "[Console]::Error.Write('BLUN_PRIVATE_ACL_ERROR_V1')",
1112
- 'exit 1',
1124
+ "[Console]::Out.Write('BLUN_PRIVATE_ACL_OK_V1')",
1125
+ 'exit 0',
1126
+ '} catch {',
1127
+ "$e='BLUN_PRIVATE_ACL_ERROR_V1'; if ($kind -eq 'directory') { $e += '|' + $_.Exception.Message }; [Console]::Error.Write($e)",
1128
+ 'exit 1',
1113
1129
  '}',
1114
1130
  ].join('\n');
1115
1131
 
1116
- const WINDOWS_ACL_ENCODED_COMMAND = Buffer.from(WINDOWS_ACL_SCRIPT, 'utf16le').toString('base64');
1132
+ const WINDOWS_ACL_ENCODED_COMMAND = Buffer.from(
1133
+ WINDOWS_ACL_SCRIPT.replace(/\n[ \t]*\n/gu, '\n'),
1134
+ 'utf16le',
1135
+ ).toString('base64');
1117
1136
 
1118
1137
  function windowsSystemDirectory(options) {
1119
1138
  const env = options.env || process.env;
@@ -1127,10 +1146,16 @@ function windowsSystemDirectory(options) {
1127
1146
  return path.win32.normalize(directory);
1128
1147
  }
1129
1148
 
1130
- function windowsHostEnvironment(systemDirectory) {
1131
- const systemRoot = path.win32.dirname(systemDirectory);
1132
- return { SystemRoot: systemRoot, WINDIR: systemRoot };
1133
- }
1149
+ function windowsHostEnvironment(systemDirectory, options) {
1150
+ const systemRoot = path.win32.dirname(systemDirectory);
1151
+ const source = options.env || process.env;
1152
+ const temporaryDirectory = source.TEMP || source.TMP;
1153
+ return {
1154
+ SystemRoot: systemRoot,
1155
+ WINDIR: systemRoot,
1156
+ ...(temporaryDirectory ? { TEMP: temporaryDirectory, TMP: temporaryDirectory } : {}),
1157
+ };
1158
+ }
1134
1159
 
1135
1160
  function runWindowsCommand(executable, args, options, env, input) {
1136
1161
  const execFileSyncImpl = options.execFileSyncImpl || execFileSync;
@@ -1190,14 +1215,14 @@ function resolveWindowsPrivateTarget(targetPath) {
1190
1215
  throw new Error('Invalid Windows private path.');
1191
1216
  }
1192
1217
  return absoluteTarget;
1193
- } catch {
1194
- throw new Error('Windows private ACL hardening or verification failed.');
1218
+ } catch (error) {
1219
+ throw new Error('Windows private ACL hardening or verification failed: invalid target path.', { cause: error });
1195
1220
  }
1196
1221
  }
1197
1222
 
1198
- function windowsAclController(options) {
1199
- const systemDirectory = windowsSystemDirectory(options);
1200
- const hostEnvironment = windowsHostEnvironment(systemDirectory);
1223
+ function windowsAclController(options) {
1224
+ const systemDirectory = windowsSystemDirectory(options);
1225
+ const hostEnvironment = windowsHostEnvironment(systemDirectory, options);
1201
1226
  const whoami = path.win32.join(systemDirectory, 'whoami.exe');
1202
1227
  const powershell = path.win32.join(
1203
1228
  systemDirectory,
@@ -1228,14 +1253,19 @@ function windowsAclController(options) {
1228
1253
  return userSid;
1229
1254
  };
1230
1255
 
1231
- const invoke = (targetPath, kind, input) => {
1232
- try {
1233
- const absoluteTarget = resolveWindowsPrivateTarget(targetPath);
1234
- const root = path.parse(absoluteTarget).root;
1256
+ const invoke = (targetPath, kind, input) => {
1257
+ try {
1258
+ const absoluteTarget = resolveWindowsPrivateTarget(targetPath);
1259
+ const failure = (reason) => new Error(
1260
+ kind === 'directory'
1261
+ ? `Windows private ACL hardening or verification failed for ${absoluteTarget}: ${reason}`
1262
+ : 'Windows private ACL hardening or verification failed.',
1263
+ );
1264
+ const root = path.parse(absoluteTarget).root;
1235
1265
  if (!path.isAbsolute(absoluteTarget)
1236
1266
  || root.toLowerCase() === absoluteTarget.toLowerCase()
1237
1267
  || !['directory', 'file', 'file-write', 'tree'].includes(kind)) {
1238
- throw new Error('Windows private ACL hardening or verification failed.');
1268
+ throw failure('invalid target or operation');
1239
1269
  }
1240
1270
  const env = {
1241
1271
  ...hostEnvironment,
@@ -1253,11 +1283,19 @@ function windowsAclController(options) {
1253
1283
  env,
1254
1284
  input,
1255
1285
  );
1256
- } catch {
1257
- throw new Error('Windows private ACL hardening or verification failed.');
1258
- }
1259
- if (output !== WINDOWS_ACL_OK_SENTINEL) {
1260
- throw new Error('Windows private ACL hardening or verification failed.');
1286
+ } catch (error) {
1287
+ const stderr = String(error?.stderr || '');
1288
+ const marker = 'BLUN_PRIVATE_ACL_ERROR_V1|';
1289
+ const detail = kind === 'directory' && stderr.startsWith(marker)
1290
+ ? stderr.slice(marker.length).replace(/[\x00-\x1f\x7f]/gu, ' ').slice(0, 400)
1291
+ : 'Windows ACL helper failed without a native diagnosis';
1292
+ if (kind === 'directory') {
1293
+ throw new Error(failure(detail).message, { cause: new Error(detail) });
1294
+ }
1295
+ throw failure(detail);
1296
+ }
1297
+ if (output !== WINDOWS_ACL_OK_SENTINEL) {
1298
+ throw failure('unexpected ACL helper response');
1261
1299
  }
1262
1300
  } finally {
1263
1301
  input?.fill(0);
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+
5
+ const PROFILE_NAME = /^[a-z0-9][a-z0-9_-]{0,31}$/u;
6
+ const RESERVED = new Set(['doctor', 'export', 'help', 'login', 'mcp', 'server', 'tools', 'update', 'upgrade', 'version']);
7
+
8
+ function profileName(value) {
9
+ const name = String(value || '').trim().toLowerCase();
10
+ if (!PROFILE_NAME.test(name)) {
11
+ throw new Error('Profilnamen duerfen nur Buchstaben, Zahlen, Unterstriche und Bindestriche enthalten.');
12
+ }
13
+ return name;
14
+ }
15
+
16
+ function parseProfileLaunchArgs(args, mode) {
17
+ const remaining = [...args];
18
+ if (mode !== 'king') return { args: remaining, name: undefined };
19
+
20
+ const flag = remaining.indexOf('--profile');
21
+ if (flag >= 0) {
22
+ if (flag + 1 >= remaining.length) throw new Error('--profile braucht einen Profilnamen.');
23
+ const name = profileName(remaining[flag + 1]);
24
+ remaining.splice(flag, 2);
25
+ return { args: remaining, name };
26
+ }
27
+
28
+ if (remaining.length === 1 && !remaining[0].startsWith('-') && !RESERVED.has(remaining[0].toLowerCase())) {
29
+ return { args: [], name: profileName(remaining[0]), implicit: true };
30
+ }
31
+ if (remaining.length === 2 && remaining.includes('-c')) {
32
+ const candidate = remaining.find(value => value !== '-c');
33
+ if (candidate && !candidate.startsWith('-')) return { args: ['-c'], name: profileName(candidate), implicit: true };
34
+ }
35
+ return { args: remaining, name: undefined };
36
+ }
37
+
38
+ function resolveSelectedProfile(sharedHome, name, pathImpl = path) {
39
+ return pathImpl.join(sharedHome, 'profile', profileName(name));
40
+ }
41
+
42
+ module.exports = { parseProfileLaunchArgs, resolveSelectedProfile };