···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 user 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
···6161 guard let normalized = KeyTokens.normalize(rawToken) else {
6262 throw ExitError(
6363 code: CLIErrorCode.unsupportedKey,
6464- message: "The key token '\(rawToken.lowercased())' is not supported in v1."
6464+ message: "The key token '\(rawToken.lowercased())' is not supported."
6565 )
6666 }
6767
+36-1
ProwlCLITests/ProwlCLIIntegrationTests.swift
···808808 }
809809810810 func testKeyCommandRejectUnsupportedKey() throws {
811811- let result = try runProwl(args: ["key", "ctrl-z", "--json"])
811811+ let result = try runProwl(args: ["key", "hyper-k", "--json"])
812812 XCTAssertNotEqual(result.exitCode, 0)
813813 let payload = try jsonObject(from: result.stdout)
814814 XCTAssertEqual(payload["ok"] as? Bool, false)
···963963 XCTAssertEqual(input.rawToken, alias, "rawToken should preserve '\(alias)'")
964964 } else {
965965 XCTFail("Expected key command envelope for alias '\(alias)'")
966966+ }
967967+ }
968968+ }
969969+970970+ func testKeyCommandExpandedTokensAccepted() throws {
971971+ let tokenCases: [(raw: String, normalized: String)] = [
972972+ ("cmd-c", "cmd-c"),
973973+ ("command-shift-k", "cmd-shift-k"),
974974+ ("alt-enter", "opt-enter"),
975975+ ("ctrl-z", "ctrl-z"),
976976+ ("deleteforward", "delete-forward"),
977977+ ("f12", "f12"),
978978+ ]
979979+980980+ for (raw, normalized) in tokenCases {
981981+ let socketPath = temporarySocketPath(suffix: "key-expanded-\(normalized.replacingOccurrences(of: "-", with: "_"))")
982982+ let response = CommandResponse(
983983+ ok: true,
984984+ command: "key",
985985+ schemaVersion: "prowl.cli.key.v1"
986986+ )
987987+988988+ let (requestData, result) = try runWithMockServer(
989989+ socketPath: socketPath,
990990+ response: response,
991991+ args: ["key", raw, "--json"]
992992+ )
993993+994994+ XCTAssertEqual(result.exitCode, 0, "Token '\(raw)' should be accepted")
995995+ let envelope = try JSONDecoder().decode(CommandEnvelope.self, from: requestData)
996996+ if case .key(let input) = envelope.command {
997997+ XCTAssertEqual(input.token, normalized)
998998+ XCTAssertEqual(input.rawToken, raw)
999999+ } else {
10001000+ XCTFail("Expected key command envelope for token '\(raw)'")
9661001 }
9671002 }
9681003 }
+20-23
doc-onevcat/contracts/cli/key.md
···8899## What changed in this revision
10101111-Compared with the initial draft, this version makes `key` safer and more script-friendly:
1111+Compared with the initial draft, this version makes `key` broader and more script-friendly:
12121313-- define a strict input grammar with aliases (`return` -> `enter`, `escape` -> `esc`, `pgup` -> `pageup`, ...)
1313+- normalize aliases (`return` -> `enter`, `escape` -> `esc`, `pgup` -> `pageup`, ...)
1414+- accept modifier prefixes and combinations such as `cmd-c`, `shift-tab`, `opt-enter`, `cmd-shift-k`
1515+- accept additional named keys such as `delete-forward`, `insert`, and `f1`...`f12`
1416- add `--repeat <n>` as first-class input instead of forcing shell loops
1517- split `requested` vs `normalized` in output so callers can debug token normalization
1618- add `delivery` counters for machine verification (`attempted` / `delivered`)
···60626163### Canonical tokens
62646363-- `enter`
6464-- `esc`
6565-- `tab`
6666-- `backspace`
6767-- `up`
6868-- `down`
6969-- `left`
7070-- `right`
7171-- `pageup`
7272-- `pagedown`
7373-- `home`
7474-- `end`
7575-- `ctrl-c`
7676-- `ctrl-d`
7777-- `ctrl-l`
6565+Canonical normalized tokens now include:
6666+6767+- navigation keys such as `tab`, `up`, `down`, `left`, `right`, `pageup`, `pagedown`, `home`, `end`
6868+- editing keys such as `enter`, `backspace`, `delete-forward`, `insert`
6969+- printable keys such as letters, digits, and common punctuation
7070+- control combinations such as `ctrl-c`, `ctrl-d`, `ctrl-l`, `ctrl-z`
7171+- shortcut combinations such as `cmd-c`, `cmd-shift-k`, `shift-tab`, `opt-enter`
7272+- function keys `f1`...`f12`
78737974### Accepted aliases (normalized to canonical)
8075···8681- `arrow-right` -> `right`
8782- `pgup` -> `pageup`
8883- `pgdn` -> `pagedown`
8989-- `ctrl+c` -> `ctrl-c`
9090-- `ctrl+d` -> `ctrl-d`
9191-- `ctrl+l` -> `ctrl-l`
8484+- `forward-delete` / `deleteforward` -> `delete-forward`
8585+- `ins` -> `insert`
8686+- `command-*` -> `cmd-*`
8787+- `alt-*` / `option-*` -> `opt-*`
8888+- `ctrl+*` -> `ctrl-*`
92899390### `--repeat`
9491···162159- `normalized`: string
163160 - canonical token used by runtime
164161 - must be one of the canonical tokens listed above
165165-- `category`: `"navigation"` | `"editing"` | `"control"`
162162+- `category`: `"navigation"` | `"editing"` | `"control"` | `"shortcut"` | `"function"`
166163167164### `delivery`
168165···216213 "schema_version": "prowl.cli.key.v1",
217214 "error": {
218215 "code": "UNSUPPORTED_KEY",
219219- "message": "The key token 'ctrl-z' is not supported in v1",
216216+ "message": "The key token 'hyper-k' is not supported.",
220217 "details": {
221221- "token": "ctrl-z"
218218+ "token": "hyper-k"
222219 }
223220 }
224221}
+1-1
supacode/CLIService/KeyCommandHandler.swift
···7070 guard let category = KeyTokens.category(for: input.token) else {
7171 return errorResponse(
7272 code: CLIErrorCode.unsupportedKey,
7373- message: "The key token '\(input.token)' is not supported in v1."
7373+ message: "The key token '\(input.token)' is not supported."
7474 )
7575 }
7676