···1212 @OptionGroup var selector: SelectorOptions
1313 @OptionGroup var options: GlobalOptions
14141515+ @Argument(help: "Target pane/tab UUID or worktree id/name/path (auto-resolved).")
1616+ var target: String?
1717+1518 mutating func run() throws {
1619 try CLIExecution.run(command: "focus", output: options.outputMode, colorEnabled: options.colorEnabled) {
1717- let sel = try selector.resolve()
2020+ let sel = try selector.resolve(positionalTarget: target)
1821 let envelope = CommandEnvelope(
1922 output: options.outputMode,
2023 command: .focus(FocusInput(selector: sel))
+32-6
ProwlCLI/Commands/KeyCommand.swift
···77struct KeyCommand: ParsableCommand {
88 static let configuration = CommandConfiguration(
99 commandName: "key",
1010- abstract: "Send a key event to a terminal pane."
1010+ abstract: "Send a key event to a terminal pane.",
1111+ discussion: """
1212+ With one positional argument, the key is sent to the current pane.
1313+ With two positional arguments, the first is the target (auto-resolved) and
1414+ the second is the key token.
1515+ """
1116 )
12171318 @OptionGroup var selector: SelectorOptions
···1621 @Option(name: .long, help: "Number of times to repeat the key (1-100).")
1722 var `repeat`: Int = 1
18231919- @Argument(help: "Key token (e.g. enter, esc, tab, ctrl-c, up, down).")
2020- var token: String
2424+ @Argument(
2525+ help: """
2626+ Key token, or target followed by key token. \
2727+ One argument: key token sent to current pane. \
2828+ Two arguments: first is target (auto-resolved), second is key token.
2929+ """
3030+ )
3131+ var args: [String] = []
21322233 mutating func run() throws {
2334 try CLIExecution.run(command: "key", output: options.outputMode, colorEnabled: options.colorEnabled) {
2424- let sel = try selector.resolve()
3535+ // Parse positional args: 1 = token, 2 = target + token
3636+ let positionalTarget: String?
3737+ let rawToken: String
3838+ switch args.count {
3939+ case 1:
4040+ positionalTarget = nil
4141+ rawToken = args[0].trimmingCharacters(in: .whitespaces)
4242+ case 2:
4343+ positionalTarget = args[0]
4444+ rawToken = args[1].trimmingCharacters(in: .whitespaces)
4545+ default:
4646+ throw ExitError(
4747+ code: CLIErrorCode.invalidArgument,
4848+ message: "Expected 1 or 2 positional arguments (optional target and key token), got \(args.count)."
4949+ )
5050+ }
5151+5252+ let sel = try selector.resolve(positionalTarget: positionalTarget)
25532654 guard (1...100).contains(self.repeat) else {
2755 throw ExitError(
···2957 message: "Repeat count must be between 1 and 100, got \(self.repeat)."
3058 )
3159 }
3232-3333- let rawToken = token.trimmingCharacters(in: .whitespaces)
34603561 guard let normalized = KeyTokens.normalize(rawToken) else {
3662 throw ExitError(
+4-1
ProwlCLI/Commands/ReadCommand.swift
···1515 @Option(name: .long, help: "Number of recent lines to read (omit for snapshot).")
1616 var last: Int?
17171818+ @Argument(help: "Target pane/tab UUID or worktree id/name/path (auto-resolved).")
1919+ var target: String?
2020+1821 mutating func run() throws {
1922 try CLIExecution.run(command: "read", output: options.outputMode, colorEnabled: options.colorEnabled) {
2020- let sel = try selector.resolve()
2323+ let sel = try selector.resolve(positionalTarget: target)
21242225 if let n = last, n < 1 {
2326 throw ExitError(
+16-2
ProwlCLI/Commands/SelectorOptions.swift
···55import ProwlCLIShared
6677struct SelectorOptions: ParsableArguments {
88+ @Option(name: .shortAndLong, help: "Auto-resolve target by pane/tab UUID or worktree id/name/path.")
99+ var target: String?
1010+811 @Option(name: .long, help: "Target worktree by id, name, or path.")
912 var worktree: String?
1013···16191720 /// Validate mutual exclusivity and return typed selector.
1821 func resolve() throws -> TargetSelector {
1919- let provided = [worktree, tab, pane].compactMap { $0 }
2222+ let provided = [target, worktree, tab, pane].compactMap { $0 }
2023 guard provided.count <= 1 else {
2124 throw ExitError(
2225 code: CLIErrorCode.invalidArgument,
2323- message: "At most one target selector (--worktree, --tab, --pane) is allowed."
2626+ message: "At most one target selector (--target, --worktree, --tab, --pane) is allowed."
2427 )
2528 }
2929+ if let a = target { return .auto(a) }
2630 if let w = worktree { return .worktree(w) }
2731 if let t = tab { return .tab(t) }
2832 if let p = pane { return .pane(p) }
2933 return .none
3434+ }
3535+3636+ /// Resolve with an additional auto-target value (from positional argument).
3737+ /// The positional target takes effect only when no flag selectors are specified.
3838+ func resolve(positionalTarget: String?) throws -> TargetSelector {
3939+ let flagSelector = try resolve()
4040+ if case .none = flagSelector, let positional = positionalTarget {
4141+ return .auto(positional)
4242+ }
4343+ return flagSelector
3044 }
3145}
+36-5
ProwlCLI/Commands/SendCommand.swift
···18181919 static let configuration = CommandConfiguration(
2020 commandName: "send",
2121- abstract: "Send text input to a terminal pane."
2121+ abstract: "Send text input to a terminal pane.",
2222+ discussion: """
2323+ With one positional argument, it is treated as text sent to the current pane.
2424+ With two positional arguments, the first is the target (auto-resolved) and
2525+ the second is the text.
2626+ """
2227 )
23282429 @OptionGroup var selector: SelectorOptions
···3641 @Option(name: .long, help: "Maximum seconds to wait for completion (1–300, default: 30).")
3742 var timeout: Int?
38433939- @Argument(help: "Text to send. Alternatively pipe via stdin.")
4040- var text: String?
4444+ @Argument(
4545+ help: """
4646+ Text to send, or target followed by text. \
4747+ One argument: text sent to current pane. \
4848+ Two arguments: first is target (auto-resolved), second is text.
4949+ """
5050+ )
5151+ var args: [String] = []
41524253 mutating func run() throws {
4354 try CLIExecution.run(command: "send", output: options.outputMode, colorEnabled: options.colorEnabled) {
4444- let sel = try selector.resolve()
5555+ // Parse positional args: 0 = stdin, 1 = text, 2 = target + text
5656+ let positionalTarget: String?
5757+ let positionalText: String?
5858+ switch args.count {
5959+ case 0:
6060+ positionalTarget = nil
6161+ positionalText = nil
6262+ case 1:
6363+ positionalTarget = nil
6464+ positionalText = args[0]
6565+ case 2:
6666+ positionalTarget = args[0]
6767+ positionalText = args[1]
6868+ default:
6969+ throw ExitError(
7070+ code: CLIErrorCode.invalidArgument,
7171+ message: "Expected at most 2 positional arguments (target and text), got \(args.count)."
7272+ )
7373+ }
7474+7575+ let sel = try selector.resolve(positionalTarget: positionalTarget)
45764677 if let timeout, (timeout < 1 || timeout > 300) {
4778 throw ExitError(
···6899 let stdinIsPiped = isatty(fileno(stdin)) == 0 && Self.stdinHasData()
69100 let inputText: String
70101 let source: InputSource
7171- if let argText = text {
102102+ if let argText = positionalText {
72103 if stdinIsPiped {
73104 throw ExitError(
74105 code: CLIErrorCode.invalidArgument,
+62-22
doc-onevcat/contracts/cli/input.md
···77777878### 3.1 Selector flags
79798080-- `--worktree <id|name|path>`
8181-- `--tab <id>`
8282-- `--pane <id>`
8080+- `-t <value>` / `--target <value>` — auto-resolve: try pane UUID → tab UUID → worktree id/name/path.
8181+- `--worktree <id|name|path>` — explicit worktree selector.
8282+- `--tab <id>` — explicit tab UUID selector.
8383+- `--pane <id>` — explicit pane UUID selector.
8484+8585+### 3.2 Positional target shorthand
83868484-### 3.2 Mutual exclusivity (hard rule)
8787+`focus` and `read` accept an optional positional argument as auto-target:
8888+8989+```bash
9090+prowl focus <target>
9191+prowl read <target> --last 50
9292+```
9393+9494+`send` and `key` use argument count to disambiguate:
9595+9696+- `prowl send "text"` — 1 arg → text to current pane.
9797+- `prowl send <target> "text"` — 2 args → auto-target + text.
9898+- `prowl key enter` — 1 arg → key token to current pane.
9999+- `prowl key <target> enter` — 2 args → auto-target + key token.
100100+101101+Positional targets are ignored when flag selectors (`-t`, `--worktree`, `--tab`, `--pane`) are present.
102102+103103+### 3.3 Mutual exclusivity (hard rule)
8510486105Exactly **zero or one** selector is allowed.
87106···9111092111This is preferred over implicit precedence because it is easier to reason about in scripts.
931129494-### 3.3 Resolution rules
113113+### 3.4 Resolution rules
9511496115- `--pane`: exact pane.
97116- `--tab`: current focused pane of target tab.
98117- `--worktree`: selected tab + focused pane in target worktree.
118118+- `-t` / `--target` / positional: auto-resolve in order pane → tab → worktree.
99119- none: currently focused pane in current context.
100120101121If required context does not exist:
···166186### Grammar
167187168188```bash
169169-prowl focus [--worktree <...> | --tab <...> | --pane <...>] [--json]
189189+prowl focus [<target>] [--json]
190190+prowl focus [-t <...> | --worktree <...> | --tab <...> | --pane <...>] [--json]
170191```
171192172193### Rules
173194174174-- Selectors are optional; no selector means “focus current target and bring app front”.
195195+- Optional positional `<target>` is auto-resolved (pane → tab → worktree).
196196+- Flag selectors override positional target.
197197+- No selector means “focus current target and bring app front”.
175198- More than one selector is invalid.
176199177200## 5.4 `send`
···179202### Grammar
180203181204```bash
182182-prowl send [selector] [--no-enter] [--no-wait] [--timeout <seconds>] [--json] [<text>]
183183-# or
184184-printf '...' | prowl send [selector] [--no-enter] [--no-wait] [--timeout <seconds>] [--json]
205205+prowl send [flags] <text>
206206+prowl send [flags] <target> <text>
207207+printf '...' | prowl send [flags]
208208+printf '...' | prowl send [flags] -t <target>
185209```
186210211211+Where `[flags]` includes `[--no-enter] [--no-wait] [--capture] [--timeout <seconds>] [--json]` and optional selector flags (`-t`, `--worktree`, `--tab`, `--pane`).
212212+187213### Rules
188214189189-- Input source is exactly one of:
190190- - positional `<text>` (`argv`)
191191- - stdin (`stdin`)
192192-- Both provided simultaneously: `INVALID_ARGUMENT`.
193193-- Neither provided (or empty stdin): `EMPTY_INPUT`.
215215+- Positional argument count determines interpretation:
216216+ - 0 args: read text from stdin, send to current pane.
217217+ - 1 arg: text to current pane.
218218+ - 2 args: first is auto-resolved target, second is text.
219219+- Flag selector (`-t`, `--worktree`, `--tab`, `--pane`) overrides positional target.
220220+- Input source is exactly one of positional text or stdin. Both: `INVALID_ARGUMENT`. Neither: `EMPTY_INPUT`.
194221- Default sends trailing Enter; `--no-enter` disables it.
195222- Default waits for command completion (requires shell integration); `--no-wait` disables it and returns immediately after delivery.
196223- `--timeout <seconds>` sets the maximum wait duration (default: 30, range: 1–300). Ignored when `--no-wait` is used.
···201228### Grammar
202229203230```bash
204204-prowl key [selector] <token> [--repeat <n>] [--json]
231231+prowl key [flags] <token>
232232+prowl key [flags] <target> <token>
205233```
206234235235+Where `[flags]` includes `[--repeat <n>] [--json]` and optional selector flags (`-t`, `--worktree`, `--tab`, `--pane`).
236236+207237### Rules
208238209209-- Exactly one positional `<token>` required.
239239+- Positional argument count determines interpretation:
240240+ - 1 arg: key token to current pane.
241241+ - 2 args: first is auto-resolved target, second is key token.
242242+- Flag selector overrides positional target.
243243+- Exactly one key token required.
210244- Token parsing is case-insensitive; canonical output token is lowercase kebab-case.
211245- Alias normalization follows `key.md`.
212246- `--repeat` default is `1`, range `1...100`.
···217251### Grammar
218252219253```bash
220220-prowl read [selector] [--json]
221221-prowl read [selector] --last <n> [--json]
254254+prowl read [<target>] [--last <n>] [--json]
255255+prowl read [-t <...> | --worktree <...> | --tab <...> | --pane <...>] [--last <n>] [--json]
222256```
223257224258### Rules
···279313```bash
280314prowl .
281315prowl open ~/Projects/Prowl
282282-prowl focus --pane 6E1A2A10-D99F-4E3F-920C-D93AA3C05764 --json
316316+prowl focus 6E1A2A10-D99F-4E3F-920C-D93AA3C05764 # auto-resolve pane UUID
317317+prowl focus --pane 6E1A2A10-D99F-4E3F-920C-D93AA3C05764 # explicit pane
318318+prowl focus main # auto-resolve worktree name
319319+prowl send "echo hello" # text to current pane
320320+prowl send 6E1A2A10-D99F-4E3F-920C-D93AA3C05764 "echo hi" # target + text
283321printf 'git status' | prowl send --worktree Prowl --json
284284-prowl key --pane 6E1A2A10-D99F-4E3F-920C-D93AA3C05764 return --repeat 2 --json
285285-prowl read --tab 2FC00CF0-3974-4E1B-BEF8-7A08A8E3B7C0 --last 200 --json
322322+prowl key enter # key to current pane
323323+prowl key 6E1A2A10-D99F-4E3F-920C-D93AA3C05764 ctrl-c # target + key
324324+prowl read 6E1A2A10-D99F-4E3F-920C-D93AA3C05764 --last 200 # positional target + flag
286325```
287326288327Invalid:
289328290329```bash
291330prowl focus --pane <id> --tab <id> # multiple selectors
331331+prowl focus --pane <id> <positional> # flag + positional (flag wins, positional ignored)
292332prowl send "echo hi" < /tmp/input.txt # two input sources
293333prowl key --repeat 0 enter # repeat out of range
294334prowl list --pane <id> # list does not accept selector