Skip to content
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,12 @@ enum CmuxEmbeddedConfigSchema {
"default": false,
"description": "When true, a paste that Ghostty's clipboard-paste-protection flags as unsafe (for example, text with a line break going to a program that has not enabled bracketed paste) waits for confirmation in a sheet attached to the terminal's window. When false, cmux pastes it without asking. OSC 52 clipboard reads are not affected."
},
"agentImagePasteSendsCtrlV": {
"type": "boolean",
"default": false,
"descriptionKey": "schemaDescriptions.terminal.agentImagePasteSendsCtrlV",
"description": "When true, Cmd+V with only an image on the clipboard sends Ctrl+V to Claude Code or Codex running in a local terminal pane, so the agent attaches the image from the clipboard itself. When false, or in any other pane (no running agent, cmux ssh, detected SSH, or Cloud), the image is saved to a temporary file and its path is pasted or uploaded as before. Clipboards that also carry text, rich text, URLs, or files always use the regular paste."
},
"textEditingGestures": {
"type": "boolean",
"default": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ public struct TerminalCatalogSection: SettingCatalogSection {
userDefaultsKey: "terminal.confirmUnsafePaste"
)

/// Whether Cmd+V with only an image on the clipboard sends Ctrl+V to a
/// running Claude Code or Codex in a local pane, so the agent reads the
/// image from the clipboard itself, instead of pasting a temporary file
/// path. Off by default; other panes always get the file path.
public let agentImagePasteSendsCtrlV = DefaultsKey<Bool>(
id: "terminal.agentImagePasteSendsCtrlV",
defaultValue: false,
userDefaultsKey: "terminal.agentImagePasteSendsCtrlV"
)

/// Whether macOS text-editing gestures are replayed as their line-editor
/// equivalents: Command and Option arrow motion, and the Command and Option
/// deletion chords. Off by default, because the mode claims chords the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ struct SettingCatalogTests {
#expect(!SettingCatalog().terminal.confirmUnsafePaste.defaultValue)
}

@Test func agentImagePasteSendsCtrlVDefaultsOff() {
#expect(!SettingCatalog().terminal.agentImagePasteSendsCtrlV.defaultValue)
}

@Test func keyIdsMatchTheirSectionPrefix() {
// Each key's dotted id must start with its section's prefix; this is
// the convention that lets the JSON store use `id` as the JSON path.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,14 @@ extension Array where Element == CuratedSettingEntry {
paths: ["terminal.confirmUnsafePaste"],
synonyms: "terminal.confirmUnsafePaste confirm unsafe paste protection warning multi-line newline clipboard sheet"
),
.init(
section: .terminal,
id: "agent-image-paste-ctrl-v",
title: String(localized: "settings.terminal.agentImagePasteSendsCtrlV", defaultValue: "Paste Images into Agents with Ctrl+V"),
detailText: String(localized: "settings.terminal.agentImagePasteSendsCtrlV.subtitleOff", defaultValue: "Pasting an image saves it to a temporary file and pastes the file path."),
paths: ["terminal.agentImagePasteSendsCtrlV"],
synonyms: "terminal.agentImagePasteSendsCtrlV agent image paste ctrl+v control v clipboard screenshot picture claude code codex attach"
),
.init(
section: .terminal,
id: "password-input-indicator",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public struct TerminalSection: View {
@State private var copyOnSelect: DefaultsValueModel<Bool>
@State private var reflowHardWrapOnCopy: DefaultsValueModel<Bool>
@State private var confirmUnsafePaste: DefaultsValueModel<Bool>
@State private var agentImagePasteSendsCtrlV: DefaultsValueModel<Bool>
@State private var textEditingGestures: DefaultsValueModel<Bool>
@State private var passwordInputIndicator: DefaultsValueModel<Bool>
@State private var passwordInputDots: DefaultsValueModel<Bool>
Expand Down Expand Up @@ -57,6 +58,7 @@ public struct TerminalSection: View {
_copyOnSelect = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.copyOnSelect))
_reflowHardWrapOnCopy = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.reflowHardWrapOnCopy))
_confirmUnsafePaste = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.confirmUnsafePaste))
_agentImagePasteSendsCtrlV = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.agentImagePasteSendsCtrlV))
_textEditingGestures = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.textEditingGestures))
_passwordInputIndicator = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.showPasswordInputIndicator))
_passwordInputDots = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.showPasswordInputDots))
Expand Down Expand Up @@ -98,6 +100,7 @@ public struct TerminalSection: View {
copyOnSelect,
reflowHardWrapOnCopy,
confirmUnsafePaste,
agentImagePasteSendsCtrlV,
textEditingGestures,
passwordInputIndicator,
passwordInputDots,
Expand Down Expand Up @@ -453,6 +456,19 @@ public struct TerminalSection: View {
.accessibilityIdentifier("SettingsTerminalConfirmUnsafePasteToggle")
}
SettingsCardDivider()
SettingsCardRow(
configurationReview: .json("terminal.agentImagePasteSendsCtrlV"),
String(localized: "settings.terminal.agentImagePasteSendsCtrlV", defaultValue: "Paste Images into Agents with Ctrl+V"),
subtitle: agentImagePasteSendsCtrlV.current
? String(localized: "settings.terminal.agentImagePasteSendsCtrlV.subtitleOn", defaultValue: "When Claude Code or Codex is running in a local pane, pasting an image sends Ctrl+V so the agent attaches it from the clipboard. Other panes still get a file path.")
: String(localized: "settings.terminal.agentImagePasteSendsCtrlV.subtitleOff", defaultValue: "Pasting an image saves it to a temporary file and pastes the file path.")
) {
Toggle("", isOn: Binding(get: { agentImagePasteSendsCtrlV.current }, set: { agentImagePasteSendsCtrlV.set($0) }))
.labelsHidden()
.controlSize(.small)
.accessibilityIdentifier("SettingsTerminalAgentImagePasteSendsCtrlVToggle")
}
SettingsCardDivider()
SettingsCardRow(
configurationReview: .json("terminal.textEditingGestures"),
String(localized: "settings.terminal.textEditingGestures", defaultValue: "Text Editing Gestures"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ struct SettingsRowAnchorResolutionTests {
"terminal.copyOnSelect",
"terminal.reflowHardWrapOnCopy",
"terminal.confirmUnsafePaste",
"terminal.agentImagePasteSendsCtrlV",
"terminal.showPasswordInputIndicator",
"terminal.showPasswordInputDots",
"terminal.textEditingGestures",
Expand Down
Loading
Loading