blun-king-cli 9.1.45 → 9.1.46

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.45
12
+ npm install -g blun-king-cli@9.1.46
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.45
12
+ npm install -g blun-king-cli@9.1.46
13
13
  ```
14
14
 
15
15
  ## Reproduzierbares Staging und Packen
package/blun.mjs CHANGED
@@ -76582,6 +76582,24 @@ var init_jitter = __esmMin((() => {
76582
76582
  }));
76583
76583
  //#endregion
76584
76584
  //#region ../../packages/agent-core/src/tools/cron/scheduler.ts
76585
+ function sessionLoopIntervalMs(task) {
76586
+ if (task.owner !== "session-loop" || typeof task.loopInterval !== "string") return null;
76587
+ if (task.loopInterval === "auto") return 15 * 6e4;
76588
+ const match = /^([1-9]\d*)(m|h|d)$/i.exec(task.loopInterval);
76589
+ if (match === null) return null;
76590
+ const amount = Number(match[1]);
76591
+ const unit = match[2]?.toLowerCase();
76592
+ const multiplier = unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
76593
+ const intervalMs = amount * multiplier;
76594
+ return Number.isSafeInteger(intervalMs) ? intervalMs : null;
76595
+ }
76596
+ function countSessionLoopCoalesced(intervalMs, firstFireMs, nowMs) {
76597
+ const count = Math.max(1, Math.min(MAX_COALESCE_ITERATIONS, Math.floor((nowMs - firstFireMs) / intervalMs) + 1));
76598
+ return {
76599
+ count,
76600
+ lastDueMs: firstFireMs + (count - 1) * intervalMs
76601
+ };
76602
+ }
76585
76603
  function createCronScheduler(opts) {
76586
76604
  const { clocks, source, onFire, isIdle, isKilled, removeOneShot, onAdvanceCursor, pollIntervalMs } = opts;
76587
76605
  const parsedCache = /* @__PURE__ */ new Map();
@@ -76605,6 +76623,8 @@ function createCronScheduler(opts) {
76605
76623
  * the search budget (legal-but-never-fires expression).
76606
76624
  */
76607
76625
  function computeJitteredNext(task, parsed, baseMs) {
76626
+ const intervalMs = sessionLoopIntervalMs(task);
76627
+ if (intervalMs !== null) return baseMs + intervalMs;
76608
76628
  const ideal = computeNextCronRun(parsed, baseMs);
76609
76629
  if (ideal === null) return null;
76610
76630
  if (task.recurring === false) return oneShotJitteredNextCronRunMs(task, ideal);
@@ -76668,11 +76688,12 @@ function createCronScheduler(opts) {
76668
76688
  const nextFireAt = computeJitteredNext(task, parsed, baseFromMs);
76669
76689
  if (nextFireAt === null) continue;
76670
76690
  if (now < nextFireAt) continue;
76671
- const ideal = computeNextCronRun(parsed, baseFromMs);
76691
+ const intervalMs = sessionLoopIntervalMs(task);
76692
+ const ideal = intervalMs === null ? computeNextCronRun(parsed, baseFromMs) : nextFireAt;
76672
76693
  let coalescedCount = 1;
76673
76694
  let lastDueMs = null;
76674
76695
  if (task.recurring !== false && ideal !== null) {
76675
- const result = countCoalesced(task, parsed, ideal, now);
76696
+ const result = intervalMs === null ? countCoalesced(task, parsed, ideal, now) : countSessionLoopCoalesced(intervalMs, ideal, now);
76676
76697
  coalescedCount = Math.max(1, result.count);
76677
76698
  lastDueMs = result.lastDueMs;
76678
76699
  }
@@ -77632,6 +77653,10 @@ var init_cron_create = __esmMin((() => {
77632
77653
  //#endregion
77633
77654
  //#region ../../packages/agent-core/src/agent/session-loop.ts
77634
77655
  function parseLoopInterval(rawInterval) {
77656
+ if (rawInterval.trim().toLowerCase() === "auto") return {
77657
+ interval: "auto",
77658
+ cron: "* * * * *"
77659
+ };
77635
77660
  const match = LOOP_INTERVAL_PATTERN.exec(rawInterval.trim());
77636
77661
  if (match === null) throw invalidLoopInterval(rawInterval);
77637
77662
  const amount = Number(match[1]);
@@ -77639,25 +77664,13 @@ function parseLoopInterval(rawInterval) {
77639
77664
  if (!Number.isSafeInteger(amount)) throw invalidLoopInterval(rawInterval);
77640
77665
  const totalMinutes = unit === "m" ? amount : unit === "h" ? amount * 60 : amount * 1440;
77641
77666
  if (!Number.isSafeInteger(totalMinutes)) throw invalidLoopInterval(rawInterval);
77642
- if (totalMinutes === 1440) return {
77643
- interval: "1d",
77644
- cron: "0 0 * * *"
77645
- };
77646
- if (totalMinutes < 60 && 60 % totalMinutes === 0) return {
77647
- interval: `${String(totalMinutes)}m`,
77648
- cron: totalMinutes === 1 ? "* * * * *" : `*/${String(totalMinutes)} * * * *`
77667
+ return {
77668
+ interval: `${String(amount)}${unit}`,
77669
+ cron: "* * * * *"
77649
77670
  };
77650
- if (totalMinutes < 1440 && totalMinutes % 60 === 0) {
77651
- const hours = totalMinutes / 60;
77652
- if (24 % hours === 0) return {
77653
- interval: `${String(hours)}h`,
77654
- cron: hours === 1 ? "0 * * * *" : `0 */${String(hours)} * * *`
77655
- };
77656
- }
77657
- throw invalidLoopInterval(rawInterval);
77658
77671
  }
77659
77672
  function invalidLoopInterval(rawInterval) {
77660
- return new BlunError(ErrorCodes.LOOP_INTERVAL_INVALID, `Invalid loop interval ${JSON.stringify(rawInterval)}; use an exact cron cadence such as 1m, 5m, 15m, 1h, 6h, or 1d`);
77673
+ return new BlunError(ErrorCodes.LOOP_INTERVAL_INVALID, `Invalid loop interval ${JSON.stringify(rawInterval)}`);
77661
77674
  }
77662
77675
  var MAX_LOOP_PROMPT_BYTES, LOOP_INTERVAL_PATTERN, SessionLoopMode;
77663
77676
  var init_session_loop = __esmMin((() => {
@@ -401109,7 +401122,8 @@ const BUILTIN_SLASH_COMMAND_DEFINITIONS = [
401109
401122
  aliases: [],
401110
401123
  descriptionKey: "command.loop.description",
401111
401124
  priority: 80,
401112
- argumentHint: "[interval] [prompt]",
401125
+ argumentHint: "<natürlicher Auftrag>",
401126
+ liveArgumentHint: loopInputHint,
401113
401127
  availability: (args) => {
401114
401128
  const trimmed = args.trim();
401115
401129
  if (trimmed === "") return "always";
@@ -420058,7 +420072,8 @@ function isBusy(host) {
420058
420072
  }
420059
420073
  //#endregion
420060
420074
  //#region src/tui/commands/loop.ts
420061
- const LOOP_ARGUMENT_HINT = "/loop [interval] [prompt]";
420075
+ const LOOP_ARGUMENT_HINT = "/loop <natürlicher Auftrag> · Zeitangaben dürfen überall stehen";
420076
+ const DEFAULT_LOOP_INTERVAL = "auto";
420062
420077
  const LOOP_CREATE_FILLERS = new Set([
420063
420078
  "mit",
420064
420079
  "alle",
@@ -420068,6 +420083,77 @@ const LOOP_CREATE_FILLERS = new Set([
420068
420083
  "each"
420069
420084
  ]);
420070
420085
  const LOOP_INTERVAL_TOKEN = /^\d+(?:m|h|d)$/i;
420086
+ const LOOP_NATURAL_INTERVAL = /\b(?:alle|jede(?:n|r|s)?|every|each)\s+(\d+)\s*(min(?:ute)?n?|minutes?|stunden?|hours?|tage?|days?)\b/iu;
420087
+ const LOOP_NATURAL_HALF_HOUR = /\b(?:alle|jede(?:n|r|s)?|every|each)\s+(?:halbe|half(?:\s+an)?)\s+(?:stunde|hour)\b/iu;
420088
+ const LOOP_NATURAL_SINGLE_INTERVAL = /\b(?:jede(?:n|r|s)?|every|each)\s+(minute|stunde|hour|tag|day)\b/iu;
420089
+ const LOOP_NATURAL_ADVERB_INTERVAL = /\b(stündlich|stuendlich|hourly|täglich|taeglich|daily)\b/iu;
420090
+ const LOOP_SELF_CONTROLLED = /\b(?:selbst\s+entscheiden|selbstgesteuert)\b/iu;
420091
+ function naturalLoopIntervalMatch(args) {
420092
+ const numeric = LOOP_NATURAL_INTERVAL.exec(args);
420093
+ if (numeric !== null) {
420094
+ const amount = numeric[1];
420095
+ const unit = numeric[2]?.toLowerCase() ?? "";
420096
+ const suffix = unit.startsWith("stund") || unit.startsWith("hour") ? "h" : unit.startsWith("tag") || unit.startsWith("day") ? "d" : "m";
420097
+ return {
420098
+ match: numeric,
420099
+ interval: `${amount}${suffix}`
420100
+ };
420101
+ }
420102
+ const halfHour = LOOP_NATURAL_HALF_HOUR.exec(args);
420103
+ if (halfHour !== null) return {
420104
+ match: halfHour,
420105
+ interval: "30m"
420106
+ };
420107
+ const single = LOOP_NATURAL_SINGLE_INTERVAL.exec(args);
420108
+ if (single !== null) {
420109
+ const unit = single[1]?.toLowerCase() ?? "";
420110
+ return {
420111
+ match: single,
420112
+ interval: unit === "minute" ? "1m" : unit === "tag" || unit === "day" ? "1d" : "1h"
420113
+ };
420114
+ }
420115
+ const adverb = LOOP_NATURAL_ADVERB_INTERVAL.exec(args);
420116
+ if (adverb !== null) {
420117
+ const unit = adverb[1]?.toLowerCase() ?? "";
420118
+ return {
420119
+ match: adverb,
420120
+ interval: unit === "täglich" || unit === "taeglich" || unit === "daily" ? "1d" : "1h"
420121
+ };
420122
+ }
420123
+ return null;
420124
+ }
420125
+ function parseNaturalLoopInterval(args) {
420126
+ const parsed = naturalLoopIntervalMatch(args);
420127
+ if (parsed === null) return null;
420128
+ const { match, interval } = parsed;
420129
+ const prompt = `${args.slice(0, match.index)} ${args.slice(match.index + match[0].length)}`.replace(/\s+/gu, " ").trim();
420130
+ if (prompt.length === 0) return { kind: "error" };
420131
+ return {
420132
+ kind: "create",
420133
+ interval,
420134
+ prompt
420135
+ };
420136
+ }
420137
+ function naturalLoopIntervalText(interval) {
420138
+ if (interval === "auto") return "Selbstgesteuert";
420139
+ const match = /^([1-9]\d*)(m|h|d)$/i.exec(interval);
420140
+ if (match === null) return "";
420141
+ const amount = Number(match[1]);
420142
+ const unit = match[2]?.toLowerCase();
420143
+ if (unit === "m") return `Erkannt: alle ${String(amount)} ${amount === 1 ? "Minute" : "Minuten"}`;
420144
+ if (unit === "h") return `Erkannt: alle ${String(amount)} ${amount === 1 ? "Stunde" : "Stunden"}`;
420145
+ return `Erkannt: alle ${String(amount)} ${amount === 1 ? "Tag" : "Tage"}`;
420146
+ }
420147
+ function loopInputHint(rawArgs) {
420148
+ const args = rawArgs.trim();
420149
+ if (args.length === 0) return "Natürlichen Auftrag eingeben; Zeitangaben dürfen überall stehen";
420150
+ if (/^(?:status|pause|paused|resume|stop)$/i.test(args)) return "";
420151
+ const natural = naturalLoopIntervalMatch(args);
420152
+ if (natural !== null) return naturalLoopIntervalText(natural.interval);
420153
+ const explicit = /^(?:start\s+)?([1-9]\d*(?:m|h|d))\b/i.exec(args);
420154
+ if (explicit?.[1] !== void 0) return naturalLoopIntervalText(explicit[1].toLowerCase());
420155
+ return "Selbstgesteuert";
420156
+ }
420071
420157
  function parseLoopCommand(rawArgs) {
420072
420158
  const args = rawArgs.trim();
420073
420159
  if (args.length === 0 || args.toLowerCase() === "status") return { kind: "status" };
@@ -420079,13 +420165,35 @@ function parseLoopCommand(rawArgs) {
420079
420165
  if (action === "stop") return { kind: "stop" };
420080
420166
  }
420081
420167
  let startIndex = action === "start" ? 1 : 0;
420168
+ const createArgs = tokens.slice(startIndex).join(" ");
420169
+ const natural = parseNaturalLoopInterval(createArgs);
420170
+ if (natural !== null) return natural;
420171
+ const selfControlled = LOOP_SELF_CONTROLLED.exec(createArgs);
420172
+ if (selfControlled !== null) {
420173
+ const prompt = `${createArgs.slice(0, selfControlled.index)} ${createArgs.slice(selfControlled.index + selfControlled[0].length)}`.replace(/\s+/gu, " ").trim();
420174
+ if (prompt.length === 0) return { kind: "error" };
420175
+ return {
420176
+ kind: "create",
420177
+ interval: "auto",
420178
+ prompt
420179
+ };
420180
+ }
420082
420181
  if (LOOP_CREATE_FILLERS.has(tokens[startIndex]?.toLowerCase() ?? "")) startIndex += 1;
420083
420182
  const interval = tokens[startIndex];
420084
- const prompt = tokens.slice(startIndex + 1).join(" ").trim();
420085
- if (interval === void 0 || !LOOP_INTERVAL_TOKEN.test(interval) || prompt.length === 0) return { kind: "error" };
420183
+ if (interval !== void 0 && LOOP_INTERVAL_TOKEN.test(interval)) {
420184
+ const prompt = tokens.slice(startIndex + 1).join(" ").trim();
420185
+ if (prompt.length === 0) return { kind: "error" };
420186
+ return {
420187
+ kind: "create",
420188
+ interval,
420189
+ prompt
420190
+ };
420191
+ }
420192
+ const prompt = tokens.slice(startIndex).join(" ").trim();
420193
+ if (prompt.length === 0) return { kind: "error" };
420086
420194
  return {
420087
420195
  kind: "create",
420088
- interval,
420196
+ interval: DEFAULT_LOOP_INTERVAL,
420089
420197
  prompt
420090
420198
  };
420091
420199
  }
@@ -501930,6 +502038,9 @@ var PersonalMemoryController = class {
501930
502038
  };
501931
502039
  //#endregion
501932
502040
  //#region src/tui/constant/scrollback.ts
502041
+ function shouldEnableScrollbackMouseTracking(buffer) {
502042
+ return buffer !== void 0 && buffer.isActive();
502043
+ }
501933
502044
  const ENABLE_SCROLLBACK_MOUSE_TRACKING = "\x1B[?1000h\x1B[?1006h";
501934
502045
  const DISABLE_SCROLLBACK_MOUSE_TRACKING = "\x1B[?1000l\x1B[?1006l";
501935
502046
  //#endregion
@@ -502286,7 +502397,7 @@ var BottomPinnedTUI = class extends TUI {
502286
502397
  }
502287
502398
  const buffer = this.scrollbackBuffer;
502288
502399
  buffer?.updateSnapshot(clipped, scrollViewportRows);
502289
- this.setMouseTracking(buffer !== void 0 && buffer.totalLines > 0);
502400
+ this.setMouseTracking(shouldEnableScrollbackMouseTracking(buffer));
502290
502401
  if (buffer !== void 0 && buffer.isActive() && terminalRows > 0) {
502291
502402
  const pinnedLines = [];
502292
502403
  for (let i = chromeAt; i < blocks.length; i++) {
@@ -510056,8 +510167,10 @@ var CustomEditor = class extends Editor {
510056
510167
  consumingPaste = false;
510057
510168
  consumeBuffer = "";
510058
510169
  argumentHints = /* @__PURE__ */ new Map();
510059
- setArgumentHints(hints) {
510170
+ argumentHintResolvers = /* @__PURE__ */ new Map();
510171
+ setArgumentHints(hints, resolvers = /* @__PURE__ */ new Map()) {
510060
510172
  this.argumentHints = hints;
510173
+ this.argumentHintResolvers = resolvers;
510061
510174
  }
510062
510175
  constructor(tui, options = {}) {
510063
510176
  const theme = createEditorTheme();
@@ -510151,12 +510264,18 @@ var CustomEditor = class extends Editor {
510151
510264
  computeArgumentHint() {
510152
510265
  if (this.inputMode === "bash") return void 0;
510153
510266
  const text = this.getText();
510154
- const match = /^\/(\S+)( ?)$/.exec(text);
510267
+ const match = /^\/(\S+)(?:\s+([\s\S]*))?$/.exec(text);
510155
510268
  if (match === null) return void 0;
510156
510269
  const cmd = match[1];
510157
- const trailingSpace = match[2] ?? "";
510270
+ const args = match[2] ?? "";
510271
+ const trailingSpace = text.endsWith(" ") ? " " : "";
510158
510272
  if (cmd === void 0) return void 0;
510159
510273
  if (this.hasAutocompleteActivity() && trailingSpace.length === 0) return void 0;
510274
+ const resolver = this.argumentHintResolvers.get(cmd);
510275
+ if (resolver !== void 0) {
510276
+ const resolved = resolver(args);
510277
+ if (resolved.length > 0) return args.length > 0 ? ` · ${resolved}` : ` ${resolved}`;
510278
+ }
510160
510279
  const hint = this.argumentHints.get(cmd);
510161
510280
  if (hint === void 0) return void 0;
510162
510281
  const { line, col } = this.getCursor();
@@ -512061,20 +512180,26 @@ var BlunTUI = class {
512061
512180
  aliases: cmd.aliases,
512062
512181
  description: cmd.description,
512063
512182
  ...cmd.argumentHint !== void 0 ? { argumentHint: cmd.argumentHint } : {},
512183
+ ...cmd.liveArgumentHint !== void 0 ? { liveArgumentHint: cmd.liveArgumentHint } : {},
512064
512184
  ...completer !== void 0 ? { getArgumentCompletions: (prefix) => completer(prefix) } : {}
512065
512185
  };
512066
512186
  });
512067
512187
  const provider = new FileMentionProvider(slashCommands, this.state.appState.workDir, this.fdPath, this.state.appState.additionalDirs, () => this.state.appState.inputMode, this.mentionFileSearch);
512068
512188
  this.state.editor.setAutocompleteProvider(provider);
512069
512189
  const argumentHints = /* @__PURE__ */ new Map();
512190
+ const argumentHintResolvers = /* @__PURE__ */ new Map();
512070
512191
  for (const cmd of slashCommands) {
512071
512192
  if (cmd.argumentHint === void 0) continue;
512072
512193
  const visibleHint = cmd.description?.trim();
512073
512194
  if (visibleHint === void 0 || visibleHint.length === 0) continue;
512074
512195
  argumentHints.set(cmd.name, visibleHint);
512075
512196
  for (const alias of cmd.aliases ?? []) argumentHints.set(alias, visibleHint);
512197
+ if (cmd.liveArgumentHint !== void 0) {
512198
+ argumentHintResolvers.set(cmd.name, cmd.liveArgumentHint);
512199
+ for (const alias of cmd.aliases ?? []) argumentHintResolvers.set(alias, cmd.liveArgumentHint);
512200
+ }
512076
512201
  }
512077
- this.state.editor.setArgumentHints(argumentHints);
512202
+ this.state.editor.setArgumentHints(argumentHints, argumentHintResolvers);
512078
512203
  }
512079
512204
  refreshSlashCommandAutocomplete() {
512080
512205
  this.setupAutocomplete();
@@ -514681,16 +514806,44 @@ function readTelegramUpdateRecipients(stateDir = defaultTelegramStateDir()) {
514681
514806
  return [];
514682
514807
  }
514683
514808
  }
514809
+ function claimTelegramUpdateNotice(version, chatId, receiptDir = join(homedir(), ".blun", "updates", "telegram-notices")) {
514810
+ const safeVersion = version.trim().replace(/[^0-9A-Za-z._-]/gu, "_").slice(0, 64);
514811
+ if (safeVersion.length === 0 || !TELEGRAM_DM_ID.test(chatId)) return null;
514812
+ const receiptPath = join(receiptDir, `${safeVersion}-${chatId}.sent`);
514813
+ try {
514814
+ mkdirSync(receiptDir, {
514815
+ recursive: true,
514816
+ mode: 448
514817
+ });
514818
+ writeFileSync(receiptPath, `${(/* @__PURE__ */ new Date()).toISOString()}\n`, {
514819
+ encoding: "utf8",
514820
+ flag: "wx",
514821
+ mode: 384
514822
+ });
514823
+ return receiptPath;
514824
+ } catch {
514825
+ return null;
514826
+ }
514827
+ }
514684
514828
  async function sendTelegramUpdateNotice(text, options = {}) {
514685
514829
  const stateDir = options.stateDir ?? defaultTelegramStateDir();
514686
514830
  const configured = hasConfiguredToken(stateDir);
514687
514831
  const recipients = configured ? readTelegramUpdateRecipients(stateDir) : [];
514688
514832
  const send = options.send ?? ((chatId, message) => sendReplyFallback(chatId, message));
514689
514833
  let sent = 0;
514690
- for (const chatId of recipients) if (await send(chatId, text).catch(() => false)) sent += 1;
514834
+ let attempted = 0;
514835
+ for (const chatId of recipients) {
514836
+ const receiptPath = options.version === void 0 ? void 0 : claimTelegramUpdateNotice(options.version, chatId, options.receiptDir);
514837
+ if (options.version !== void 0 && receiptPath === null) continue;
514838
+ attempted += 1;
514839
+ if (await send(chatId, text).catch(() => false)) sent += 1;
514840
+ else if (receiptPath !== void 0) try {
514841
+ unlinkSync(receiptPath);
514842
+ } catch {}
514843
+ }
514691
514844
  return {
514692
514845
  configured,
514693
- attempted: recipients.length,
514846
+ attempted,
514694
514847
  sent
514695
514848
  };
514696
514849
  }
@@ -514843,7 +514996,7 @@ async function runShell(opts, version, updateStartupNotice) {
514843
514996
  workDir,
514844
514997
  startupNotice: configWarning,
514845
514998
  onStartupNoticeShown: updateStartupNotice === void 0 || updateNoticeText === void 0 ? void 0 : async () => {
514846
- const delivery = await sendTelegramUpdateNotice(updateNoticeText);
514999
+ const delivery = await sendTelegramUpdateNotice(updateNoticeText, { version: updateStartupNotice.details.version });
514847
515000
  if (!delivery.configured || delivery.attempted === 0 || delivery.sent === delivery.attempted) await updateStartupNotice.acknowledge();
514848
515001
  }
514849
515002
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.45",
3
+ "version": "9.1.46",
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": {