blun-king-cli 9.1.628 → 9.1.630

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/bin/blun.js CHANGED
File without changes
File without changes
package/bin/king.js CHANGED
File without changes
File without changes
@@ -40,9 +40,142 @@ const {
40
40
  writePrivateFile,
41
41
  } = require('./private-paths');
42
42
 
43
- const { writeTelegramTemplate } = require('./rename-private-staging.cjs');
43
+ const { writeTelegramTemplate } = require('./rename-private-staging.cjs');
44
44
  const PKG = path.resolve(__dirname, '..');
45
- const ARGS = process.argv.slice(2);
45
+ const { parseProfileLaunchArgs, resolveSelectedProfile } = require('./profile-launch.cjs');
46
+
47
+ // Generated from actual 628 Commander registrations; see GRAMMAR-628.json.
48
+ // No subcommand option arity table: subcommand tails belong to Core.
49
+ const ROOT_OPTION_ARITY = Object.freeze({
50
+ "-V": "flag",
51
+ "--version": "flag",
52
+ "--local": "flag",
53
+ "--message": "required",
54
+ "--history-limit": "required",
55
+ "--timeout-ms": "required",
56
+ "--thinking": "required",
57
+ "--url": "required",
58
+ "--token": "required",
59
+ "--password": "required",
60
+ "--tls-fingerprint": "required",
61
+ "-S": "optional",
62
+ "--session": "optional",
63
+ "-r": "optional",
64
+ "--resume": "optional",
65
+ "-c": "flag",
66
+ "--continue": "flag",
67
+ "-C": "flag",
68
+ "-y": "flag",
69
+ "--yolo": "flag",
70
+ "--auto": "flag",
71
+ "-p": "required",
72
+ "--prompt": "required",
73
+ "--output-format": "required",
74
+ "--skills-dir": "required",
75
+ "--add-dir": "required",
76
+ "--effort": "required",
77
+ "--permission-mode": "required",
78
+ "--fork-session": "required",
79
+ "--allowed-tools": "required",
80
+ "--settings": "required",
81
+ "--yes": "flag",
82
+ "--auto-approve": "flag",
83
+ "--plan": "flag",
84
+ "-h": "flag",
85
+ "--help": "flag"
86
+ });
87
+ const ROOT_COMMAND_NAMES = new Set(["export","proof","brief","handoff","replay","guard","demo","workspace","acp","server","web","login","mistakes","memory","doctor","config","agents","sessions","status","models","logs","vis","setup","docs","upgrade","update","__plugin_run_node","tui","chat","terminal"]);
88
+
89
+ function parseExplicitProfileArgs(args, mode) {
90
+ if (mode !== LAUNCHER_MODES.KING) return { args: [...args], name: undefined };
91
+ const remaining = [];
92
+ let name;
93
+ let firstOperandSeen = false;
94
+ let unknownDestination = false;
95
+ for (let i = 0; i < args.length; i++) {
96
+ const arg = args[i];
97
+ if (arg === '--') {
98
+ remaining.push(...args.slice(i));
99
+ break;
100
+ }
101
+ if (arg === '--profile' || arg.startsWith('--profile=')) {
102
+ if (name !== undefined) throw new Error('--profile darf nur einmal angegeben werden.');
103
+ if (arg !== '--profile') throw new Error('--profile braucht einen getrennten Profilnamen.');
104
+ const pair = i + 1 < args.length ? [arg, args[++i]] : [arg];
105
+ name = parseProfileLaunchArgs(pair, mode).name;
106
+ continue;
107
+ }
108
+ remaining.push(arg);
109
+ let token = arg;
110
+ let arity;
111
+ let recognized = false;
112
+ let attached = false;
113
+ let separator = false;
114
+ // Replay Commander's virtual tokens after each BOOLEAN short option.
115
+ // The remainder can itself become --, a long option, or a value option:
116
+ // -cy- -> -c -y --; -cy-prompt -> -c -y --prompt.
117
+ for (;;) {
118
+ if (token === '--') { separator = true; break; }
119
+ arity = Object.hasOwn(ROOT_OPTION_ARITY, token) ? ROOT_OPTION_ARITY[token] : undefined;
120
+ recognized = arity !== undefined;
121
+ if (recognized) break;
122
+ if (token.length > 2 && token[0] === '-' && token[1] !== '-') {
123
+ const short = '-' + token[1];
124
+ arity = ROOT_OPTION_ARITY[short];
125
+ if (arity !== undefined) {
126
+ if (arity !== 'flag') { recognized = true; attached = true; break; }
127
+ token = '-' + token.slice(2);
128
+ continue;
129
+ }
130
+ }
131
+ if (/^--[^=]+=/u.test(token)) {
132
+ arity = ROOT_OPTION_ARITY[token.slice(0, token.indexOf('='))];
133
+ recognized = arity === 'required' || arity === 'optional';
134
+ attached = recognized;
135
+ }
136
+ break;
137
+ }
138
+ if (separator) {
139
+ remaining.push(...args.slice(i + 1));
140
+ break;
141
+ }
142
+ if (recognized) {
143
+ if (!attached && i + 1 < args.length
144
+ && (arity === 'required'
145
+ || (arity === 'optional' && !(args[i + 1].length > 1 && args[i + 1][0] === '-')))) {
146
+ remaining.push(args[++i]);
147
+ }
148
+ continue;
149
+ }
150
+ const maybeOption = token.length > 1 && token[0] === '-';
151
+ if (maybeOption) unknownDestination = true;
152
+ // Match Commander's first operand/unknown split. A command-looking value
153
+ // behind -p/-S, a previous positional target or an unknown option is NOT
154
+ // a new command boundary.
155
+ if (!unknownDestination && !firstOperandSeen) {
156
+ if (ROOT_COMMAND_NAMES.has(arg)) {
157
+ remaining.push(...args.slice(i + 1));
158
+ break;
159
+ }
160
+ firstOperandSeen = true;
161
+ }
162
+ }
163
+ return { args: remaining, name };
164
+ }
165
+
166
+ const PROFILE = parseExplicitProfileArgs(process.argv.slice(2), launcherModeFromArgv(process.argv));
167
+ const ARGS = PROFILE.args;
168
+
169
+ function applyProfileEnvironment(env, paths) {
170
+ env.BLUN_HOME = paths.blunHome;
171
+ if (PROFILE.name !== undefined) {
172
+ env.BLUN_SHARED_HOME = paths.sharedHome;
173
+ env.BLUN_PROFILE = PROFILE.name;
174
+ } else if (String(env.BLUN_STATE_DIR || '').trim()) {
175
+ env.BLUN_SHARED_HOME = paths.blunHome;
176
+ }
177
+ return env;
178
+ }
46
179
 
