scream-code 0.7.8 → 0.7.10

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.
@@ -121994,7 +121994,7 @@ function optionalBuildString(value) {
121994
121994
  return typeof value === "string" && value.length > 0 ? value : void 0;
121995
121995
  }
121996
121996
  const SCREAM_BUILD_INFO = {
121997
- version: optionalBuildString("0.7.7"),
121997
+ version: optionalBuildString("0.7.10"),
121998
121998
  channel: optionalBuildString(""),
121999
121999
  commit: optionalBuildString(""),
122000
122000
  buildTarget: optionalBuildString("darwin-arm64")
@@ -123815,6 +123815,15 @@ function effectiveThinkingLevel(model, thinkingDraft) {
123815
123815
  if (levels.includes(thinkingDraft)) return thinkingDraft;
123816
123816
  return levels[0] ?? "off";
123817
123817
  }
123818
+ function modelHasImageIn(model) {
123819
+ return model.capabilities?.includes("image_in") === true;
123820
+ }
123821
+ function modelHasVideoIn(model) {
123822
+ return model.capabilities?.includes("video_in") === true;
123823
+ }
123824
+ function modelHasAudioIn(model) {
123825
+ return model.capabilities?.includes("audio_in") === true;
123826
+ }
123818
123827
  var ModelSelectorComponent = class extends Container {
123819
123828
  focused = false;
123820
123829
  opts;
@@ -123849,11 +123858,13 @@ var ModelSelectorComponent = class extends Container {
123849
123858
  if (matchesKey(data, Key.left)) {
123850
123859
  const nextIdx = idx <= 0 ? levels.length - 1 : idx - 1;
123851
123860
  this.thinkingDraft = levels[nextIdx];
123861
+ this.opts.onChangeThinking?.(selected.alias, this.thinkingDraft);
123852
123862
  return;
123853
123863
  }
123854
123864
  if (matchesKey(data, Key.right)) {
123855
123865
  const nextIdx = idx === -1 || idx >= levels.length - 1 ? 0 : idx + 1;
123856
123866
  this.thinkingDraft = levels[nextIdx];
123867
+ this.opts.onChangeThinking?.(selected.alias, this.thinkingDraft);
123857
123868
  return;
123858
123869
  }
123859
123870
  }
@@ -123874,7 +123885,7 @@ var ModelSelectorComponent = class extends Container {
123874
123885
  const choices = view.items;
123875
123886
  const navParts = ["↑↓ 模型", "←→ 思考等级"];
123876
123887
  if (view.page.pageCount > 1) navParts.push("PgUp/PgDn 翻页");
123877
- navParts.push("Enter 应用", "Esc 取消");
123888
+ navParts.push("Enter 切换模型", "Esc 取消");
123878
123889
  const titleSuffix = searchable && view.query.length === 0 ? chalk.hex(colors.textMuted)(" (输入搜索)") : "";
123879
123890
  const lines = [chalk.hex(colors.primary)("─".repeat(width)), chalk.hex(colors.primary).bold(" 选择模型") + titleSuffix];
123880
123891
  if (searchable && view.query.length > 0) lines.push(chalk.hex(colors.primary)(" 搜索:") + chalk.hex(colors.text)(view.query));
@@ -123893,10 +123904,17 @@ var ModelSelectorComponent = class extends Container {
123893
123904
  lines.push(line);
123894
123905
  }
123895
123906
  lines.push("");
123896
- lines.push(chalk.hex(colors.textMuted)(" Thinking"));
123907
+ lines.push(chalk.hex(colors.textMuted)(" Thinking(全局设置)"));
123897
123908
  const selected = choices[view.selectedIndex];
123898
123909
  if (selected !== void 0) lines.push(this.renderThinkingControl(selected.model));
123899
123910
  lines.push("");
123911
+ lines.push(chalk.hex(colors.textMuted)(" 多模态能力"));
123912
+ if (selected !== void 0) {
123913
+ lines.push(this.renderImageControl(selected.model));
123914
+ lines.push(this.renderVideoControl(selected.model));
123915
+ lines.push(this.renderAudioControl(selected.model));
123916
+ }
123917
+ lines.push("");
123900
123918
  if (view.page.pageCount > 1) lines.push(chalk.hex(colors.textMuted)(` Page ${String(view.page.page + 1)}/${String(view.page.pageCount)}`));
123901
123919
  lines.push(chalk.hex(colors.primary)("─".repeat(width)));
123902
123920
  return lines.map((line) => truncateToWidth(line, width));
@@ -123912,6 +123930,21 @@ var ModelSelectorComponent = class extends Container {
123912
123930
  if (availability === "unsupported") return `${line} ${chalk.hex(colors.textMuted)("unsupported")}`;
123913
123931
  return line;
123914
123932
  }
123933
+ renderImageControl(model) {
123934
+ const { colors } = this.opts;
123935
+ if (modelHasImageIn(model)) return ` ${chalk.hex(colors.textMuted)("识图")}: ${chalk.hex(colors.success).bold("✓ 支持")}`;
123936
+ return ` ${chalk.hex(colors.textMuted)("识图")}: ${chalk.hex(colors.textDim)("✗ 不支持")}`;
123937
+ }
123938
+ renderVideoControl(model) {
123939
+ const { colors } = this.opts;
123940
+ if (modelHasVideoIn(model)) return ` ${chalk.hex(colors.textMuted)("视频")}: ${chalk.hex(colors.success).bold("✓ 支持")}`;
123941
+ return ` ${chalk.hex(colors.textMuted)("视频")}: ${chalk.hex(colors.textDim)("✗ 不支持")}`;
123942
+ }
123943
+ renderAudioControl(model) {
123944
+ const { colors } = this.opts;
123945
+ if (modelHasAudioIn(model)) return ` ${chalk.hex(colors.textMuted)("音频")}: ${chalk.hex(colors.success).bold("✓ 支持")}`;
123946
+ return ` ${chalk.hex(colors.textMuted)("音频")}: ${chalk.hex(colors.textDim)("✗ 不支持")}`;
123947
+ }
123915
123948
  };
123916
123949
  //#endregion
123917
123950
  //#region src/tui/components/dialogs/text-input-dialog.ts
@@ -124124,6 +124157,33 @@ const THINKING_OPTIONS = [
124124
124157
  label: "高强度思考"
124125
124158
  }
124126
124159
  ];
124160
+ const IMAGE_OPTIONS = [{
124161
+ value: "off",
124162
+ label: "关闭识图",
124163
+ description: "模型不支持图片输入时请选择此项"
124164
+ }, {
124165
+ value: "on",
124166
+ label: "开启识图",
124167
+ description: "模型支持图片输入时开启,允许发送图片"
124168
+ }];
124169
+ const VIDEO_OPTIONS = [{
124170
+ value: "off",
124171
+ label: "关闭视频",
124172
+ description: "模型不支持视频输入时请选择此项"
124173
+ }, {
124174
+ value: "on",
124175
+ label: "开启视频",
124176
+ description: "模型支持视频输入时开启,允许发送视频"
124177
+ }];
124178
+ const AUDIO_OPTIONS = [{
124179
+ value: "off",
124180
+ label: "关闭音频",
124181
+ description: "模型不支持音频输入时请选择此项"
124182
+ }, {
124183
+ value: "on",
124184
+ label: "开启音频",
124185
+ description: "模型支持音频输入时开启,允许发送音频"
124186
+ }];
124127
124187
  function promptWireType(host) {
124128
124188
  return new Promise((resolve) => {
124129
124189
  const picker = new ChoicePickerComponent({
@@ -124177,6 +124237,63 @@ function promptThinkingMode(host) {
124177
124237
  host.mountEditorReplacement(picker);
124178
124238
  });
124179
124239
  }
124240
+ function promptImageMode(host) {
124241
+ return new Promise((resolve) => {
124242
+ const picker = new ChoicePickerComponent({
124243
+ title: "多模态配置 · 图片输入",
124244
+ hint: "模型不支持请选择关闭",
124245
+ options: IMAGE_OPTIONS,
124246
+ colors: host.state.theme.colors,
124247
+ onSelect: (value) => {
124248
+ host.restoreEditor();
124249
+ resolve(value === "on");
124250
+ },
124251
+ onCancel: () => {
124252
+ host.restoreEditor();
124253
+ resolve(void 0);
124254
+ }
124255
+ });
124256
+ host.mountEditorReplacement(picker);
124257
+ });
124258
+ }
124259
+ function promptVideoMode(host) {
124260
+ return new Promise((resolve) => {
124261
+ const picker = new ChoicePickerComponent({
124262
+ title: "多模态配置 · 视频输入",
124263
+ hint: "模型不支持请选择关闭",
124264
+ options: VIDEO_OPTIONS,
124265
+ colors: host.state.theme.colors,
124266
+ onSelect: (value) => {
124267
+ host.restoreEditor();
124268
+ resolve(value === "on");
124269
+ },
124270
+ onCancel: () => {
124271
+ host.restoreEditor();
124272
+ resolve(void 0);
124273
+ }
124274
+ });
124275
+ host.mountEditorReplacement(picker);
124276
+ });
124277
+ }
124278
+ function promptAudioMode(host) {
124279
+ return new Promise((resolve) => {
124280
+ const picker = new ChoicePickerComponent({
124281
+ title: "多模态配置 · 音频输入",
124282
+ hint: "模型不支持请选择关闭",
124283
+ options: AUDIO_OPTIONS,
124284
+ colors: host.state.theme.colors,
124285
+ onSelect: (value) => {
124286
+ host.restoreEditor();
124287
+ resolve(value === "on");
124288
+ },
124289
+ onCancel: () => {
124290
+ host.restoreEditor();
124291
+ resolve(void 0);
124292
+ }
124293
+ });
124294
+ host.mountEditorReplacement(picker);
124295
+ });
124296
+ }
124180
124297
  //#endregion
124181
124298
  //#region src/tui/commands/auth.ts
124182
124299
  async function handleConnectCommand(host, args) {
@@ -124319,15 +124436,21 @@ async function handleDiyConfig(host) {
124319
124436
  const maxContextTokens = parseInt(maxContextStr, 10) || 131072;
124320
124437
  const thinkingLevel = await promptThinkingMode(host);
124321
124438
  if (thinkingLevel === void 0) return;
124439
+ const imageEnabled = await promptImageMode(host);
124440
+ if (imageEnabled === void 0) return;
124441
+ const videoEnabled = await promptVideoMode(host);
124442
+ if (videoEnabled === void 0) return;
124443
+ const audioEnabled = await promptAudioMode(host);
124444
+ if (audioEnabled === void 0) return;
124322
124445
  const providerId = `custom-${modelId.replaceAll(/[^A-Za-z0-9._-]/g, "-")}`;
124323
124446
  const catalogModel = {
124324
124447
  id: modelId,
124325
124448
  name: modelId,
124326
124449
  capability: {
124327
124450
  max_context_tokens: maxContextTokens,
124328
- image_in: false,
124329
- video_in: false,
124330
- audio_in: false,
124451
+ image_in: imageEnabled,
124452
+ video_in: videoEnabled,
124453
+ audio_in: audioEnabled,
124331
124454
  thinking: thinkingLevel !== "off",
124332
124455
  tool_use: true
124333
124456
  },
@@ -125629,11 +125752,68 @@ function showModelPicker(host, selectedValue = host.state.appState.model) {
125629
125752
  host.restoreEditor();
125630
125753
  performModelSwitch(host, alias, thinkingLevel);
125631
125754
  },
125755
+ onChangeThinking: (alias, level) => {
125756
+ changeThinkingLevel(host, alias, level);
125757
+ },
125632
125758
  onCancel: () => {
125633
125759
  host.restoreEditor();
125634
125760
  }
125635
125761
  }));
125636
125762
  }
125763
+ /**
125764
+ * Apply a thinking-level change immediately (←/→ keys in the model selector).
125765
+ * Decoupled from performModelSwitch so the user can cycle thinking on the
125766
+ * currently-selected model without pressing Enter. If the alias is the active
125767
+ * model, the running session is updated too; otherwise only the default is
125768
+ * persisted.
125769
+ */
125770
+ async function changeThinkingLevel(host, alias, level) {
125771
+ if (isBusy(host.state.appState)) {
125772
+ host.showError("Cannot change thinking while streaming — press Esc or Ctrl-C first.");
125773
+ return;
125774
+ }
125775
+ const prevLevel = host.state.appState.thinkingLevel;
125776
+ const isActiveModel = alias === host.state.appState.model;
125777
+ if (isActiveModel && level !== prevLevel) {
125778
+ const session = host.session;
125779
+ try {
125780
+ if (session === void 0) await host.authFlow.activateModelAfterLogin(alias, level);
125781
+ else await session.setThinking(level);
125782
+ } catch (error) {
125783
+ const msg = formatErrorMessage(error);
125784
+ host.showError(`Failed to set thinking: ${msg}`);
125785
+ return;
125786
+ }
125787
+ host.setAppState({ thinkingLevel: level });
125788
+ }
125789
+ let persisted = false;
125790
+ try {
125791
+ persisted = await persistThinkingDefault(host, alias, level);
125792
+ } catch (error) {
125793
+ const msg = formatErrorMessage(error);
125794
+ host.showError(`Failed to save thinking default: ${msg}`);
125795
+ return;
125796
+ }
125797
+ const status = isActiveModel && level !== prevLevel ? `Thinking set to ${level} for ${alias}.` : persisted ? `Saved thinking ${level} as default for ${alias}.` : `Thinking already ${level} for ${alias}.`;
125798
+ host.showStatus(status, host.state.theme.colors.success);
125799
+ }
125800
+ async function persistThinkingDefault(host, alias, level) {
125801
+ const config = await host.harness.getConfig({ reload: true });
125802
+ const effectiveThinking = level !== "off";
125803
+ const existingEffort = config.thinking?.effort;
125804
+ const newEffort = effectiveThinking ? level : existingEffort;
125805
+ if (!(config.defaultModel === alias)) return false;
125806
+ if (config.defaultThinking === effectiveThinking && existingEffort === newEffort) return false;
125807
+ await host.harness.setConfig({
125808
+ defaultThinking: effectiveThinking,
125809
+ thinking: {
125810
+ ...config.thinking,
125811
+ mode: effectiveThinking ? "on" : "off",
125812
+ effort: newEffort
125813
+ }
125814
+ });
125815
+ return true;
125816
+ }
125637
125817
  async function performModelSwitch(host, alias, thinkingLevel) {
125638
125818
  if (isBusy(host.state.appState)) {
125639
125819
  host.showError("Cannot switch models while streaming — press Esc or Ctrl-C first.");
@@ -125641,7 +125821,9 @@ async function performModelSwitch(host, alias, thinkingLevel) {
125641
125821
  }
125642
125822
  const prevModel = host.state.appState.model;
125643
125823
  const prevThinkingLevel = host.state.appState.thinkingLevel;
125644
- const runtimeChanged = alias !== prevModel || thinkingLevel !== prevThinkingLevel;
125824
+ const modelChanged = alias !== prevModel;
125825
+ const thinkingChanged = thinkingLevel !== prevThinkingLevel;
125826
+ const needsSessionActivation = modelChanged || thinkingChanged;
125645
125827
  const overflow = alias !== prevModel ? contextOverflowForModel(host.state.appState, alias) : null;
125646
125828
  if (overflow !== null) {
125647
125829
  host.showNotice("Storm Breaker(风暴守护者)", `无法切换到模型「${alias}」:当前会话上下文 ${formatTokenCount$1(overflow.currentTokens)} 已超出该模型上限 ${formatTokenCount$1(overflow.maxContextTokens)}。建议先执行 /compact 压缩上下文,或选择上下文窗口更大的模型。`);
@@ -125649,10 +125831,10 @@ async function performModelSwitch(host, alias, thinkingLevel) {
125649
125831
  }
125650
125832
  const session = host.session;
125651
125833
  try {
125652
- if (session === void 0 && runtimeChanged) await host.authFlow.activateModelAfterLogin(alias, thinkingLevel);
125834
+ if (session === void 0 && needsSessionActivation) await host.authFlow.activateModelAfterLogin(alias, thinkingLevel);
125653
125835
  else if (session !== void 0) {
125654
- if (alias !== prevModel) await session.setModel(alias);
125655
- if (thinkingLevel !== prevThinkingLevel) await session.setThinking(thinkingLevel);
125836
+ if (modelChanged) await session.setModel(alias);
125837
+ if (thinkingChanged) await session.setThinking(thinkingLevel);
125656
125838
  }
125657
125839
  } catch (error) {
125658
125840
  const msg = formatErrorMessage(error);
@@ -125671,7 +125853,12 @@ async function performModelSwitch(host, alias, thinkingLevel) {
125671
125853
  host.showError(`Switched to ${alias}, but failed to save default: ${msg}`);
125672
125854
  return;
125673
125855
  }
125674
- const status = runtimeChanged ? `Switched to ${alias} with thinking ${thinkingLevel}.` : persisted ? `Saved ${alias} with thinking ${thinkingLevel} as default.` : `Already using ${alias} with thinking ${thinkingLevel}.`;
125856
+ const status = (() => {
125857
+ if (modelChanged) return `Switched to ${alias} with thinking ${thinkingLevel}.`;
125858
+ if (thinkingChanged) return `Thinking set to ${thinkingLevel} for ${alias}.`;
125859
+ if (persisted) return `Saved ${alias} with thinking ${thinkingLevel} as default.`;
125860
+ return `Already using ${alias} with thinking ${thinkingLevel}.`;
125861
+ })();
125675
125862
  host.showStatus(status, host.state.theme.colors.success);
125676
125863
  }
125677
125864
  async function persistModelSelection(host, alias, thinkingLevel) {
@@ -133020,6 +133207,15 @@ function readImagePath(path) {
133020
133207
  mimeType: meta.mime
133021
133208
  };
133022
133209
  }
133210
+ /**
133211
+ * Read an image file from disk into a ClipboardImage. Used when the user
133212
+ * pastes a file URL that the terminal has translated into a text path —
133213
+ * the path points at a real image file, so we load it directly rather
133214
+ * than going through the clipboard native binding.
133215
+ */
133216
+ function readImageFromPath(path) {
133217
+ return readImagePath(path);
133218
+ }
133023
133219
  function readVideoPath(path) {
133024
133220
  const mimeType = videoMimeFromPath(path);
133025
133221
  if (mimeType === null) return null;
@@ -133471,6 +133667,16 @@ var EditorKeyboardController = class {
133471
133667
  return false;
133472
133668
  };
133473
133669
  editor.onPasteImage = async () => this.handleClipboardImagePaste();
133670
+ editor.onPasteImagePath = async (path) => this.handleImagePathPaste(path);
133671
+ }
133672
+ async handleImagePathPaste(path) {
133673
+ const media = readImageFromPath(path);
133674
+ if (media === null) return;
133675
+ const meta = parseImageMeta(media.bytes);
133676
+ if (meta === null) return;
133677
+ const attachment = this.imageStore.addImage(media.bytes, meta.mime, meta.width, meta.height);
133678
+ this.host.state.editor.insertTextAtCursor?.(`${attachment.placeholder} `);
133679
+ this.host.state.ui.requestRender();
133474
133680
  }
133475
133681
  clearPendingExit() {
133476
133682
  if (!this.pendingExit) return;
@@ -134413,8 +134619,6 @@ var SessionEventHandler = class {
134413
134619
  if (event.maxContextTokens !== void 0) patch.maxContextTokens = event.maxContextTokens;
134414
134620
  if (event.planMode !== void 0) patch.planMode = event.planMode ? event.planStrategy === "fusion" ? "fusionplan" : this.host.state.appState.planMode === "fusionplan" ? "fusionplan" : "plan" : "off";
134415
134621
  if (event.permission !== void 0) patch.permissionMode = event.permission;
134416
- if (event.model !== void 0) patch.model = event.model;
134417
- if (event.thinkingLevel !== void 0) patch.thinkingLevel = event.thinkingLevel;
134418
134622
  if (Object.keys(patch).length > 0) this.host.setAppState(patch);
134419
134623
  }
134420
134624
  handleSessionMetaChanged(event) {
@@ -140734,11 +140938,62 @@ function styleTitle(title, status, colors) {
140734
140938
  /**
140735
140939
  * Custom editor extending pi-tui Editor with app-level keybindings.
140736
140940
  */
140737
- const ANSI_SGR = /\u001B\[[0-9;]*m/g;
140941
+ const ANSI_SGR = /\[[0-9;]*m/g;
140738
140942
  const PASTE_MARKER_RE = /\[paste #(\d+)(?: (?:\+\d+ lines|\d+ chars))?\]/g;
140739
140943
  const BRACKET_PASTE_START = "\x1B[200~";
140740
140944
  const BRACKET_PASTE_END = "\x1B[201~";
140741
- const KITTY_CSI_U = /^\u001B\[(\d+);(\d+)((?::\d+)*)u$/;
140945
+ const BRACKETED_IMAGE_PATH_REGEX = /\.(?:png|jpe?g|gif|webp)$/i;
140946
+ const BRACKETED_IMAGE_PATH_BOUNDARY_REGEX = /\.(?:png|jpe?g|gif|webp)(?=$|["']?\s)/gi;
140947
+ const SHELL_ESCAPED_PATH_CHAR_REGEX = /\\([\\\s'"()[\]{}&;<>|?*!$`])/g;
140948
+ function isPastedPathSeparator(char) {
140949
+ return char === void 0 || char === " " || char === " " || char === "\r" || char === "\n";
140950
+ }
140951
+ function imagePathBoundaryEnd(payload, segmentStart, extensionEnd) {
140952
+ const quote = payload[segmentStart];
140953
+ const afterExtension = payload[extensionEnd];
140954
+ if (quote === "\"" || quote === "'") return afterExtension === quote && isPastedPathSeparator(payload[extensionEnd + 1]) ? extensionEnd + 1 : void 0;
140955
+ if (isPastedPathSeparator(afterExtension)) return extensionEnd;
140956
+ }
140957
+ function normalizePastedImagePath(path) {
140958
+ const trimmed = path.trim();
140959
+ const first = trimmed[0];
140960
+ const last = trimmed[trimmed.length - 1];
140961
+ return (trimmed.length > 1 && (first === "\"" || first === "'") && last === first ? trimmed.slice(1, -1) : trimmed).replace(SHELL_ESCAPED_PATH_CHAR_REGEX, "$1");
140962
+ }
140963
+ /**
140964
+ * If a bracketed paste contains only image file paths (one or more,
140965
+ * space/quote separated, all with image extensions), return them. Returns
140966
+ * `undefined` when the paste is mixed text + paths or contains non-image
140967
+ * content — in that case the caller should fall through to normal text
140968
+ * paste. Mirrors oh-my-pi's approach so pasting a Finder-copied image
140969
+ * file becomes a multimodal image attachment instead of a text path the
140970
+ * agent would have to Read manually.
140971
+ */
140972
+ function extractBracketedImagePastePaths(data) {
140973
+ if (!data.includes(BRACKET_PASTE_START)) return void 0;
140974
+ const startIndex = data.indexOf(BRACKET_PASTE_START);
140975
+ const endIndex = data.indexOf(BRACKET_PASTE_END, startIndex + 6);
140976
+ if (endIndex === -1) return void 0;
140977
+ const pasted = data.slice(startIndex + 6, endIndex).trim();
140978
+ if (pasted.length === 0) return void 0;
140979
+ const paths = [];
140980
+ let segmentStart = 0;
140981
+ BRACKETED_IMAGE_PATH_BOUNDARY_REGEX.lastIndex = 0;
140982
+ for (let match = BRACKETED_IMAGE_PATH_BOUNDARY_REGEX.exec(pasted); match !== null; match = BRACKETED_IMAGE_PATH_BOUNDARY_REGEX.exec(pasted)) {
140983
+ const extensionEnd = match.index + match[0].length;
140984
+ const boundaryEnd = imagePathBoundaryEnd(pasted, segmentStart, extensionEnd);
140985
+ if (boundaryEnd === void 0) continue;
140986
+ const path = normalizePastedImagePath(pasted.slice(segmentStart, boundaryEnd));
140987
+ if (path.length === 0 || !BRACKETED_IMAGE_PATH_REGEX.test(path)) return void 0;
140988
+ paths.push(path);
140989
+ segmentStart = boundaryEnd;
140990
+ while (segmentStart < pasted.length && isPastedPathSeparator(pasted[segmentStart])) segmentStart += 1;
140991
+ BRACKETED_IMAGE_PATH_BOUNDARY_REGEX.lastIndex = segmentStart;
140992
+ }
140993
+ if (paths.length === 0 || segmentStart !== pasted.length) return void 0;
140994
+ return paths;
140995
+ }
140996
+ const KITTY_CSI_U = /^\[(\d+);(\d+)((?::\d+)*)u$/;
140742
140997
  const CAPS_LOCK_BIT = 64;
140743
140998
  const CTRL_BIT = 4;
140744
140999
  const SHIFT_BIT = 1;
@@ -140770,7 +141025,7 @@ function normalizeCapsLockedCtrl(data) {
140770
141025
  if (codepoint < 65 || codepoint > 90) return data;
140771
141026
  const loweredCodepoint = codepoint + 32;
140772
141027
  const strippedModifier = (modifier & -65) + 1;
140773
- return `\u001B[${String(loweredCodepoint)};${String(strippedModifier)}${tail}u`;
141028
+ return `[${String(loweredCodepoint)};${String(strippedModifier)}${tail}u`;
140774
141029
  }
140775
141030
  /** Convert a visible-char index (ANSI-stripped) back to an index into the raw ANSI-bearing string. */
140776
141031
  function mapVisibleIdxToRaw(line, visibleIdx) {
@@ -140821,6 +141076,15 @@ var CustomEditor = class extends Editor {
140821
141076
  * the next keystroke.
140822
141077
  */
140823
141078
  onPasteImage;
141079
+ /**
141080
+ * Called when a bracketed paste contains only image file paths (e.g.
141081
+ * the user copied an image file in Finder and pasted — the terminal
141082
+ * translates the file URL into a text path). The host loads each path
141083
+ * from disk and registers it as an image attachment, so the paste
141084
+ * becomes a multimodal image input instead of a text path the agent
141085
+ * would have to Read manually.
141086
+ */
141087
+ onPasteImagePath;
140824
141088
  /** Fires exactly once when the user first types anything into the editor. */
140825
141089
  onFirstInput;
140826
141090
  firstInputFired = false;
@@ -140912,6 +141176,15 @@ var CustomEditor = class extends Editor {
140912
141176
  if (!normalized.includes(BRACKET_PASTE_END)) this.consumingPaste = true;
140913
141177
  return;
140914
141178
  }
141179
+ const pastedImagePaths = extractBracketedImagePastePaths(normalized);
141180
+ if (pastedImagePaths !== void 0 && this.onPasteImagePath !== void 0) {
141181
+ const handler = this.onPasteImagePath;
141182
+ (async () => {
141183
+ for (const path of pastedImagePaths) await handler(path);
141184
+ })();
141185
+ if (!normalized.includes(BRACKET_PASTE_END)) this.consumingPaste = true;
141186
+ return;
141187
+ }
140915
141188
  if (matchesKey(normalized, process.platform === "win32" ? "alt+v" : Key.ctrl("v"))) {
140916
141189
  if (this.expandPasteMarkerAtCursor()) return;
140917
141190
  if (this.onPasteImage !== void 0) {
package/dist/main.mjs CHANGED
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
6
6
  import "./suppress-sqlite-warning-C2VB0doZ.mjs";
7
7
  //#region src/main.ts
8
8
  try {
9
- (await import("./app-AVBOJrWs.mjs")).main();
9
+ (await import("./app-BJoewl4C.mjs")).main();
10
10
  } catch (error) {
11
11
  process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
12
12
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scream-code",
3
- "version": "0.7.8",
3
+ "version": "0.7.10",
4
4
  "description": "A terminal-native AI agent for builders",
5
5
  "license": "MIT",
6
6
  "author": "ScreamCli",
@@ -57,10 +57,10 @@
57
57
  "@types/semver": "^7.7.0",
58
58
  "tsx": "^4.21.0",
59
59
  "@scream-code/agent-core": "^0.7.0",
60
- "@scream-code/memory": "0.7.0",
61
60
  "@scream-code/config": "^0.7.0",
62
- "@scream-code/scream-code-sdk": "^0.7.0",
63
- "@scream-code/migration-legacy": "^0.7.0"
61
+ "@scream-code/memory": "0.7.0",
62
+ "@scream-code/migration-legacy": "^0.7.0",
63
+ "@scream-code/scream-code-sdk": "^0.7.0"
64
64
  },
65
65
  "engines": {
66
66
  "node": ">=22.19.0"