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.
- package/bin/input-draft-persistence.cjs +77 -0
- package/blun.mjs +47 -0
- package/package.json +1 -1
|
@@ -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));
|