47
180
  function normalizeWindowsPath(value) {
48
181
  return path.win32.normalize(value).replace(/\\+$/u, '').toLowerCase();
@@ -52,6 +185,7 @@ function resolveLauncherPrivatePaths(options = {}) {
52
185
  const env = options.env || process.env;
53
186
  const platform = options.platform || process.platform;
54
187
  const configuredHome = String(env.BLUN_HOME || '').trim();
188
+ const selectedProfile = options.profileName ?? PROFILE.name;
55
189
  const stateDir = String(env.BLUN_STATE_DIR || '').trim();
56
190
  if (stateDir) {
57
191
  const paths = platform === 'win32' ? path.win32 : path.posix;
@@ -78,10 +212,35 @@ function resolveLauncherPrivatePaths(options = {}) {
78
212
  ancestor = parent;
79
213
  }
80
214
  }
215
+ if (selectedProfile !== undefined) {
216
+ // A state override cannot bypass the canonical OS-home/profile guards.
217
+ const profileEnv = { ...env };
218
+ delete profileEnv.BLUN_STATE_DIR;
219
+ const selected = resolveLauncherPrivatePaths({ ...options, env: profileEnv, profileName: selectedProfile });
220
+ const normalize = platform === 'win32' ? normalizeWindowsPath : paths.normalize;
221
+ if (normalize(blunHome) !== normalize(selected.blunHome)) {
222
+ throw new Error('BLUN_STATE_DIR widerspricht dem ausgewaehlten Profil.');
223
+ }
224
+ return selected;
225
+ }
81
226
  return { blunHome, userHome: (options.homedir || os.homedir)() };
82
227
  }
83
228
  if (platform !== 'win32') {
229
+ const posix = path.posix;
84
230
  const userHome = (options.homedir || os.homedir)();
231
+ if (selectedProfile !== undefined) {
232
+ const configuredShared = String(env.BLUN_SHARED_HOME || '').trim();
233
+ const sharedHome = configuredShared
234
+ ? posix.resolve(configuredShared)
235
+ : configuredHome && posix.basename(posix.dirname(configuredHome)) === 'profile'
236
+ ? posix.resolve(configuredHome, '..', '..')
237
+ : configuredHome ? posix.resolve(configuredHome) : posix.join(userHome, '.blun');
238
+ const blunHome = resolveSelectedProfile(sharedHome, selectedProfile, posix);
239
+ if (configuredShared && configuredHome && posix.resolve(configuredHome) !== blunHome) {
240
+ throw new Error(`BLUN_HOME passt nicht zum Profil ${selectedProfile}: ${blunHome}`);
241
+ }
242
+ return { blunHome, sharedHome, userHome };
243
+ }
85
244
  return {
86
245
  blunHome: configuredHome ? path.resolve(configuredHome) : path.join(userHome, '.blun'),
87
246
  userHome,
@@ -108,6 +267,19 @@ function resolveLauncherPrivatePaths(options = {}) {
108
267
  }
109
268
 
110
269
  const blunHome = path.win32.join(userHome, '.blun');
270
+ if (selectedProfile !== undefined) {
271
+ const profileHome = resolveSelectedProfile(blunHome, selectedProfile, path.win32);
272
+ const configuredShared = String(env.BLUN_SHARED_HOME || '').trim();
273
+ if (configuredShared && normalizeWindowsPath(path.win32.resolve(configuredShared)) !== normalizeWindowsPath(blunHome)) {
274
+ throw new Error(`BLUN_SHARED_HOME muss unter Windows exakt im Windows-Benutzerprofil liegen: ${blunHome}`);
275
+ }
276
+ if (configuredHome && normalizeWindowsPath(path.win32.resolve(configuredHome)) !== normalizeWindowsPath(profileHome)
277
+ && normalizeWindowsPath(path.win32.resolve(configuredHome)) !== normalizeWindowsPath(blunHome)) {
278
+ throw new Error(`BLUN_HOME passt nicht zum Profil ${selectedProfile}: ${profileHome}`);
279
+ }
280
+ return { blunHome: profileHome, sharedHome: blunHome, userHome };
281
+ }
282
+
111
283
  if (configuredHome
112
284
  && normalizeWindowsPath(path.win32.resolve(configuredHome)) !== normalizeWindowsPath(blunHome)) {
113
285
  throw new Error(`BLUN_HOME muss unter Windows exakt im Windows-Benutzerprofil liegen: ${blunHome}`);
@@ -290,6 +462,9 @@ function spawnManagedLauncher(binary, cwd) {
290
462
  async function runLauncher(options = {}) {
291
463
  const callerCwd = process.cwd();
292
464
  const mode = options.mode || launcherModeFromArgv(process.argv);
465
+ if (PROFILE.name !== undefined) {
466
+ applyProfileEnvironment(process.env, resolveLauncherPrivatePaths());
467
+ }
293
468
  const explicitUpdateRequest = isNpmPackageUpdateRequest(ARGS);
294
469
  const explicitToolsRequest = String(ARGS[0] || '').toLowerCase() === 'tools';
295
470
  const noticeResult = options.skipUpdateNotice === true
@@ -394,7 +569,7 @@ async function runLauncher(options = {}) {
394
569
  const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
395
570
  const informationalEnv = { ...process.env };
396
571
  delete informationalEnv.BLUN_HOME;
397
- env.BLUN_HOME = resolveLauncherPrivatePaths({ env: informationalEnv }).blunHome;
572
+ applyProfileEnvironment(env, resolveLauncherPrivatePaths({ env: informationalEnv }));
398
573
  process.exitCode = await runInformationalCore(ARGS, env, callerCwd, releaseNotice);
399
574
  return;
400
575
  }
@@ -406,11 +581,11 @@ async function runLauncher(options = {}) {
406
581
  process.exitCode = 1;
407
582
  return;
408
583
  }
409
- const blunDir = resolveLauncherPrivatePaths().blunHome;
584
+ const inspectionPaths = resolveLauncherPrivatePaths();
585
+ const blunDir = inspectionPaths.blunHome;
410
586
  require('./verify-bundled-agent-sources.cjs').verifyBundledAgentSources(PKG);
411
587
  const env = createLauncherEnvironment(process.env, LAUNCHER_MODES.BLUN, readPackageVersion(), blunDir);
412
- env.BLUN_HOME = blunDir;
413
- if (String(process.env.BLUN_STATE_DIR || '').trim()) env.BLUN_SHARED_HOME = blunDir;
588
+ applyProfileEnvironment(env, inspectionPaths);
414
589
  process.exitCode = await runInformationalCore(ARGS, env, callerCwd, releaseNotice);
415
590
  return;
416
591
  }
@@ -440,7 +615,7 @@ async function runLauncher(options = {}) {
440
615
  const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
441
616
  const informationalEnv = { ...process.env };
442
617
  delete informationalEnv.BLUN_HOME;
443
- env.BLUN_HOME = resolveLauncherPrivatePaths({ env: informationalEnv }).blunHome;
618
+ applyProfileEnvironment(env, resolveLauncherPrivatePaths({ env: informationalEnv }));
444
619
  process.exitCode = await superviseProtectedCore(ARGS, env, callerCwd, releaseNotice);
445
620
  return;
446
621
  }
@@ -530,8 +705,7 @@ async function runLauncher(options = {}) {
530
705
 
531
706
  // --- 6. Start -----------------------------------------------------------
532
707
  const env = createLauncherEnvironment(process.env, mode, readPackageVersion(), blunDir);
533
- env.BLUN_HOME = blunDir;
534
- if (String(process.env.BLUN_STATE_DIR || '').trim()) env.BLUN_SHARED_HOME = blunDir;
708
+ applyProfileEnvironment(env, getPrivatePaths());
535
709
  process.exitCode = await superviseProtectedCore(
536
710
  ARGS,
537
711
  env,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,152 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { randomUUID } = require('node:crypto');
6
+ const { listExistingProfiles, resolveSelectedProfile } = require('./profile-launch.cjs');
7
+
8
+ const PROFILE_HANDOFF_REQUEST = 'blun-profile-handoff-request';
9
+ const PROFILE_HANDOFF_REPLY = 'blun-profile-handoff-reply';
10
+ const PROFILE_HANDOFF_CANCEL = 'blun-profile-handoff-cancel';
11
+ const PROFILE_HANDOFF_READY = 'blun-profile-handoff-ready';
12
+ const PROFILE_HANDOFF_EXIT = 76;
13
+ const REQUEST_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
14
+ const SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/u;
15
+ const samePath = (a, b) => process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b;
16
+ const unavailable = () => Object.assign(new Error('PROFILE_HANDOFF_UNAVAILABLE'), { code: 'PROFILE_HANDOFF_UNAVAILABLE' });
17
+
18
+ function createProfileHandoff(env, isBusy = () => false) {
19
+ let pending;
20
+ const validate = (name) => {
21
+ if (typeof env.BLUN_HOME !== 'string' || !path.isAbsolute(env.BLUN_HOME)
22
+ || (env.BLUN_SHARED_HOME !== undefined && !path.isAbsolute(env.BLUN_SHARED_HOME))) throw unavailable();
23
+ const sharedHome = path.resolve(env.BLUN_SHARED_HOME || env.BLUN_HOME);
24
+ const homeDir = path.resolve(env.BLUN_HOME);
25
+ const current = env.BLUN_PROFILE;
26
+ if (current !== undefined) {
27
+ if (!env.BLUN_SHARED_HOME || path.basename(resolveSelectedProfile(sharedHome, current)) !== current
28
+ || !samePath(homeDir, resolveSelectedProfile(sharedHome, current))) throw unavailable();
29
+ } else if (!samePath(homeDir, sharedHome)) throw unavailable();
30
+ const names = listExistingProfiles(sharedHome);
31
+ if (typeof name !== 'string' || !names.includes(name) || name === current
32
+ || (current !== undefined && !names.includes(current))) throw unavailable();
33
+ const target = resolveSelectedProfile(sharedHome, name);
34
+ if (isBusy(target)) throw unavailable();
35
+ const identities = [sharedHome, path.join(sharedHome, 'profile'), homeDir, target]
36
+ .map(file => ({ file, stat: fs.lstatSync(file, { bigint: true }) }));
37
+ if (identities.some(({ stat }) => !stat.isDirectory() || stat.isSymbolicLink())) throw unavailable();
38
+ return { name, sharedHome, homeDir: target, identities };
39
+ };
40
+ return {
41
+ hasPending: () => pending !== undefined,
42
+ getSourceSessionId: () => pending?.sourceSessionId,
43
+ prepare(message) {
44
+ if (message?.type === PROFILE_HANDOFF_CANCEL && typeof message.requestId === 'string'
45
+ && REQUEST_ID.test(message.requestId) && Object.keys(message).sort().join(',') === 'requestId,type') {
46
+ if (pending?.requestId === message.requestId) pending = undefined;
47
+ return { type: PROFILE_HANDOFF_REPLY, requestId: message.requestId, status: 'cancelled' };
48
+ }
49
+ if (!message || message.type !== PROFILE_HANDOFF_REQUEST || typeof message.requestId !== 'string' || !REQUEST_ID.test(message.requestId)) return undefined;
50
+ const shape = Object.keys(message).sort().join(',');
51
+ if (shape !== 'name,requestId,type' && shape !== 'name,requestId,sourceSessionId,type') return undefined;
52
+ if (shape === 'name,requestId,sourceSessionId,type'
53
+ && (typeof message.sourceSessionId !== 'string' || !SESSION_ID.test(message.sourceSessionId))) return undefined;
54
+ let status = 'rejected';
55
+ try {
56
+ if (pending) {
57
+ if (pending.requestId === message.requestId && pending.name === message.name
58
+ && pending.sourceSessionId === message.sourceSessionId) status = 'prepared';
59
+ } else {
60
+ pending = { ...validate(message.name), requestId: message.requestId, sourceSessionId: message.sourceSessionId };
61
+ status = 'prepared';
62
+ }
63
+ } catch {}
64
+ return { type: PROFILE_HANDOFF_REPLY, requestId: message.requestId, status };
65
+ },
66
+ commit() {
67
+ try {
68
+ if (!pending) throw unavailable();
69
+ const fresh = validate(pending.name);
70
+ for (let index = 0; index < pending.identities.length; index++) {
71
+ const before = pending.identities[index];
72
+ const after = fresh.identities[index];
73
+ if (before.file !== after.file || before.stat.dev !== after.stat.dev || before.stat.ino !== after.stat.ino) throw unavailable();
74
+ }
75
+ return Object.freeze({ name: fresh.name, sharedHome: fresh.sharedHome, homeDir: fresh.homeDir,
76
+ ...(pending.sourceSessionId === undefined ? {} : { sourceSessionId: pending.sourceSessionId }) });
77
+ } catch {
78
+ throw unavailable();
79
+ }
80
+ },
81
+ };
82
+ }
83
+
84
+ function createProfileHandoffClient(channel = process) {
85
+ const enabled = channel.env?.BLUN_PROFILE_HANDOFF === '1';
86
+ let pending;
87
+ let disposed = false;
88
+ const available = () => !disposed && enabled && channel.connected === true
89
+ && typeof channel.send === 'function';
90
+ const cancel = () => {
91
+ const request = pending;
92
+ if (!request) return;
93
+ pending = undefined;
94
+ request.finish(false);
95
+ if (channel.connected === true) {
96
+ try { channel.send({ type: PROFILE_HANDOFF_CANCEL, requestId: request.id }, () => {}); } catch {}
97
+ }
98
+ };
99
+ return Object.freeze({
100
+ isPrepared: () => available() && pending?.prepared === true,
101
+ cancel,
102
+ dispose() { cancel(); disposed = true; },
103
+ /** @param {string} name @param {{ signal?: AbortSignal, timeoutMs?: number, sourceSessionId?: string }} [options] */
104
+ prepare(name, { signal, timeoutMs = 5000, sourceSessionId } = {}) {
105
+ try {
106
+ if (!available() || pending || signal?.aborted || typeof name !== 'string'
107
+ || path.basename(resolveSelectedProfile(path.parse(__dirname).root, name)) !== name
108
+ || !Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 30000
109
+ || (sourceSessionId !== undefined && (typeof sourceSessionId !== 'string' || !SESSION_ID.test(sourceSessionId)))) return Promise.resolve(false);
110
+ } catch { return Promise.resolve(false); }
111
+ return new Promise(resolve => {
112
+ const request = { id: randomUUID(), prepared: false, finish: undefined };
113
+ let settled = false;
114
+ let timer;
115
+ const aborted = () => cancel();
116
+ const disconnected = () => cancel();
117
+ const message = reply => {
118
+ if (pending !== request || reply?.type !== PROFILE_HANDOFF_REPLY || reply.requestId !== request.id
119
+ || Object.keys(reply).sort().join(',') !== 'requestId,status,type') return;
120
+ if (reply.status === 'prepared') request.finish(true);
121
+ else if (reply.status === 'rejected' || reply.status === 'cancelled') cancel();
122
+ };
123
+ request.finish = prepared => {
124
+ if (!prepared) signal?.removeEventListener('abort', aborted);
125
+ if (settled) return;
126
+ settled = true;
127
+ clearTimeout(timer);
128
+ channel.removeListener('message', message);
129
+ channel.removeListener('disconnect', disconnected);
130
+ try { channel.channel?.unref(); } catch {}
131
+ request.prepared = prepared;
132
+ resolve(prepared);
133
+ };
134
+ pending = request;
135
+ channel.on('message', message);
136
+ channel.once('disconnect', disconnected);
137
+ signal?.addEventListener('abort', aborted, { once: true });
138
+ timer = setTimeout(aborted, timeoutMs);
139
+ try {
140
+ channel.channel?.ref();
141
+ channel.send({ type: PROFILE_HANDOFF_REQUEST, requestId: request.id, name,
142
+ ...(sourceSessionId === undefined ? {} : { sourceSessionId }) }, error => {
143
+ if (error && pending === request) cancel();
144
+ });
145
+ } catch { cancel(); }
146
+ });
147
+ },
148
+ });
149
+ }
150
+
151
+ module.exports = { createProfileHandoff, createProfileHandoffClient, PROFILE_HANDOFF_CANCEL, PROFILE_HANDOFF_REQUEST, PROFILE_HANDOFF_REPLY,
152
+ PROFILE_HANDOFF_READY, PROFILE_HANDOFF_EXIT };
@@ -0,0 +1,120 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const fs = require('node:fs');
5
+
6
+ const PROFILE_NAME = /^[a-z0-9][a-z0-9_-]{0,31}$/u;
7
+ const RESERVED = new Set(['doctor', 'export', 'help', 'login', 'mcp', 'server', 'tools', 'update', 'upgrade', 'version']);
8
+
9
+ function profileName(value) {
10
+ const name = String(value || '').trim().toLowerCase();
11
+ if (!PROFILE_NAME.test(name)) {
12
+ throw new Error('Profilnamen duerfen nur Buchstaben, Zahlen, Unterstriche und Bindestriche enthalten.');
13
+ }
14
+ return name;
15
+ }
16
+
17
+ function parseProfileLaunchArgs(args, mode) {
18
+ const remaining = [...args];
19
+ if (mode !== 'king') return { args: remaining, name: undefined };
20
+
21
+ const flag = remaining.indexOf('--profile');
22
+ if (flag >= 0) {
23
+ if (flag + 1 >= remaining.length) throw new Error('--profile braucht einen Profilnamen.');
24
+ const name = profileName(remaining[flag + 1]);
25
+ remaining.splice(flag, 2);
26
+ return { args: remaining, name };
27
+ }
28
+
29
+ if (remaining.length === 1 && !remaining[0].startsWith('-') && !RESERVED.has(remaining[0].toLowerCase())) {
30
+ return { args: [], name: profileName(remaining[0]), implicit: true };
31
+ }
32
+ if (remaining.length === 2 && remaining.includes('-c')) {
33
+ const candidate = remaining.find(value => value !== '-c');
34
+ if (candidate && !candidate.startsWith('-')) return { args: ['-c'], name: profileName(candidate), implicit: true };
35
+ }
36
+ return { args: remaining, name: undefined };
37
+ }
38
+
39
+ function resolveSelectedProfile(sharedHome, name, pathImpl = path) {
40
+ return pathImpl.join(sharedHome, 'profile', profileName(name));
41
+ }
42
+
43
+ function catalogUnavailable() {
44
+ return Object.assign(new Error('PROFILE_CATALOG_UNAVAILABLE'), { code: 'PROFILE_CATALOG_UNAVAILABLE' });
45
+ }
46
+
47
+ function directoryIdentity(target, fsImpl) {
48
+ let stat;
49
+ try { stat = fsImpl.lstatSync(target, { bigint: true }); }
50
+ catch (error) {
51
+ if (error.code === 'ENOENT') return undefined;
52
+ throw error;
53
+ }
54
+ if (!stat.isDirectory() || stat.isSymbolicLink()) throw catalogUnavailable();
55
+ return stat;
56
+ }
57
+
58
+ function homeIdentity(home, fsImpl) {
59
+ const root = path.parse(home).root;
60
+ let current = root;
61
+ let stat = directoryIdentity(current, fsImpl);
62
+ for (const part of home.slice(root.length).split(path.sep).filter(Boolean)) {
63
+ if (stat === undefined) return undefined;
64
+ current = path.join(current, part);
65
+ stat = directoryIdentity(current, fsImpl);
66
+ }
67
+ return stat;
68
+ }
69
+
70
+ function sameDirectory(before, after) {
71
+ return before !== undefined && after !== undefined && before.dev === after.dev && before.ino === after.ino;
72
+ }
73
+
74
+ /** Directory names only; selection must revalidate them before changing owner. */
75
+ function listExistingProfiles(sharedHome, options = {}) {
76
+ const fsImpl = options.fsImpl || fs;
77
+ try {
78
+ if (typeof sharedHome !== 'string' || !path.isAbsolute(sharedHome)) throw catalogUnavailable();
79
+ const home = path.resolve(sharedHome);
80
+ if (process.platform === 'win32' && home.startsWith('\\\\')) throw catalogUnavailable();
81
+ const beforeHome = homeIdentity(home, fsImpl);
82
+ if (beforeHome === undefined) return [];
83
+ const profileRoot = path.join(home, 'profile');
84
+ const beforeProfiles = directoryIdentity(profileRoot, fsImpl);
85
+ if (beforeProfiles === undefined) {
86
+ if (!sameDirectory(beforeHome, homeIdentity(home, fsImpl))) throw catalogUnavailable();
87
+ return [];
88
+ }
89
+ const rows = [];
90
+ const dir = fsImpl.opendirSync(profileRoot);
91
+ try {
92
+ let count = 0;
93
+ for (let entry; (entry = dir.readSync()) !== null;) {
94
+ if (++count > 4096) throw catalogUnavailable();
95
+ const name = entry.name;
96
+ if (!PROFILE_NAME.test(name)) continue;
97
+ const target = resolveSelectedProfile(home, name);
98
+ let stat;
99
+ try { stat = fsImpl.lstatSync(target, { bigint: true }); }
100
+ catch (error) {
101
+ if (error.code === 'ENOENT') continue;
102
+ throw error;
103
+ }
104
+ if (stat.isDirectory() && !stat.isSymbolicLink()) rows.push({ name, target, stat });
105
+ }
106
+ } finally { dir.closeSync(); }
107
+ if (!sameDirectory(beforeHome, homeIdentity(home, fsImpl))
108
+ || !sameDirectory(beforeProfiles, directoryIdentity(profileRoot, fsImpl))) throw catalogUnavailable();
109
+ for (const row of rows) {
110
+ if (!sameDirectory(row.stat, directoryIdentity(row.target, fsImpl))) throw catalogUnavailable();
111
+ }
112
+ if (!sameDirectory(beforeHome, homeIdentity(home, fsImpl))
113
+ || !sameDirectory(beforeProfiles, directoryIdentity(profileRoot, fsImpl))) throw catalogUnavailable();
114
+ return rows.map(row => row.name).sort();
115
+ } catch {
116
+ throw catalogUnavailable();
117
+ }
118
+ }
119
+
120
+ module.exports = { parseProfileLaunchArgs, resolveSelectedProfile, listExistingProfiles };
File without changes
File without changes
File without changes
File without changes
package/blun.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // BLUN_BUILD_INPUT_SHA256:c6e4e4587ddf016fdb40a775622da5455beee2ed97708e374bf683b38e4a9dea
2
+ // BLUN_BUILD_INPUT_SHA256:295bff16bb3a40df46f164599652a975e64b232ee91e1b245fa87962a4b61885
3
3
  /*! wasm-webp 0.1.0
4
4
  MIT License
5
5
 
@@ -13347,23 +13347,15 @@ var init_managed_usage = __esmMin((() => {
13347
13347
  init_utils$2();
13348
13348
  DEFAULT_BLUN_BASE_URL = "https://api.blun.ai/v1";
13349
13349
  DEFAULT_BLUN_AUXILIARY_BASE_URL = "https://api.blun.ai/coding/v1";
13350
- ACCOUNT_USAGE_WINDOWS = [
13351
- [
13352
- "fuenf_stunden",
13353
- "five_hours",
13354
- "5h"
13355
- ],
13356
- [
13357
- "sieben_tage",
13358
- "seven_days",
13359
- "7d"
13360
- ],
13361
- [
13362
- "monat",
13363
- "month",
13364
- "month"
13365
- ]
13366
- ];
13350
+ ACCOUNT_USAGE_WINDOWS = [[
13351
+ "sieben_tage",
13352
+ "seven_days",
13353
+ "7d"
13354
+ ], [
13355
+ "monat",
13356
+ "month",
13357
+ "month"
13358
+ ]];
13367
13359
  }));
13368
13360
  function cloneOverrides(overrides) {
13369
13361
  if (overrides === void 0) return void 0;
@@ -13864,9 +13856,11 @@ function parseManagedQuotaPayload(payload) {
13864
13856
  const billingKind = nonEmptyString$6(payload["art"]);
13865
13857
  const globalUnlimited = payload["unlimited"];
13866
13858
  if (plan === void 0 || typeof paid !== "boolean" || !Number.isSafeInteger(creditCents) || creditCents < 0 || billingKind === void 0 || globalUnlimited !== void 0 && typeof globalUnlimited !== "boolean" || !isRecord$27(payload["stand"])) return;
13867
- if (!hasStrictQuotaStand(payload["stand"], globalUnlimited === true)) return void 0;
13859
+ const stand = payload["stand"];
13860
+ if (!hasStrictQuotaStand(stand, globalUnlimited === true)) return void 0;
13861
+ const presentWindows = QUOTA_WINDOWS.filter(([sourceKey]) => sourceKey in stand);
13868
13862
  const limits = parseManagedUsagePayload(payload).limits;
13869
- if (limits.length !== REQUIRED_WINDOWS.length || REQUIRED_WINDOWS.some(([, id]) => limits.filter((row) => row.id === id).length !== 1)) return;
13863
+ if (limits.length !== presentWindows.length || presentWindows.some(([, id]) => limits.filter((row) => row.id === id).length !== 1)) return;
13870
13864
  return {
13871
13865
  plan,
13872
13866
  paid,
@@ -13931,7 +13925,8 @@ function nonEmptyString$6(value) {
13931
13925
  return normalized.length === 0 ? void 0 : normalized;
13932
13926
  }
13933
13927
  function hasStrictQuotaStand(stand, globalUnlimited) {
13934
- return REQUIRED_WINDOWS.every(([sourceKey]) => {
13928
+ return QUOTA_WINDOWS.every(([sourceKey]) => {
13929
+ if (!(sourceKey in stand)) return true;
13935
13930
  const row = stand[sourceKey];
13936
13931
  if (!isRecord$27(row)) return false;
13937
13932
  const used = row["verbraucht"];
@@ -13954,18 +13949,14 @@ function hasValidResetAt(value) {
13954
13949
  if (typeof value !== "string") return false;
13955
13950
  return /(?:Z|[+-]\d{2}:\d{2})$/i.test(value) && Number.isFinite(Date.parse(value));
13956
13951
  }
13957
- var MANAGED_QUOTA_PATH, REQUIRED_WINDOWS;
13952
+ var MANAGED_QUOTA_PATH, QUOTA_WINDOWS;
13958
13953
  var init_managed_quota$1 = __esmMin((() => {
13959
13954
  init_constants$6();
13960
13955
  init_api_error();
13961
13956
  init_managed_usage();
13962
13957
  init_utils$2();
13963
13958
  MANAGED_QUOTA_PATH = "/api/verbrauch/mein-stand";
13964
- REQUIRED_WINDOWS = [
13965
- ["fuenf_stunden", "five_hours"],
13966
- ["sieben_tage", "seven_days"],
13967
- ["monat", "month"]
13968
- ];
13959
+ QUOTA_WINDOWS = [["sieben_tage", "seven_days"], ["monat", "month"]];
13969
13960
  }));
13970
13961
  function blunCodeFeedbackUrl(baseUrl) {
13971
13962
  return `${blunCodeAuxiliaryBaseUrl(baseUrl)}/feedback`;
@@ -529129,8 +529120,11 @@ function normalizeManagedQuotaWindows(rows) {
529129
529120
  unlimited: row.unlimited === true ? true : void 0
529130
529121
  });
529131
529122
  }
529132
- if (byId.size !== WINDOW_IDS.length) return void 0;
529133
- return WINDOW_IDS.map((id) => byId.get(id));
529123
+ if (byId.size === 0 && rows.some((row) => row.id !== void 0 && WINDOW_IDS.includes(row.id))) return;
529124
+ return WINDOW_IDS.flatMap((id) => {
529125
+ const window = byId.get(id);
529126
+ return window === void 0 ? [] : [window];
529127
+ });
529134
529128
  }
529135
529129
  function selectHighestManagedQuotaWindow(windows) {
529136
529130
  return windows?.filter((window) => window.unlimited !== true).reduce((highest, window) => highest === void 0 || window.fraction > highest.fraction ? window : highest, void 0);
@@ -529148,11 +529142,7 @@ function nextManagedQuotaWarning(windows, highestShown) {
529148
529142
  var MANAGED_QUOTA_WARNING_METADATA_KEY, WINDOW_IDS;
529149
529143
  var init_managed_quota = __esmMin((() => {
529150
529144
  MANAGED_QUOTA_WARNING_METADATA_KEY = "managedQuotaWarningThreshold";
529151
- WINDOW_IDS = [
529152
- "five_hours",
529153
- "seven_days",
529154
- "month"
529155
- ];
529145
+ WINDOW_IDS = ["seven_days", "month"];
529156
529146
  }));
529157
529147
  function managedProviderOnly(providers) {
529158
529148
  const managed = providers[DEFAULT_OAUTH_PROVIDER_NAME];
@@ -537127,6 +537117,7 @@ function centerQuotaLine(line, width) {
537127
537117
  return `${" ".repeat(leftPadding)}${line}`;
537128
537118
  }
537129
537119
  function formatManagedQuotaFooterLines(windows, colors, width, options = {}) {
537120
+ windows = windows.filter((window) => window.id !== "five_hours");
537130
537121
  if (windows.length === 0 || width <= 0) return [];
537131
537122
  const locale = options.locale ?? getCurrentUiLocale();
537132
537123
  const variants = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.628",
3
+ "version": "9.1.630",
4
4
  "description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
5
5
  "license": "MIT",
6
6
  "bin": {
package/worker-host.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // BLUN_BUILD_INPUT_SHA256:c6e4e4587ddf016fdb40a775622da5455beee2ed97708e374bf683b38e4a9dea
2
+ // BLUN_BUILD_INPUT_SHA256:295bff16bb3a40df46f164599652a975e64b232ee91e1b245fa87962a4b61885
3
3
  /*! wasm-webp 0.1.0
4
4
  MIT License
5
5
 
@@ -13011,23 +13011,15 @@ function managedContextWindowFrom(payload) {
13011
13011
  const [first] = values;
13012
13012
  return values.every((value) => value === first) ? first : void 0;
13013
13013
  }
13014
- const ACCOUNT_USAGE_WINDOWS = [
13015
- [
13016
- "fuenf_stunden",
13017
- "five_hours",
13018
- "5h"
13019
- ],
13020
- [
13021
- "sieben_tage",
13022
- "seven_days",
13023
- "7d"
13024
- ],
13025
- [
13026
- "monat",
13027
- "month",
13028
- "month"
13029
- ]
13030
- ];
13014
+ const ACCOUNT_USAGE_WINDOWS = [[
13015
+ "sieben_tage",
13016
+ "seven_days",
13017
+ "7d"
13018
+ ], [
13019
+ "monat",
13020
+ "month",
13021
+ "month"
13022
+ ]];
13031
13023
  function toAccountUsageRow(raw, id, label, accountUnlimited) {
13032
13024
  if (!isRecord$9(raw)) return null;
13033
13025
  const used = toInt(raw["verbraucht"]);
@@ -13738,11 +13730,7 @@ async function provisionManagedBlunCodeConfig(options) {
13738
13730
  };
13739
13731
  }
13740
13732
  const MANAGED_QUOTA_PATH = "/api/verbrauch/mein-stand";
13741
- const REQUIRED_WINDOWS = [
13742
- ["fuenf_stunden", "five_hours"],
13743
- ["sieben_tage", "seven_days"],
13744
- ["monat", "month"]
13745
- ];
13733
+ const QUOTA_WINDOWS = [["sieben_tage", "seven_days"], ["monat", "month"]];
13746
13734
  function blunManagedQuotaUrl(oauthHost) {
13747
13735
  return `${(oauthHost ?? process.env["BLUN_OAUTH_HOST"] ?? "https://account.blun.ai").replace(/\/+$/, "")}${MANAGED_QUOTA_PATH}`;
13748
13736
  }
@@ -13754,9 +13742,11 @@ function parseManagedQuotaPayload(payload) {
13754
13742
  const billingKind = nonEmptyString$5(payload["art"]);
13755
13743
  const globalUnlimited = payload["unlimited"];
13756
13744
  if (plan === void 0 || typeof paid !== "boolean" || !Number.isSafeInteger(creditCents) || creditCents < 0 || billingKind === void 0 || globalUnlimited !== void 0 && typeof globalUnlimited !== "boolean" || !isRecord$9(payload["stand"])) return;
13757
- if (!hasStrictQuotaStand(payload["stand"], globalUnlimited === true)) return void 0;
13745
+ const stand = payload["stand"];
13746
+ if (!hasStrictQuotaStand(stand, globalUnlimited === true)) return void 0;
13747
+ const presentWindows = QUOTA_WINDOWS.filter(([sourceKey]) => sourceKey in stand);
13758
13748
  const limits = parseManagedUsagePayload(payload).limits;
13759
- if (limits.length !== REQUIRED_WINDOWS.length || REQUIRED_WINDOWS.some(([, id]) => limits.filter((row) => row.id === id).length !== 1)) return;
13749
+ if (limits.length !== presentWindows.length || presentWindows.some(([, id]) => limits.filter((row) => row.id === id).length !== 1)) return;
13760
13750
  return {
13761
13751
  plan,
13762
13752
  paid,
@@ -13821,7 +13811,8 @@ function nonEmptyString$5(value) {
13821
13811
  return normalized.length === 0 ? void 0 : normalized;
13822
13812
  }
13823
13813
  function hasStrictQuotaStand(stand, globalUnlimited) {
13824
- return REQUIRED_WINDOWS.every(([sourceKey]) => {
13814
+ return QUOTA_WINDOWS.every(([sourceKey]) => {
13815
+ if (!(sourceKey in stand)) return true;
13825
13816
  const row = stand[sourceKey];
13826
13817
  if (!isRecord$9(row)) return false;
13827
13818
  const used = row["verbraucht"];