laohuang 0.9.3 → 0.9.5
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/dist/bin.js +266 -15
- package/dist/bin.js.map +4 -4
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -7382,7 +7382,7 @@ process.emit = new Proxy(process.emit, {
|
|
|
7382
7382
|
|
|
7383
7383
|
// src/main.ts
|
|
7384
7384
|
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
|
|
7385
|
-
import { dirname as dirname13, join as
|
|
7385
|
+
import { dirname as dirname13, join as join21 } from "node:path";
|
|
7386
7386
|
import { fileURLToPath } from "node:url";
|
|
7387
7387
|
|
|
7388
7388
|
// ../../packages/session/session-context/dist/conversation-history.js
|
|
@@ -23522,7 +23522,8 @@ var CONTROL_KEYS = /* @__PURE__ */ new Map([
|
|
|
23522
23522
|
[12, makeKeyInput("ctrl_l", { ctrl: true })],
|
|
23523
23523
|
[15, makeKeyInput("character", { text: "o", ctrl: true })],
|
|
23524
23524
|
[19, makeKeyInput("character", { text: "s", ctrl: true })],
|
|
23525
|
-
[20, makeKeyInput("character", { text: "t", ctrl: true })]
|
|
23525
|
+
[20, makeKeyInput("character", { text: "t", ctrl: true })],
|
|
23526
|
+
[22, makeKeyInput("character", { text: "v", ctrl: true })]
|
|
23526
23527
|
]);
|
|
23527
23528
|
var ESCAPE_INCOMPLETE = { status: "incomplete" };
|
|
23528
23529
|
var ESCAPE_CONSUMED = { status: "consumed" };
|
|
@@ -23669,6 +23670,10 @@ var RawInputDecoder = class {
|
|
|
23669
23670
|
this.buffer = this.buffer.subarray(3);
|
|
23670
23671
|
return kind !== void 0 ? { status: "action", action: inputAction(kind) } : ESCAPE_CONSUMED;
|
|
23671
23672
|
}
|
|
23673
|
+
if (second === 118) {
|
|
23674
|
+
this.buffer = this.buffer.subarray(2);
|
|
23675
|
+
return { status: "action", action: inputAction(InputActionKind.Key, "", makeKeyInput("character", { text: "v", alt: true })) };
|
|
23676
|
+
}
|
|
23672
23677
|
this.buffer = this.buffer.subarray(1);
|
|
23673
23678
|
return ESCAPE_CONSUMED;
|
|
23674
23679
|
}
|
|
@@ -23956,7 +23961,7 @@ function decodeSpecialEscapeAction(sequence) {
|
|
|
23956
23961
|
return kind !== void 0 ? inputAction(kind) : null;
|
|
23957
23962
|
}
|
|
23958
23963
|
function decodeModifiedControlKey(sequence) {
|
|
23959
|
-
const kitty = /^\x1b\[(13|57414|9|117)(?::\d*)?(?::\d+)?;(\d+)(?::(\d+))?u$/.exec(sequence);
|
|
23964
|
+
const kitty = /^\x1b\[(13|57414|9|117|118)(?::\d*)?(?::\d+)?;(\d+)(?::(\d+))?u$/.exec(sequence);
|
|
23960
23965
|
if (kitty !== null) {
|
|
23961
23966
|
const code = kitty[1];
|
|
23962
23967
|
const modifier2 = Number.parseInt(kitty[2], 10) - 1;
|
|
@@ -23966,6 +23971,9 @@ function decodeModifiedControlKey(sequence) {
|
|
|
23966
23971
|
if (!isKittyPressEvent(kitty[3])) {
|
|
23967
23972
|
return null;
|
|
23968
23973
|
}
|
|
23974
|
+
if (code === "118" && (matchesKittyModifiers(modifier2, 4) || matchesKittyModifiers(modifier2, 2))) {
|
|
23975
|
+
return inputAction(InputActionKind.Key, "", makeKeyInput("character", { text: "v", ctrl: matchesKittyModifiers(modifier2, 4), alt: matchesKittyModifiers(modifier2, 2) }));
|
|
23976
|
+
}
|
|
23969
23977
|
if ((code === "13" || code === "57414") && matchesKittyModifiers(modifier2, 2)) {
|
|
23970
23978
|
return inputAction(InputActionKind.Key, "", makeKeyInput("enter", { alt: true }));
|
|
23971
23979
|
}
|
|
@@ -23980,6 +23988,9 @@ function decodeModifiedControlKey(sequence) {
|
|
|
23980
23988
|
}
|
|
23981
23989
|
const modifier = Number.parseInt(modifyOtherKeys[1], 10) - 1;
|
|
23982
23990
|
const codepoint = Number.parseInt(modifyOtherKeys[2], 10);
|
|
23991
|
+
if (codepoint === 118 && (modifier === 4 || modifier === 2)) {
|
|
23992
|
+
return inputAction(InputActionKind.Key, "", makeKeyInput("character", { text: "v", ctrl: modifier === 4, alt: modifier === 2 }));
|
|
23993
|
+
}
|
|
23983
23994
|
if (codepoint === 117 && modifier === 4) {
|
|
23984
23995
|
return inputAction(InputActionKind.DeleteToLineStart);
|
|
23985
23996
|
}
|
|
@@ -24330,6 +24341,7 @@ var TerminalInputDecoder = class {
|
|
|
24330
24341
|
|
|
24331
24342
|
// ../../packages/terminal/tui/dist/keybindings/default-keybindings.js
|
|
24332
24343
|
var DEFAULT_KEYBINDINGS = [
|
|
24344
|
+
{ context: "editor", key: process.platform === "win32" ? "alt+v" : "ctrl+v", action: "paste_clipboard" },
|
|
24333
24345
|
{ context: "terminal", key: "ctrl+o", action: "toggle_tool_output" },
|
|
24334
24346
|
{ context: "terminal", key: "ctrl+l", action: "select_model" },
|
|
24335
24347
|
{ context: "terminal", key: "ctrl+t", action: "toggle_thinking" },
|
|
@@ -27498,6 +27510,7 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
|
|
|
27498
27510
|
#renderTimer = null;
|
|
27499
27511
|
#lastRenderAt = 0;
|
|
27500
27512
|
#disposeResize = null;
|
|
27513
|
+
#clipboard = null;
|
|
27501
27514
|
writeError = null;
|
|
27502
27515
|
constructor(ui, driver, editor, decoderFactory) {
|
|
27503
27516
|
this.#ui = ui;
|
|
@@ -27600,6 +27613,7 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
|
|
|
27600
27613
|
return this.#viewHost.render(context);
|
|
27601
27614
|
}
|
|
27602
27615
|
requestExit() {
|
|
27616
|
+
this.invalidateClipboardPaste();
|
|
27603
27617
|
this.#exitRequested = true;
|
|
27604
27618
|
this.#exitResolve?.();
|
|
27605
27619
|
}
|
|
@@ -27608,6 +27622,7 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
|
|
|
27608
27622
|
return;
|
|
27609
27623
|
}
|
|
27610
27624
|
this.#closed = true;
|
|
27625
|
+
this.invalidateClipboardPaste();
|
|
27611
27626
|
if (this.#renderTimer !== null) {
|
|
27612
27627
|
clearTimeout(this.#renderTimer);
|
|
27613
27628
|
this.#renderTimer = null;
|
|
@@ -27814,14 +27829,29 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
|
|
|
27814
27829
|
}
|
|
27815
27830
|
changed = true;
|
|
27816
27831
|
} else if (item.type === "open_selection") {
|
|
27832
|
+
this.invalidateClipboardPaste();
|
|
27817
27833
|
this.#viewHost.openSelection(item.request).then(item.resolve);
|
|
27818
27834
|
changed = true;
|
|
27819
27835
|
} else if (item.type === "open_prompt") {
|
|
27836
|
+
this.invalidateClipboardPaste();
|
|
27820
27837
|
this.#viewHost.openPrompt(item.request).then(item.resolve);
|
|
27821
27838
|
changed = true;
|
|
27822
27839
|
} else if (item.type === "close_view") {
|
|
27823
27840
|
this.#viewHost.closeActive(item.value);
|
|
27824
27841
|
changed = true;
|
|
27842
|
+
} else if (item.type === "clipboard") {
|
|
27843
|
+
if (this.#clipboard === item.controller)
|
|
27844
|
+
this.#clipboard = null;
|
|
27845
|
+
if (item.controller.signal.aborted) {
|
|
27846
|
+
this.#appendNotice("Clipboard paste cancelled because input changed; paste again.");
|
|
27847
|
+
} else if (item.error) {
|
|
27848
|
+
this.#appendNotice(item.error);
|
|
27849
|
+
} else if (item.text) {
|
|
27850
|
+
this.#applyEditorAction(inputAction(InputActionKind.Insert, item.text));
|
|
27851
|
+
} else {
|
|
27852
|
+
this.#appendNotice("Clipboard has no image or text.");
|
|
27853
|
+
}
|
|
27854
|
+
changed = true;
|
|
27825
27855
|
} else if (item.event instanceof LocalMessage) {
|
|
27826
27856
|
const message = item.event;
|
|
27827
27857
|
this.#ui.appendTranscript(createNoticeBlock(this.#ui.newBlockId(), message.text, message.tone));
|
|
@@ -27856,6 +27886,7 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
|
|
|
27856
27886
|
#applyActions(actions) {
|
|
27857
27887
|
for (const action of actions) {
|
|
27858
27888
|
if (action.kind === InputActionKind.Newline) {
|
|
27889
|
+
this.invalidateClipboardPaste();
|
|
27859
27890
|
if (this.#viewHost.activeId() !== null) {
|
|
27860
27891
|
this.#viewHost.handleInput({ type: "text", text: "\n" });
|
|
27861
27892
|
this.#needsRender = true;
|
|
@@ -27868,6 +27899,12 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
|
|
|
27868
27899
|
}
|
|
27869
27900
|
}
|
|
27870
27901
|
#applyInputEvent(event) {
|
|
27902
|
+
const clipboardAction = event.type === "key" && this.#ui.keybindings.resolve(event.key, ["editor"]) === "paste_clipboard";
|
|
27903
|
+
if (clipboardAction && this.#viewHost.activeId() === null) {
|
|
27904
|
+
this.#pasteClipboard();
|
|
27905
|
+
return;
|
|
27906
|
+
}
|
|
27907
|
+
this.invalidateClipboardPaste();
|
|
27871
27908
|
if (this.#viewHost.handleInput(event)) {
|
|
27872
27909
|
this.#needsRender = true;
|
|
27873
27910
|
return;
|
|
@@ -27887,6 +27924,8 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
|
|
|
27887
27924
|
}
|
|
27888
27925
|
}
|
|
27889
27926
|
#applyKeyAction(action) {
|
|
27927
|
+
if (action === "paste_clipboard")
|
|
27928
|
+
return;
|
|
27890
27929
|
const capability = ACTION_CAPABILITIES[action];
|
|
27891
27930
|
if (capability !== void 0 && !this.#ui.capabilities[capability]) {
|
|
27892
27931
|
this.#appendNotice(unavailableActionNotice(action));
|
|
@@ -27996,6 +28035,7 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
|
|
|
27996
28035
|
return true;
|
|
27997
28036
|
}
|
|
27998
28037
|
#applyEof() {
|
|
28038
|
+
this.invalidateClipboardPaste();
|
|
27999
28039
|
const effect = this.#editor.apply(inputAction(InputActionKind.Eof), { runtimeActive: this.#ui.isRunning() });
|
|
28000
28040
|
this.#applyEffect(effect);
|
|
28001
28041
|
this.#needsRender = true;
|
|
@@ -28018,6 +28058,27 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
|
|
|
28018
28058
|
#appendNotice(text3) {
|
|
28019
28059
|
this.#ui.appendTranscript(createNoticeBlock(this.#ui.newBlockId(), text3, "warning"));
|
|
28020
28060
|
}
|
|
28061
|
+
invalidateClipboardPaste() {
|
|
28062
|
+
this.#clipboard?.abort();
|
|
28063
|
+
}
|
|
28064
|
+
#pasteClipboard() {
|
|
28065
|
+
if (this.#clipboard)
|
|
28066
|
+
return;
|
|
28067
|
+
const read = this.#ui.clipboardReader;
|
|
28068
|
+
if (!read) {
|
|
28069
|
+
this.#appendNotice("Clipboard paste is unavailable in this host.");
|
|
28070
|
+
return;
|
|
28071
|
+
}
|
|
28072
|
+
const controller = new AbortController();
|
|
28073
|
+
this.#clipboard = controller;
|
|
28074
|
+
const deliver = (text3, error) => {
|
|
28075
|
+
if (this.#closed || this.#exitRequested)
|
|
28076
|
+
return;
|
|
28077
|
+
this.#work.push({ type: "clipboard", controller, text: text3, error });
|
|
28078
|
+
this.#scheduleWakeup();
|
|
28079
|
+
};
|
|
28080
|
+
void Promise.resolve().then(() => read(controller.signal)).then((text3) => deliver(text3), (error) => deliver(null, error instanceof Error ? error.message : "Clipboard paste failed."));
|
|
28081
|
+
}
|
|
28021
28082
|
#refreshCompletions() {
|
|
28022
28083
|
const registry = this.#ui.commandRegistry;
|
|
28023
28084
|
if (registry !== null) {
|
|
@@ -28076,6 +28137,7 @@ function normalizePositiveInteger(value) {
|
|
|
28076
28137
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
|
|
28077
28138
|
}
|
|
28078
28139
|
var TerminalUI = class {
|
|
28140
|
+
clipboardReader;
|
|
28079
28141
|
theme;
|
|
28080
28142
|
state;
|
|
28081
28143
|
reducer;
|
|
@@ -28106,6 +28168,7 @@ var TerminalUI = class {
|
|
|
28106
28168
|
#pendingDisplayDrops = 0;
|
|
28107
28169
|
#keyActionCallback;
|
|
28108
28170
|
constructor(options = {}) {
|
|
28171
|
+
this.clipboardReader = options.clipboardReader;
|
|
28109
28172
|
this.theme = resolveTerminalTheme(options.theme);
|
|
28110
28173
|
this.projectRoot = options.projectRoot ?? null;
|
|
28111
28174
|
this.provider = options.provider ?? null;
|
|
@@ -28224,6 +28287,8 @@ var TerminalUI = class {
|
|
|
28224
28287
|
Object.assign(this.capabilities, capabilities);
|
|
28225
28288
|
}
|
|
28226
28289
|
setSessionId(sessionId) {
|
|
28290
|
+
if (sessionId !== this.#sessionId)
|
|
28291
|
+
this.#loop?.invalidateClipboardPaste();
|
|
28227
28292
|
if (sessionId !== this.#sessionId)
|
|
28228
28293
|
this.state.contextTokens = 0;
|
|
28229
28294
|
this.#sessionId = sessionId;
|
|
@@ -28243,6 +28308,7 @@ var TerminalUI = class {
|
|
|
28243
28308
|
this.#loop?.requestRender();
|
|
28244
28309
|
}
|
|
28245
28310
|
setComposerText(text3) {
|
|
28311
|
+
this.#loop?.invalidateClipboardPaste();
|
|
28246
28312
|
const editor = this.#loop?.editor;
|
|
28247
28313
|
if (editor === void 0) {
|
|
28248
28314
|
return;
|
|
@@ -39430,6 +39496,160 @@ async function copyText(text3, options) {
|
|
|
39430
39496
|
return terminalCopy(text3, options, failures);
|
|
39431
39497
|
}
|
|
39432
39498
|
|
|
39499
|
+
// src/clipboard-paste.ts
|
|
39500
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
39501
|
+
import { mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
|
|
39502
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
39503
|
+
import { join as join20 } from "node:path";
|
|
39504
|
+
import { randomUUID as randomUUID18 } from "node:crypto";
|
|
39505
|
+
import sharp3 from "sharp";
|
|
39506
|
+
var MAX_OUTPUT = 30 * 1024 * 1024;
|
|
39507
|
+
var MAX_TEXT = 1024 * 1024;
|
|
39508
|
+
var MAC_CLIPBOARD_SCRIPT = `
|
|
39509
|
+
ObjC.import('AppKit');
|
|
39510
|
+
function run() {
|
|
39511
|
+
var pb = $.NSPasteboard.generalPasteboard;
|
|
39512
|
+
var data = pb.dataForType($.NSPasteboardTypePNG);
|
|
39513
|
+
if (!data.js) {
|
|
39514
|
+
var tiff = pb.dataForType($.NSPasteboardTypeTIFF);
|
|
39515
|
+
if (tiff.js) {
|
|
39516
|
+
var bitmap = $.NSBitmapImageRep.imageRepWithData(tiff);
|
|
39517
|
+
if (!bitmap.js) throw new Error('Cannot decode clipboard image');
|
|
39518
|
+
if (bitmap.pixelsWide * bitmap.pixelsHigh > 64000000) throw new Error('Clipboard image exceeds pixel limit');
|
|
39519
|
+
data = bitmap.representationUsingTypeProperties($.NSPNGFileType, $({}));
|
|
39520
|
+
if (!data.js) throw new Error('Cannot encode clipboard image');
|
|
39521
|
+
}
|
|
39522
|
+
}
|
|
39523
|
+
if (data.js) {
|
|
39524
|
+
if (data.length > 20971520) throw new Error('Clipboard image exceeds 20 MiB');
|
|
39525
|
+
return JSON.stringify({kind:'image', base64:ObjC.unwrap(data.base64EncodedStringWithOptions(0))});
|
|
39526
|
+
}
|
|
39527
|
+
var text = pb.stringForType($.NSPasteboardTypeString);
|
|
39528
|
+
return JSON.stringify(text.js ? {kind:'text', text:ObjC.unwrap(text)} : {kind:'empty'});
|
|
39529
|
+
}`;
|
|
39530
|
+
var WINDOWS_CLIPBOARD_SCRIPT = `
|
|
39531
|
+
$ErrorActionPreference = 'Stop'
|
|
39532
|
+
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
39533
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
39534
|
+
Add-Type -AssemblyName System.Drawing
|
|
39535
|
+
$image = [System.Windows.Forms.Clipboard]::GetImage()
|
|
39536
|
+
if ($null -ne $image) {
|
|
39537
|
+
$stream = [System.IO.MemoryStream]::new()
|
|
39538
|
+
try {
|
|
39539
|
+
if ([long]$image.Width * $image.Height -gt 64000000) { throw 'Clipboard image exceeds pixel limit' }
|
|
39540
|
+
$image.Save($stream, [System.Drawing.Imaging.ImageFormat]::Png)
|
|
39541
|
+
if ($stream.Length -gt 20971520) { throw 'Clipboard image exceeds 20 MiB' }
|
|
39542
|
+
@{kind='image'; base64=[Convert]::ToBase64String($stream.ToArray())} | ConvertTo-Json -Compress
|
|
39543
|
+
} finally { $stream.Dispose(); $image.Dispose() }
|
|
39544
|
+
} elseif ([System.Windows.Forms.Clipboard]::ContainsText()) {
|
|
39545
|
+
@{kind='text'; text=[System.Windows.Forms.Clipboard]::GetText()} | ConvertTo-Json -Compress
|
|
39546
|
+
} else { @{kind='empty'} | ConvertTo-Json -Compress }
|
|
39547
|
+
`;
|
|
39548
|
+
function clipboardReadCommand(platform) {
|
|
39549
|
+
if (platform === "darwin") return { command: "/usr/bin/osascript", args: ["-l", "JavaScript", "-e", MAC_CLIPBOARD_SCRIPT] };
|
|
39550
|
+
if (platform === "win32") return { command: "powershell.exe", args: ["-NoLogo", "-NoProfile", "-NonInteractive", "-STA", "-EncodedCommand", Buffer.from(WINDOWS_CLIPBOARD_SCRIPT, "utf16le").toString("base64")] };
|
|
39551
|
+
throw new Error("Image clipboard paste requires native macOS or Windows; use a local image path on this platform.");
|
|
39552
|
+
}
|
|
39553
|
+
function createClipboardReadRunner(env, timeoutMs = 5e3) {
|
|
39554
|
+
return (command, args, signal) => new Promise((resolve15, reject) => {
|
|
39555
|
+
signal.throwIfAborted();
|
|
39556
|
+
const child = spawn4(command, [...args], { env: { ...env }, shell: false, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
39557
|
+
const chunks = [];
|
|
39558
|
+
let bytes = 0;
|
|
39559
|
+
let settled = false;
|
|
39560
|
+
const finish = (error) => {
|
|
39561
|
+
if (settled) return;
|
|
39562
|
+
settled = true;
|
|
39563
|
+
clearTimeout(timer);
|
|
39564
|
+
signal.removeEventListener("abort", abort);
|
|
39565
|
+
if (error) {
|
|
39566
|
+
child.kill("SIGKILL");
|
|
39567
|
+
reject(error);
|
|
39568
|
+
} else resolve15(Buffer.concat(chunks).toString("utf8"));
|
|
39569
|
+
};
|
|
39570
|
+
const abort = () => finish(new Error("Clipboard paste cancelled."));
|
|
39571
|
+
const timer = setTimeout(() => finish(new Error("Clipboard read timed out.")), timeoutMs);
|
|
39572
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
39573
|
+
child.stdout.on("data", (chunk) => {
|
|
39574
|
+
bytes += chunk.length;
|
|
39575
|
+
if (bytes > MAX_OUTPUT) finish(new Error("Clipboard content exceeds the size limit."));
|
|
39576
|
+
else if (!settled) chunks.push(chunk);
|
|
39577
|
+
});
|
|
39578
|
+
child.stderr.resume();
|
|
39579
|
+
child.once("error", () => finish(new Error("Cannot start the system clipboard reader.")));
|
|
39580
|
+
child.once("close", (code) => finish(code === 0 ? void 0 : new Error("System clipboard read failed; check clipboard access and try again.")));
|
|
39581
|
+
if (signal.aborted) abort();
|
|
39582
|
+
});
|
|
39583
|
+
}
|
|
39584
|
+
function createClipboardPaste(options) {
|
|
39585
|
+
const run = options.run ?? createClipboardReadRunner(options.env);
|
|
39586
|
+
let directory;
|
|
39587
|
+
let closed = false;
|
|
39588
|
+
let pending;
|
|
39589
|
+
const read = async (signal) => {
|
|
39590
|
+
signal.throwIfAborted();
|
|
39591
|
+
if (closed) throw new Error("Clipboard reader is closed.");
|
|
39592
|
+
if (options.env.SSH_CONNECTION || options.env.SSH_TTY) throw new Error("Remote clipboard reading is unavailable over SSH; use a remote image path.");
|
|
39593
|
+
const command = clipboardReadCommand(options.platform);
|
|
39594
|
+
const raw = await run(command.command, command.args, signal);
|
|
39595
|
+
signal.throwIfAborted();
|
|
39596
|
+
let value;
|
|
39597
|
+
try {
|
|
39598
|
+
value = JSON.parse(raw.replace(/^\uFEFF/, ""));
|
|
39599
|
+
} catch {
|
|
39600
|
+
throw new Error("Invalid response from clipboard reader.");
|
|
39601
|
+
}
|
|
39602
|
+
if (!value || typeof value !== "object" || !("kind" in value)) throw new Error("Invalid clipboard content.");
|
|
39603
|
+
if (value.kind === "empty") return null;
|
|
39604
|
+
if (value.kind === "text" && "text" in value && typeof value.text === "string") {
|
|
39605
|
+
if (Buffer.byteLength(value.text) > MAX_TEXT) throw new Error("Clipboard text exceeds 1 MiB.");
|
|
39606
|
+
return value.text.replace(/\r\n?/g, "\n").replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "");
|
|
39607
|
+
}
|
|
39608
|
+
if (value.kind !== "image" || !("base64" in value) || typeof value.base64 !== "string" || value.base64.length > Math.ceil(DEFAULT_IMAGE_LIMITS.maxBytes / 3) * 4 || value.base64.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(value.base64)) {
|
|
39609
|
+
throw new Error("Invalid or oversized clipboard image.");
|
|
39610
|
+
}
|
|
39611
|
+
const data = Buffer.from(value.base64, "base64");
|
|
39612
|
+
if (data.toString("base64") !== value.base64) throw new Error("Invalid clipboard image encoding.");
|
|
39613
|
+
if (!data.length || data.length > DEFAULT_IMAGE_LIMITS.maxBytes) throw new Error("Clipboard image exceeds size limits.");
|
|
39614
|
+
const image = sharp3(data, { limitInputPixels: DEFAULT_IMAGE_LIMITS.maxPixels });
|
|
39615
|
+
try {
|
|
39616
|
+
const meta = await image.metadata();
|
|
39617
|
+
if (meta.format !== "png" || !meta.width || !meta.height || Math.max(meta.width, meta.height) > DEFAULT_IMAGE_LIMITS.maxDimension) {
|
|
39618
|
+
throw new Error("Invalid image");
|
|
39619
|
+
}
|
|
39620
|
+
await image.stats();
|
|
39621
|
+
} catch {
|
|
39622
|
+
throw new Error("Clipboard image is invalid or exceeds image limits.");
|
|
39623
|
+
}
|
|
39624
|
+
signal.throwIfAborted();
|
|
39625
|
+
directory ??= await mkdtemp2(join20(options.tempRoot ?? tmpdir2(), "laohuang-clipboard-"));
|
|
39626
|
+
const path5 = join20(directory, `${randomUUID18()}.png`);
|
|
39627
|
+
try {
|
|
39628
|
+
await writeFile5(path5, data, { flag: "wx", mode: 384 });
|
|
39629
|
+
signal.throwIfAborted();
|
|
39630
|
+
return ` ${JSON.stringify(path5)} `;
|
|
39631
|
+
} catch (error) {
|
|
39632
|
+
await rm2(path5, { force: true });
|
|
39633
|
+
throw error;
|
|
39634
|
+
}
|
|
39635
|
+
};
|
|
39636
|
+
return {
|
|
39637
|
+
read(signal) {
|
|
39638
|
+
if (pending) return Promise.reject(new Error("Clipboard paste already in progress."));
|
|
39639
|
+
pending = read(signal).finally(() => {
|
|
39640
|
+
pending = void 0;
|
|
39641
|
+
});
|
|
39642
|
+
return pending;
|
|
39643
|
+
},
|
|
39644
|
+
async close() {
|
|
39645
|
+
closed = true;
|
|
39646
|
+
await pending?.catch(() => {
|
|
39647
|
+
});
|
|
39648
|
+
if (directory) await rm2(directory, { recursive: true, force: true });
|
|
39649
|
+
}
|
|
39650
|
+
};
|
|
39651
|
+
}
|
|
39652
|
+
|
|
39433
39653
|
// src/main.ts
|
|
39434
39654
|
var VERSION = readPackageVersion();
|
|
39435
39655
|
function readPackageVersion() {
|
|
@@ -39535,13 +39755,13 @@ async function main(argv, options = {}) {
|
|
|
39535
39755
|
const configPath = options.configPath ?? defaultConfigPath(environ);
|
|
39536
39756
|
const manager = new ConfigManager(configPath);
|
|
39537
39757
|
const credentials = new CredentialStore(
|
|
39538
|
-
options.credentialsPath ??
|
|
39758
|
+
options.credentialsPath ?? join21(dirname13(configPath), "credentials.json")
|
|
39539
39759
|
);
|
|
39540
39760
|
const modelCatalogStore = new ModelCatalogStore(
|
|
39541
|
-
options.modelsPath ??
|
|
39761
|
+
options.modelsPath ?? join21(dirname13(configPath), "models.json")
|
|
39542
39762
|
);
|
|
39543
39763
|
const customModels = new CustomModelsStore(
|
|
39544
|
-
options.customModelsPath ??
|
|
39764
|
+
options.customModelsPath ?? join21(dirname13(configPath), "custom-models.json")
|
|
39545
39765
|
);
|
|
39546
39766
|
let modelPlatform;
|
|
39547
39767
|
let attachmentRuntime;
|
|
@@ -39683,14 +39903,41 @@ async function main(argv, options = {}) {
|
|
|
39683
39903
|
model: args.model,
|
|
39684
39904
|
baseUrl: args.baseUrl
|
|
39685
39905
|
});
|
|
39686
|
-
|
|
39906
|
+
const validation = await validateConfiguredSelection({
|
|
39687
39907
|
config,
|
|
39688
39908
|
catalog: modelPlatform.catalog,
|
|
39689
39909
|
providerAuth,
|
|
39690
39910
|
presenter: startupPresenter
|
|
39691
|
-
})
|
|
39911
|
+
});
|
|
39912
|
+
if (validation === false) {
|
|
39692
39913
|
return 2;
|
|
39693
39914
|
}
|
|
39915
|
+
if (typeof validation === "string") {
|
|
39916
|
+
const canRecover = (options.stdin ?? process.stdin).isTTY === true && (options.stdout ?? process.stdout).isTTY === true && !args.model && !environ["LAOHUANG_MODEL"];
|
|
39917
|
+
if (!canRecover) throw new Error(validation);
|
|
39918
|
+
outputFn(`${validation}. Please reconfigure profile '${config.profile}'.`);
|
|
39919
|
+
let selection;
|
|
39920
|
+
try {
|
|
39921
|
+
selection = await runInitialModelSelection({ selector, presenter: startupPresenter, providerAuth });
|
|
39922
|
+
} catch (error) {
|
|
39923
|
+
if (!(error instanceof PromptCancelledError) && !(error instanceof PromptEofError)) throw error;
|
|
39924
|
+
selection = null;
|
|
39925
|
+
}
|
|
39926
|
+
if (selection === null) {
|
|
39927
|
+
outputFn("Configuration cancelled.");
|
|
39928
|
+
return 0;
|
|
39929
|
+
}
|
|
39930
|
+
const saved = manager.resolve({ environ: {}, profile: config.profile });
|
|
39931
|
+
const baseUrl = selection.config.provider === saved.provider ? saved.baseUrl : selection.config.baseUrl;
|
|
39932
|
+
manager.configure({
|
|
39933
|
+
name: config.profile,
|
|
39934
|
+
provider: selection.config.provider,
|
|
39935
|
+
model: selection.config.model,
|
|
39936
|
+
baseUrl
|
|
39937
|
+
});
|
|
39938
|
+
config = { ...config, ...selection.config, baseUrl: args.baseUrl ?? environ["LAOHUANG_BASE_URL"] ?? baseUrl };
|
|
39939
|
+
outputFn(`Saved profile '${config.profile}' to ${configPath}`);
|
|
39940
|
+
}
|
|
39694
39941
|
} else {
|
|
39695
39942
|
const selection = await runInitialModelSelection({
|
|
39696
39943
|
selector,
|
|
@@ -39719,10 +39966,11 @@ async function main(argv, options = {}) {
|
|
|
39719
39966
|
return 2;
|
|
39720
39967
|
}
|
|
39721
39968
|
let terminalUi = null;
|
|
39969
|
+
const clipboardPaste = createClipboardPaste({ platform: process.platform, env: environ });
|
|
39722
39970
|
let terminalDriver = null;
|
|
39723
39971
|
let selectedModel = modelPlatform.catalog.getModel(config.provider, config.model);
|
|
39724
39972
|
attachmentRuntime = createAttachmentRuntime({
|
|
39725
|
-
root:
|
|
39973
|
+
root: join21(getHomeDirectory({ env: environ }), ".laohuang", "attachments"),
|
|
39726
39974
|
sessionsRoot: defaultSessionsRoot(environ),
|
|
39727
39975
|
onError: (error) => writeStderr(`Attachment cleanup failed: ${errorMessage11(error)}`)
|
|
39728
39976
|
});
|
|
@@ -39764,6 +40012,7 @@ async function main(argv, options = {}) {
|
|
|
39764
40012
|
version: VERSION,
|
|
39765
40013
|
theme: args.theme,
|
|
39766
40014
|
driver: terminalDriver,
|
|
40015
|
+
clipboardReader: (signal) => clipboardPaste.read(signal),
|
|
39767
40016
|
capabilities: { reasoning: selectedModel?.reasoning ?? false },
|
|
39768
40017
|
contextWindow: selectedModel?.contextWindow
|
|
39769
40018
|
});
|
|
@@ -40035,7 +40284,11 @@ Submit an empty line to retry, or enter a replacement.`);
|
|
|
40035
40284
|
if (composedForCleanup) await composedForCleanup.close();
|
|
40036
40285
|
else await sessionController.close();
|
|
40037
40286
|
} finally {
|
|
40038
|
-
|
|
40287
|
+
try {
|
|
40288
|
+
await clipboardPaste.close();
|
|
40289
|
+
} finally {
|
|
40290
|
+
attachmentRuntime.close();
|
|
40291
|
+
}
|
|
40039
40292
|
}
|
|
40040
40293
|
}
|
|
40041
40294
|
}
|
|
@@ -40043,7 +40296,7 @@ Submit an empty line to retry, or enter a replacement.`);
|
|
|
40043
40296
|
}
|
|
40044
40297
|
}
|
|
40045
40298
|
function defaultSessionsRoot(environ) {
|
|
40046
|
-
return
|
|
40299
|
+
return join21(getHomeDirectory({ env: environ }), ".laohuang", "sessions");
|
|
40047
40300
|
}
|
|
40048
40301
|
async function runInitialModelSelection(options) {
|
|
40049
40302
|
let providerName = options.providerName;
|
|
@@ -40102,7 +40355,7 @@ async function runInitialModelSelection(options) {
|
|
|
40102
40355
|
async function validateConfiguredSelection(options) {
|
|
40103
40356
|
const provider2 = options.catalog.getProvider(options.config.provider);
|
|
40104
40357
|
if (provider2 === void 0) {
|
|
40105
|
-
|
|
40358
|
+
return `Unknown provider: ${options.config.provider}`;
|
|
40106
40359
|
}
|
|
40107
40360
|
if (!await options.providerAuth.ensureConfigured(options.config.provider, {
|
|
40108
40361
|
promptIfMissing: true,
|
|
@@ -40115,9 +40368,7 @@ async function validateConfiguredSelection(options) {
|
|
|
40115
40368
|
}
|
|
40116
40369
|
await options.catalog.refresh(options.config.provider);
|
|
40117
40370
|
if (options.catalog.getModel(options.config.provider, options.config.model) === void 0) {
|
|
40118
|
-
|
|
40119
|
-
`Unknown model: ${options.config.provider}/${options.config.model}`
|
|
40120
|
-
);
|
|
40371
|
+
return `Unknown model: ${options.config.provider}/${options.config.model}`;
|
|
40121
40372
|
}
|
|
40122
40373
|
return true;
|
|
40123
40374
|
}
|