12 KiB
CLI Command Architecture Plan
Goal
Redesign the yaak-cli command structure to use a resource-oriented <resource> <action>
pattern that scales well, is discoverable, and supports both human and LLM workflows.
Status Snapshot
Current branch state:
- Modular CLI structure with command modules and shared
CliContext - Resource/action hierarchy in place for:
workspace list|show|create|update|deleterequest list|show|create|update|send|deletefolder list|show|create|update|deleteenvironment list|show|create|update|delete
- Top-level
sendexists as a request-send shortcut (not yet flexible request/folder/workspace resolution) - Legacy
getcommand removed - JSON create/update flow implemented (
--jsonand positional JSON shorthand) - No
request schemacommand yet
Progress checklist:
- Phase 1 complete
- Phase 2 complete
- Phase 3 complete
- Phase 4 complete
- Phase 5 complete
- Phase 6 complete
Command Architecture
Design Principles
- Resource-oriented: top-level commands are nouns, subcommands are verbs
- Polymorphic requests:
requestcovers HTTP, gRPC, and WebSocket — the CLI resolves the type viaget_any_requestand adapts behavior accordingly - Simple creation, full-fidelity via JSON: human-friendly flags for basic creation,
--jsonfor full control (targeted at LLM and scripting workflows) - Runtime schema introspection:
request schemaoutputs JSON Schema for the request models, with dynamic auth fields populated from loaded plugins at runtime - Destructive actions require confirmation:
deletecommands prompt for user confirmation before proceeding. Can be bypassed with--yes/-yfor scripting
Commands
# Top-level shortcut
yaakcli send <id> [-e <env_id>] # id can be a request, folder, or workspace
# Resource commands
yaakcli workspace list
yaakcli workspace show <id>
yaakcli workspace create --name <name>
yaakcli workspace create --json '{"name": "My Workspace"}'
yaakcli workspace create '{"name": "My Workspace"}' # positional JSON shorthand
yaakcli workspace update --json '{"id": "wk_abc", "name": "New Name"}'
yaakcli workspace delete <id>
yaakcli request list <workspace_id>
yaakcli request show <id>
yaakcli request create <workspace_id> --name <name> --url <url> [--method GET]
yaakcli request create --json '{"workspaceId": "wk_abc", "url": "..."}'
yaakcli request update --json '{"id": "rq_abc", "url": "https://new.com"}'
yaakcli request send <id> [-e <env_id>]
yaakcli request delete <id>
yaakcli request schema <http|grpc|websocket>
yaakcli folder list <workspace_id>
yaakcli folder show <id>
yaakcli folder create <workspace_id> --name <name>
yaakcli folder create --json '{"workspaceId": "wk_abc", "name": "Auth"}'
yaakcli folder update --json '{"id": "fl_abc", "name": "New Name"}'
yaakcli folder delete <id>
yaakcli environment list <workspace_id>
yaakcli environment show <id>
yaakcli environment create <workspace_id> --name <name>
yaakcli environment create --json '{"workspaceId": "wk_abc", "name": "Production"}'
yaakcli environment update --json '{"id": "ev_abc", ...}'
yaakcli environment delete <id>
send — Top-Level Shortcut
yaakcli send <id> is a convenience alias that accepts any sendable ID. It tries
each type in order via DB lookups (short-circuiting on first match):
- Request (HTTP, gRPC, or WebSocket via
get_any_request) - Folder (sends all requests in the folder)
- Workspace (sends all requests in the workspace)
ID prefixes exist (e.g. rq_, fl_, wk_) but are not relied upon — resolution
is purely by DB lookup.
request send <id> is the same but restricted to request IDs only.
Request Send — Polymorphic Behavior
send means "execute this request" regardless of protocol:
- HTTP: send request, print response, exit
- gRPC: invoke the method; for streaming, stream output to stdout until done/Ctrl+C
- WebSocket: connect, stream messages to stdout until closed/Ctrl+C
request schema — Runtime JSON Schema
Outputs a JSON Schema describing the full request shape, including dynamic fields:
- Generate base schema from
schemars::JsonSchemaderive on the Rust model structs - Load plugins, collect auth strategy definitions and their form inputs
- Merge plugin-defined auth fields into the
authenticationproperty as aoneOf - Output the combined schema as JSON
This lets an LLM call schema, read the shape, and construct valid JSON for
create --json or update --json.
Implementation Steps
Phase 1: Restructure commands (no new functionality)
Refactor main.rs into the new resource/action pattern using clap subcommand nesting.
Existing behavior stays the same, just reorganized. Remove the get command.
- Create module structure:
commands/workspace.rs,commands/request.rs, etc. - Define nested clap enums:
enum Commands { Send(SendArgs), Workspace(WorkspaceArgs), Request(RequestArgs), Folder(FolderArgs), Environment(EnvironmentArgs), } - Move existing
Workspaceslogic intoworkspace list - Move existing
Requestslogic intorequest list - Move existing
Sendlogic intorequest send - Move existing
Createlogic intorequest create - Delete the
Getcommand entirely - Extract shared setup (DB init, plugin init, encryption) into a reusable context struct
Phase 2: Add missing CRUD commands
Status: complete
workspace show <id>workspace create --name <name>(and--json)workspace update --jsonworkspace delete <id>request show <id>(JSON output of the full request model)request delete <id>folder list <workspace_id>folder show <id>folder create <workspace_id> --name <name>(and--json)folder update --jsonfolder delete <id>environment list <workspace_id>environment show <id>environment create <workspace_id> --name <name>(and--json)environment update --jsonenvironment delete <id>
Phase 3: JSON input for create/update
Both commands accept JSON via --json <string> or as a positional argument (detected
by leading {). They follow the same upsert pattern as the plugin API.
-
create --json: JSON must includeworkspaceId. Must NOT includeid(or use empty string""). Deserializes into the model with defaults for missing fields, then upserts (insert). -
update --json: JSON must includeid. Performs a fetch-merge-upsert:- Fetch the existing model from DB
- Serialize it to
serde_json::Value - Deep-merge the user's partial JSON on top (JSON Merge Patch / RFC 7386 semantics)
- Deserialize back into the typed model
- Upsert (update)
This matches how the MCP server plugin already does it (fetch existing, spread, override), but the CLI handles the merge server-side so callers don't have to.
Setting a field to
nullremoves it (forOption<T>fields), per RFC 7386.
Implementation:
- Add
--jsonflag and positional JSON detection tocreatecommands - Add
updatecommands with required--jsonflag - Implement JSON merge utility (or use
json-patchcrate)
Phase 4: Runtime schema generation
- Add
schemarsdependency toyaak-models - Derive
JsonSchemaonHttpRequest,GrpcRequest,WebsocketRequest, and their nested types (HttpRequestHeader,HttpUrlParameter, etc.) - Implement
request schemacommand:- Generate base schema from schemars
- Query plugins for auth strategy form inputs
- Convert plugin form inputs into JSON Schema properties
- Merge into the
authenticationfield - Print to stdout
Phase 5: Polymorphic send
- Update
request sendto useget_any_requestto resolve the request type - Match on
AnyRequestvariant and dispatch to the appropriate sender:AnyRequest::HttpRequest— existing HTTP send logicAnyRequest::GrpcRequest— gRPC invoke (future implementation)AnyRequest::WebsocketRequest— WebSocket connect (future implementation)
- gRPC and WebSocket send can initially return "not yet implemented" errors
Phase 6: Top-level send and folder/workspace send
- Add top-level
yaakcli send <id>command - Resolve ID by trying DB lookups in order: any_request → folder → workspace
- For folder: list all requests in folder, send each
- For workspace: list all requests in workspace, send each
- Add execution options:
--sequential(default),--parallel,--fail-fast
Execution Plan (PR Slices)
PR 1: Command tree refactor + compatibility aliases
Scope:
- Introduce
commands/modules and aCliContextfor shared setup - Add new clap hierarchy (
workspace,request,folder,environment) - Route existing behavior into:
workspace listrequest list <workspace_id>request send <id>request create <workspace_id> ...
- Keep compatibility aliases temporarily:
workspaces->workspace listrequests <workspace_id>->request list <workspace_id>create ...->request create ...
- Remove
getand update help text
Acceptance criteria:
yaakcli --helpshows noun/verb structure- Existing list/send/create workflows still work
- No behavior change in HTTP send output format
PR 2: CRUD surface area
Scope:
- Implement
show/create/update/deleteforworkspace,request,folder,environment - Ensure delete commands require confirmation by default (
--yesbypass) - Normalize output format for list/show/create/update/delete responses
Acceptance criteria:
- Every command listed in the "Commands" section parses and executes
- Delete commands are safe by default in interactive terminals
--yessupports non-interactive scripts
PR 3: JSON input + merge patch semantics
Scope:
- Add shared parser for
--jsonand positional JSON shorthand - Add
create --jsonandupdate --jsonfor all mutable resources - Implement server-side RFC 7386 merge patch behavior
- Add guardrails:
create --json: reject non-emptyidupdate --json: requireid
Acceptance criteria:
- Partial
update --jsononly modifies provided keys nullclears optional values- Invalid JSON and missing required fields return actionable errors
PR 4: request schema and plugin auth integration
Scope:
- Add
schemarstoyaak-modelsand deriveJsonSchemafor request models - Implement
request schema <http|grpc|websocket> - Merge plugin auth form inputs into
authenticationschema at runtime
Acceptance criteria:
- Command prints valid JSON schema
- Schema reflects installed auth providers at runtime
- No panic when plugins fail to initialize (degrade gracefully)
PR 5: Polymorphic request send
Scope:
- Replace request resolution in
request sendwithget_any_request - Dispatch by request type
- Keep HTTP fully functional
- Return explicit NYI errors for gRPC/WebSocket until implemented
Acceptance criteria:
- HTTP behavior remains unchanged
- gRPC/WebSocket IDs are recognized and return explicit status
PR 6: Top-level send + bulk execution
Scope:
- Add top-level
send <id>for request/folder/workspace IDs - Implement folder/workspace fan-out execution
- Add execution controls:
--sequential,--parallel,--fail-fast
Acceptance criteria:
- Correct ID dispatch order: request -> folder -> workspace
- Deterministic summary output (success/failure counts)
- Non-zero exit code when any request fails (unless explicitly configured otherwise)
Validation Matrix
- CLI parsing tests for every command path (including aliases while retained)
- Integration tests against temp SQLite DB for CRUD flows
- Snapshot tests for output text where scripting compatibility matters
- Manual smoke tests:
- Send HTTP request with template/rendered vars
- JSON create/update for each resource
- Delete confirmation and
--yes - Top-level
sendon request/folder/workspace
Open Questions
- Should compatibility aliases (
workspaces,requests,create) be removed immediately or after one release cycle? - For bulk
send, should default behavior stop on first failure or continue and summarize? - Should command output default to human-readable text with an optional
--format json, or return JSON by default forshow/list? - For
request schema, should plugin-derived auth fields be namespaced by plugin ID to avoid collisions?
Crate Changes
- yaak-cli: restructure into modules, new clap hierarchy
- yaak-models: add
schemarsdependency, deriveJsonSchemaon model structs (current derives:Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)