native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #104 from onevcat/onevclaw/issue-70-input-contract-and-architecture-plan

authored by

Wei Wang and committed by
GitHub
6b23dcd5 43da56f6

+512
+211
doc-onevcat/contracts/cli/architecture.md
··· 1 + # CLI Architecture & App Interaction Plan (Phase 1) 2 + 3 + Status: implementation plan for `#70` after contract alignment. 4 + 5 + This plan defines where CLI logic lives, how requests are transported to a running app, and how command execution is routed inside Prowl. 6 + 7 + --- 8 + 9 + ## 1) Goals 10 + 11 + - Make `prowl` a stable machine interface for a running Prowl instance. 12 + - Keep parsing and validation outside app runtime logic. 13 + - Reuse existing repository/terminal capabilities instead of rebuilding terminal core. 14 + - Align runtime behavior with contract docs under `doc-onevcat/contracts/cli/`. 15 + 16 + --- 17 + 18 + ## 2) Architectural decision (v1) 19 + 20 + ## Decision A: first-class CLI binary 21 + 22 + `prowl` MUST be implemented as a first-class Swift executable (ArgumentParser-based), not shell-script business logic. 23 + 24 + - Existing `bin/prowl` shell implementation is discarded. 25 + - Parsing truth and input validation must live in Swift CLI module. 26 + 27 + Why: 28 + 29 + - strict typed request model 30 + - easier testability (unit tests for parser) 31 + - deterministic behavior across commands 32 + - lower long-term drift vs app contracts 33 + 34 + ## Decision B: explicit app command service boundary 35 + 36 + CLI communicates with app through a dedicated command service boundary: 37 + 38 + - CLI side: build normalized command request 39 + - App side: resolve target + execute + return normalized response 40 + 41 + App should not re-interpret argv-level ambiguity. 42 + 43 + ## Decision C: command execution is app-owned 44 + 45 + Phase-1 commands are **remote-control actions on running app state**. 46 + 47 + - Open/path, list, focus, send, key, read all execute in app process context. 48 + - `open` must be able to launch app when it is not running. 49 + - CLI is transport + contract adapter, not a parallel runtime. 50 + 51 + --- 52 + 53 + ## 3) Proposed module layout 54 + 55 + ## 3.1 CLI side 56 + 57 + `ProwlCLI` target: 58 + 59 + - `CommandParser` 60 + - ArgumentParser commands and options 61 + - validation and normalization 62 + - `InputModel` 63 + - typed `OpenInput/ListInput/...` 64 + - `TransportClient` 65 + - send request to running app 66 + - receive structured response 67 + - `OutputRenderer` 68 + - `--json`: raw contract payload 69 + - text mode: readable summary 70 + 71 + ## 3.2 App side 72 + 73 + `CLICommandService` (new boundary in app): 74 + 75 + - `CommandRouter` 76 + - map command envelope -> handler 77 + - Handlers 78 + - `OpenCommandHandler` 79 + - `ListCommandHandler` 80 + - `FocusCommandHandler` 81 + - `SendCommandHandler` 82 + - `KeyCommandHandler` 83 + - `ReadCommandHandler` 84 + - Shared services 85 + - `TargetResolver` 86 + - `TerminalCommandBridge` 87 + - `RepositorySelectionBridge` 88 + 89 + Handlers should return response objects already matching v1 contracts. 90 + 91 + --- 92 + 93 + ## 4) Transport plan 94 + 95 + v1 target: **single local IPC channel** (implementation choice can be refined), but API contract is fixed: 96 + 97 + ```swift 98 + request(CommandEnvelope) -> CommandResponse 99 + ``` 100 + 101 + Transport requirements: 102 + 103 + - local machine only 104 + - talk to existing running app instance 105 + - clear app-not-running error mapping 106 + - request timeout + cancellation mapping 107 + 108 + If transport fails: 109 + 110 + - return command-specific failure with stable `error.code` 111 + - avoid leaking transport internals in machine contract 112 + 113 + --- 114 + 115 + ## 5) App interaction flow (command lifecycle) 116 + 117 + 1. CLI parses argv + stdin -> normalized typed input. 118 + 2. CLI builds command envelope (`command`, `outputMode`, `requestId` optional). 119 + 3. CLI sends envelope to app command service. 120 + 4. App command router resolves target context and executes action. 121 + 5. For open-entry commands, if app is not running, app launch is part of command execution. 122 + 6. App returns structured success/error response. 123 + 7. CLI renders JSON or text. 124 + 125 + This ensures one authoritative runtime path for both GUI-triggered and CLI-triggered actions. 126 + 127 + --- 128 + 129 + ## 6) Target resolution ownership 130 + 131 + Resolution belongs to app runtime (state-aware), with CLI only enforcing selector syntax: 132 + 133 + - CLI checks selector validity and exclusivity. 134 + - App maps selector to concrete `worktree/tab/pane` in current state. 135 + - App returns resolved target in output (per existing contracts). 136 + 137 + --- 138 + 139 + ## 7) Mapping to existing contracts 140 + 141 + - Input normalization rules: `input.md` 142 + - Output contracts: 143 + - `open.md` 144 + - `list.md` 145 + - `focus.md` 146 + - `send.md` 147 + - `key.md` 148 + - `read.md` 149 + - JSON schema validation source: 150 + - `schema.md` 151 + 152 + Implementation MUST be validated against `schema.md` for `--json` mode. 153 + 154 + --- 155 + 156 + ## 8) Plan by milestones 157 + 158 + ## M0 — contract lock 159 + 160 + - Land `input.md` and this architecture plan. 161 + - Freeze selector, stdin/argv, key repeat, read-last semantics. 162 + 163 + ## M1 — parser/runtime split 164 + 165 + - Introduce Swift `prowl` executable target. 166 + - Discard shell implementation and keep command parsing in Swift only. 167 + 168 + ## M2 — command service scaffold 169 + 170 + - Add app-side command router and handler protocols. 171 + - Implement no-op or open-only path to verify transport. 172 + 173 + ## M3 — implement phase-1 handlers 174 + 175 + - `open` behavior aligned with #64 176 + - `list/focus/send/key/read` wired to existing terminal/repository features 177 + - full error-code mapping per contracts 178 + 179 + ## M4 — test and harden 180 + 181 + - parser unit tests (argv matrix) 182 + - contract tests (`--json` payload schema validation) 183 + - integration tests for `list->focus->send/key->read` loops 184 + 185 + --- 186 + 187 + ## 9) Testing strategy 188 + 189 + - Parser golden tests: 190 + - valid/invalid token combinations 191 + - selector exclusivity 192 + - stdin/argv source rules for `send` 193 + - `--last` and `--repeat` constraints 194 + - Contract tests: 195 + - validate JSON against `schema.md` refs per subcommand 196 + - Runtime integration tests: 197 + - open exact-root / inside-root / new-root 198 + - key alias normalization and repeat delivery counters 199 + - read source/mode/last semantics 200 + 201 + --- 202 + 203 + ## 10) Why this supersedes ad-hoc approach 204 + 205 + This plan intentionally prevents a repeat of mixed concerns where: 206 + 207 + - parser logic lives in shell 208 + - app behavior evolves independently 209 + - CI churn appears before contract decisions are final 210 + 211 + By locking input + architecture first, we can implement all commands consistently and avoid contract drift.
+301
doc-onevcat/contracts/cli/input.md
··· 1 + # CLI Input Contract: `prowl` (v1) 2 + 3 + Status: draft truth source for `#70` implementation. 4 + 5 + This file defines **input-side** rules for the phase-1 CLI commands: 6 + 7 + - `open` 8 + - `list` 9 + - `focus` 10 + - `send` 11 + - `key` 12 + - `read` 13 + 14 + It complements output contracts under `doc-onevcat/contracts/cli/{open,list,focus,send,key,read}.md`. 15 + 16 + --- 17 + 18 + ## 1) Design goals 19 + 20 + - One stable command grammar for both humans and agents. 21 + - No hidden priority chains that make scripts nondeterministic. 22 + - Parse once in CLI layer; app layer should receive already-normalized typed requests. 23 + - Keep command behavior composable: `list -> focus/send/key/read`. 24 + 25 + --- 26 + 27 + ## 2) Global command model 28 + 29 + ### 2.1 Canonical form 30 + 31 + ```bash 32 + prowl <subcommand> [target-selector] [command-args] [output-options] 33 + ``` 34 + 35 + ### 2.2 Supported subcommands (v1) 36 + 37 + - `open` 38 + - `list` 39 + - `focus` 40 + - `send` 41 + - `key` 42 + - `read` 43 + 44 + Global options (not subcommands): 45 + 46 + - `--help` 47 + - `--version` 48 + 49 + ### 2.3 Bare path entry 50 + 51 + These are equivalent to `open` entry: 52 + 53 + - `prowl` 54 + - `prowl <path-like-first-arg>` 55 + - `prowl open <path>` 56 + 57 + Path-like first arg (v1): 58 + 59 + - `/...` 60 + - `./...` 61 + - `../...` 62 + - `~/...` 63 + - `file://...` 64 + - `.` 65 + - `..` 66 + 67 + ### 2.4 `--` handling 68 + 69 + `--` stops option parsing and forces following token parsing as positional arguments. 70 + 71 + - `prowl -- ./focus` MUST be treated as path entry (`open`), not subcommand `focus`. 72 + - `prowl open -- --weird-dir` MUST treat `--weird-dir` as path. 73 + 74 + --- 75 + 76 + ## 3) Target selector contract (shared) 77 + 78 + ### 3.1 Selector flags 79 + 80 + - `--worktree <id|name|path>` 81 + - `--tab <id>` 82 + - `--pane <id>` 83 + 84 + ### 3.2 Mutual exclusivity (hard rule) 85 + 86 + Exactly **zero or one** selector is allowed. 87 + 88 + - `0 selector`: operate on current focused target (where command allows it). 89 + - `1 selector`: resolve with that selector. 90 + - `>1 selector`: error `INVALID_ARGUMENT`. 91 + 92 + This is preferred over implicit precedence because it is easier to reason about in scripts. 93 + 94 + ### 3.3 Resolution rules 95 + 96 + - `--pane`: exact pane. 97 + - `--tab`: current focused pane of target tab. 98 + - `--worktree`: selected tab + focused pane in target worktree. 99 + - none: currently focused pane in current context. 100 + 101 + If required context does not exist: 102 + 103 + - return command-specific not-found / no-active-pane error. 104 + 105 + --- 106 + 107 + ## 4) Common output flags 108 + 109 + ### 4.1 `--json` 110 + 111 + All phase-1 commands MUST support `--json`. 112 + 113 + - With `--json`, output MUST match corresponding schema in `schema.md`. 114 + - Without `--json`, output is human-readable text. 115 + 116 + ### 4.2 Exit behavior 117 + 118 + - Success: exit code `0` 119 + - Failure: non-zero 120 + - Error payload shape in JSON mode MUST follow command contract (`error.code`, `error.message`, optional `error.details`). 121 + 122 + (Exact numeric non-zero codes can be refined later; error `code` string is the machine contract.) 123 + 124 + --- 125 + 126 + ## 5) Per-command input rules 127 + 128 + ## 5.1 `open` 129 + 130 + ### Grammar 131 + 132 + ```bash 133 + prowl 134 + prowl <path-like> 135 + prowl open <path> 136 + ``` 137 + 138 + ### Rules 139 + 140 + - `prowl` without path is valid and means “open app / bring to front”. 141 + - `prowl <path-like>` is first-class, not shorthand hack. 142 + - `prowl open <path>` is explicit equivalent for scripts. 143 + - For all open-entry forms, if app is not running, CLI MUST launch Prowl and complete the open/focus flow. 144 + - Path MUST be normalized by CLI: 145 + - expand `~` 146 + - resolve relative path to absolute path 147 + - resolve `file://` 148 + - normalize `.` / `..` 149 + - If provided path does not exist or is not a directory: error (`PATH_NOT_FOUND` / `PATH_NOT_DIRECTORY`). 150 + 151 + ## 5.2 `list` 152 + 153 + ### Grammar 154 + 155 + ```bash 156 + prowl list [--json] 157 + ``` 158 + 159 + ### Rules 160 + 161 + - `list` MUST NOT accept target selectors in v1 (it is global discovery). 162 + - Extra positional args: `INVALID_ARGUMENT`. 163 + 164 + ## 5.3 `focus` 165 + 166 + ### Grammar 167 + 168 + ```bash 169 + prowl focus [--worktree <...> | --tab <...> | --pane <...>] [--json] 170 + ``` 171 + 172 + ### Rules 173 + 174 + - Selectors are optional; no selector means “focus current target and bring app front”. 175 + - More than one selector is invalid. 176 + 177 + ## 5.4 `send` 178 + 179 + ### Grammar 180 + 181 + ```bash 182 + prowl send [selector] [--no-enter] [--json] [<text>] 183 + # or 184 + printf '...' | prowl send [selector] [--no-enter] [--json] 185 + ``` 186 + 187 + ### Rules 188 + 189 + - Input source is exactly one of: 190 + - positional `<text>` (`argv`) 191 + - stdin (`stdin`) 192 + - Both provided simultaneously: `INVALID_ARGUMENT`. 193 + - Neither provided (or empty stdin): `EMPTY_INPUT`. 194 + - Default sends trailing Enter; `--no-enter` disables it. 195 + 196 + ## 5.5 `key` 197 + 198 + ### Grammar 199 + 200 + ```bash 201 + prowl key [selector] <token> [--repeat <n>] [--json] 202 + ``` 203 + 204 + ### Rules 205 + 206 + - Exactly one positional `<token>` required. 207 + - Token parsing is case-insensitive; canonical output token is lowercase kebab-case. 208 + - Alias normalization follows `key.md`. 209 + - `--repeat` default is `1`, range `1...100`. 210 + - `--repeat` out of range: `INVALID_REPEAT`. 211 + 212 + ## 5.6 `read` 213 + 214 + ### Grammar 215 + 216 + ```bash 217 + prowl read [selector] [--json] 218 + prowl read [selector] --last <n> [--json] 219 + ``` 220 + 221 + ### Rules 222 + 223 + - `--last` optional; if omitted, mode is `snapshot`. 224 + - `--last <n>` requires integer `n >= 1`; otherwise `INVALID_ARGUMENT`. 225 + - At most one `--last` value. 226 + 227 + --- 228 + 229 + ## 6) Reserved command tokens (v1) 230 + 231 + These tokens are reserved as first command token: 232 + 233 + - `open` 234 + - `list` 235 + - `focus` 236 + - `send` 237 + - `key` 238 + - `read` 239 + 240 + If first token matches a reserved command, CLI MUST parse as subcommand unless forced by `--` path form. 241 + 242 + `--help` / `--version` are handled as global options, not subcommands. 243 + 244 + --- 245 + 246 + ## 7) Normalized request model (input -> typed request) 247 + 248 + CLI parser MUST produce one normalized typed request before transport. 249 + 250 + Example shape: 251 + 252 + ```swift 253 + struct CommandEnvelope { 254 + var output: OutputMode // text | json 255 + var command: Command 256 + } 257 + 258 + enum Command { 259 + case open(OpenInput) 260 + case list(ListInput) 261 + case focus(FocusInput) 262 + case send(SendInput) 263 + case key(KeyInput) 264 + case read(ReadInput) 265 + } 266 + ``` 267 + 268 + This model is the handoff contract to app/transport layer. 269 + 270 + --- 271 + 272 + ## 8) Examples (valid / invalid) 273 + 274 + Valid: 275 + 276 + ```bash 277 + prowl . 278 + prowl open ~/Projects/Prowl 279 + prowl focus --pane 6E1A2A10-D99F-4E3F-920C-D93AA3C05764 --json 280 + printf 'git status' | prowl send --worktree Prowl --json 281 + prowl key --pane 6E1A2A10-D99F-4E3F-920C-D93AA3C05764 return --repeat 2 --json 282 + prowl read --tab 2FC00CF0-3974-4E1B-BEF8-7A08A8E3B7C0 --last 200 --json 283 + ``` 284 + 285 + Invalid: 286 + 287 + ```bash 288 + prowl focus --pane <id> --tab <id> # multiple selectors 289 + prowl send "echo hi" < /tmp/input.txt # two input sources 290 + prowl key --repeat 0 enter # repeat out of range 291 + prowl list --pane <id> # list does not accept selector 292 + ``` 293 + 294 + --- 295 + 296 + ## 9) Non-goals (v1) 297 + 298 + - No complex selector query language (`--where ...`). 299 + - No streaming mode for `read`. 300 + - No macro system for `key`. 301 + - No dual parser implementations in v1.