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 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 join20 } from "node:path";
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 ?? join20(dirname13(configPath), "credentials.json")
39758
+ options.credentialsPath ?? join21(dirname13(configPath), "credentials.json")
39539
39759
  );
39540
39760
  const modelCatalogStore = new ModelCatalogStore(
39541
- options.modelsPath ?? join20(dirname13(configPath), "models.json")
39761
+ options.modelsPath ?? join21(dirname13(configPath), "models.json")
39542
39762
  );
39543
39763
  const customModels = new CustomModelsStore(
39544
- options.customModelsPath ?? join20(dirname13(configPath), "custom-models.json")
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
- if (!await validateConfiguredSelection({
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: join20(getHomeDirectory({ env: environ }), ".laohuang", "attachments"),
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
- attachmentRuntime.close();
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 join20(getHomeDirectory({ env: environ }), ".laohuang", "sessions");
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
- throw new Error(`Unknown provider: ${options.config.provider}`);
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
- throw new Error(
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
  }