blun-king-cli 9.1.284 → 9.1.285

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.
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs/promises');
5
+ const path = require('node:path');
6
+
7
+ const MAX_INPUT_DRAFT_BYTES = 64 * 1024;
8
+ const MAX_INPUT_DRAFT_FILE_BYTES = MAX_INPUT_DRAFT_BYTES + 1024;
9
+ const SCHEMA_VERSION = 1;
10
+ const VALID_MODES = new Set(['bash', 'prompt']);
11
+
12
+ function normalizeInputDraft(value) {
13
+ if (!value || typeof value !== 'object') return null;
14
+ if (!VALID_MODES.has(value.mode) || typeof value.text !== 'string') return null;
15
+ if (value.text.length === 0) return null;
16
+ if (Buffer.byteLength(value.text, 'utf8') > MAX_INPUT_DRAFT_BYTES) return null;
17
+ return { mode: value.mode, text: value.text };
18
+ }
19
+
20
+ async function clearInputDraft(filePath) {
21
+ await fs.rm(path.resolve(filePath), { force: true });
22
+ }
23
+
24
+ async function readInputDraft(filePath) {
25
+ const target = path.resolve(filePath);
26
+ try {
27
+ const stat = await fs.lstat(target);
28
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_INPUT_DRAFT_FILE_BYTES) return null;
29
+ const record = JSON.parse(await fs.readFile(target, 'utf8'));
30
+ if (record?.schemaVersion !== SCHEMA_VERSION) return null;
31
+ return normalizeInputDraft(record);
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ async function writeInputDraft(filePath, value) {
38
+ const target = path.resolve(filePath);
39
+ const text = typeof value?.text === 'string' ? value.text : '';
40
+ if (Buffer.byteLength(text, 'utf8') > MAX_INPUT_DRAFT_BYTES) {
41
+ await clearInputDraft(target);
42
+ throw new Error('draft_too_large');
43
+ }
44
+ if (text.length === 0) {
45
+ await clearInputDraft(target);
46
+ return;
47
+ }
48
+ const draft = normalizeInputDraft(value);
49
+ if (draft === null) throw new Error('draft_invalid');
50
+
51
+ const directory = path.dirname(target);
52
+ const temporary = path.join(
53
+ directory,
54
+ `.${path.basename(target)}.${process.pid}.${crypto.randomUUID()}.tmp`,
55
+ );
56
+ await fs.mkdir(directory, { recursive: true, mode: 0o700 });
57
+ try {
58
+ await fs.writeFile(
59
+ temporary,
60
+ `${JSON.stringify({ schemaVersion: SCHEMA_VERSION, ...draft })}\n`,
61
+ { encoding: 'utf8', flag: 'wx', mode: 0o600 },
62
+ );
63
+ await fs.rename(temporary, target);
64
+ await fs.chmod(target, 0o600).catch(() => {});
65
+ } catch (error) {
66
+ await fs.rm(temporary, { force: true }).catch(() => {});
67
+ throw error;
68
+ }
69
+ }
70
+
71
+ module.exports = {
72
+ MAX_INPUT_DRAFT_BYTES,
73
+ clearInputDraft,
74
+ normalizeInputDraft,
75
+ readInputDraft,
76
+ writeInputDraft,
77
+ };
package/blun.mjs CHANGED
@@ -328722,6 +328722,7 @@ const BLUN_UPDATE_STATE_FILE_NAME = "latest.json";
328722
328722
  const BLUN_UPDATE_INSTALL_STATE_FILE_NAME = "install.json";
328723
328723
  const BLUN_UPDATE_ROLLOUT_LOG_FILE_NAME = "rollout.log";
328724
328724
  const BLUN_INPUT_HISTORY_DIR_NAME = "user-history";
328725
+ const BLUN_INPUT_DRAFT_DIR_NAME = "input-drafts";
328725
328726
  const BLUN_BANNER_DIR_NAME = "banner";
328726
328727
  const BLUN_BANNER_STATE_FILE_NAME = "state.json";
328727
328728
  const BLUN_STARTUP_DIR_NAME = "startup";
@@ -340176,6 +340177,10 @@ function getInputHistoryFile(workDir) {
340176
340177
  const hash = createHash("md5").update(workDir, "utf-8").digest("hex");
340177
340178
  return join(getDataDir(), BLUN_INPUT_HISTORY_DIR_NAME, `${hash}.jsonl`);
340178
340179
  }
340180
+ function getInputDraftFile(workDir, profile) {
340181
+ const hash = createHash("sha256").update(`${profile}\0${workDir}`, "utf-8").digest("hex");
340182
+ return join(getDataDir(), BLUN_INPUT_DRAFT_DIR_NAME, `${hash}.json`);
340183
+ }
340179
340184
  //#endregion
340180
340185
  //#region src/cli/build-info.ts
340181
340186
  function optionalBuildString(value) {
@@ -496904,6 +496909,7 @@ async function appendJsonlLine(filePath, lineSchema, value) {
496904
496909
  //#region src/utils/history/input-history.ts
496905
496910
  init_zod$1();
496906
496911
  const InputHistoryEntrySchema = object({ content: string() });
496912
+ var { readInputDraft, writeInputDraft } = createRequire(import.meta.url)("./bin/input-draft-persistence.cjs");
496907
496913
  async function loadInputHistory(file) {
496908
496914
  return readJsonlFile(file, InputHistoryEntrySchema);
496909
496915
  }
@@ -504234,6 +504240,7 @@ var EditorKeyboardController = class {
504234
504240
  if (!editor.wasGhostAcceptance()) this.inlineSuggest?.notifyActivity();
504235
504241
  if (this.pendingExit) this.clearPendingExit();
504236
504242
  host.updateEditorBorderHighlight(text);
504243
+ host.scheduleInputDraftPersist(text, editor.inputMode);
504237
504244
  };
504238
504245
  let browseMode = null;
504239
504246
  editor.setHistoryFilter((entry) => {
@@ -504360,6 +504367,7 @@ var EditorKeyboardController = class {
504360
504367
  };
504361
504368
  editor.onInputModeChange = (mode) => {
504362
504369
  host.handleInputModeChange(mode);
504370
+ host.scheduleInputDraftPersist(editor.getText(), mode);
504363
504371
  };
504364
504372
  editor.onOpenExternalEditor = () => {
504365
504373
  host.track("shortcut_editor");
@@ -515552,6 +515560,9 @@ var BlunTUI = class {
515552
515560
  mediaActivityTickTimer;
515553
515561
  mediaActivityExpanded = false;
515554
515562
  lastHistoryContent;
515563
+ inputDraftTimer;
515564
+ pendingInputDraft;
515565
+ inputDraftWrite = Promise.resolve();
515555
515566
  shellOutputStreams = /* @__PURE__ */ new Map();
515556
515567
  streamingUI;
515557
515568
  authFlow;
@@ -515798,6 +515809,7 @@ var BlunTUI = class {
515798
515809
  this.loadPersistedInputHistory();
515799
515810
  this.state.editorContainer.clear();
515800
515811
  this.state.editorContainer.addChild(this.state.editor);
515812
+ await this.loadPersistedInputDraft();
515801
515813
  this.state.ui.setFocus(this.state.editor);
515802
515814
  return shouldReplayHistory;
515803
515815
  }
@@ -516069,6 +516081,7 @@ var BlunTUI = class {
516069
516081
  async stop(exitCode) {
516070
516082
  if (this.isShuttingDown) return;
516071
516083
  this.isShuttingDown = true;
516084
+ await this.flushInputDraft();
516072
516085
  if (exitCode === 0 && process.connected) try {
516073
516086
  process.send({ type: RUNTIME_EXIT_INTENT_MESSAGE });
516074
516087
  } catch {}
@@ -516952,6 +516965,40 @@ var BlunTUI = class {
516952
516965
  this.managedImageReaderAvailable = false;
516953
516966
  }
516954
516967
  }
516968
+ inputDraftFile() {
516969
+ return getInputDraftFile(
516970
+ this.state.appState.workDir,
516971
+ process.env["BLUN_PROFILE"] ?? "default"
516972
+ );
516973
+ }
516974
+ async loadPersistedInputDraft() {
516975
+ const draft = await readInputDraft(this.inputDraftFile());
516976
+ if (draft === null || this.state.editor.getText().length > 0) return;
516977
+ this.state.editor.setInputMode(draft.mode);
516978
+ this.state.editor.setText(draft.text);
516979
+ this.updateEditorBorderHighlight(draft.text);
516980
+ }
516981
+ scheduleInputDraftPersist(text, mode) {
516982
+ this.pendingInputDraft = { text, mode };
516983
+ if (this.inputDraftTimer !== void 0) clearTimeout(this.inputDraftTimer);
516984
+ this.inputDraftTimer = setTimeout(() => {
516985
+ this.inputDraftTimer = void 0;
516986
+ void this.flushInputDraft();
516987
+ }, 300);
516988
+ }
516989
+ async flushInputDraft() {
516990
+ if (this.inputDraftTimer !== void 0) {
516991
+ clearTimeout(this.inputDraftTimer);
516992
+ this.inputDraftTimer = void 0;
516993
+ }
516994
+ const draft = this.pendingInputDraft;
516995
+ this.pendingInputDraft = void 0;
516996
+ if (draft === void 0) return this.inputDraftWrite;
516997
+ this.inputDraftWrite = this.inputDraftWrite
516998
+ .then(() => writeInputDraft(this.inputDraftFile(), draft))
516999
+ .catch(() => {});
517000
+ return this.inputDraftWrite;
517001
+ }
516955
517002
  async loadPersistedInputHistory() {
516956
517003
  try {
516957
517004
  const entries = await loadInputHistory(getInputHistoryFile(this.state.appState.workDir));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.284",
3
+ "version": "9.1.285",
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": {