diff --git a/crates-cli/yaak-cli/PLAN.md b/crates-cli/yaak-cli/PLAN.md new file mode 100644 index 00000000..91b7d196 --- /dev/null +++ b/crates-cli/yaak-cli/PLAN.md @@ -0,0 +1,198 @@ +# CLI Command Architecture Plan + +## Goal + +Redesign the yaak-cli command structure to use a resource-oriented ` ` +pattern that scales well, is discoverable, and supports both human and LLM workflows. + +## Command Architecture + +### Design Principles + +- **Resource-oriented**: top-level commands are nouns, subcommands are verbs +- **Polymorphic requests**: `request` covers HTTP, gRPC, and WebSocket — the CLI + resolves the type via `get_any_request` and adapts behavior accordingly +- **Simple creation, full-fidelity via JSON**: human-friendly flags for basic creation, + `--json` for full control (targeted at LLM and scripting workflows) +- **Runtime schema introspection**: `request schema` outputs JSON Schema for the request + models, with dynamic auth fields populated from loaded plugins at runtime +- **Destructive actions require confirmation**: `delete` commands prompt for user + confirmation before proceeding. Can be bypassed with `--yes` / `-y` for scripting + +### Commands + +``` +# Top-level shortcut +yaakcli send [-e ] # id can be a request, folder, or workspace + +# Resource commands +yaakcli workspace list +yaakcli workspace show +yaakcli workspace create --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 + +yaakcli request list +yaakcli request show +yaakcli request create --name --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 [-e ] +yaakcli request delete +yaakcli request schema + +yaakcli folder list +yaakcli folder show +yaakcli folder create --name +yaakcli folder create --json '{"workspaceId": "wk_abc", "name": "Auth"}' +yaakcli folder update --json '{"id": "fl_abc", "name": "New Name"}' +yaakcli folder delete + +yaakcli environment list +yaakcli environment show +yaakcli environment create --name +yaakcli environment create --json '{"workspaceId": "wk_abc", "name": "Production"}' +yaakcli environment update --json '{"id": "ev_abc", ...}' +yaakcli environment delete + +``` + +### `send` — Top-Level Shortcut + +`yaakcli send ` is a convenience alias that accepts any sendable ID. It tries +each type in order via DB lookups (short-circuiting on first match): + +1. Request (HTTP, gRPC, or WebSocket via `get_any_request`) +2. Folder (sends all requests in the folder) +3. 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 ` 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: + +1. Generate base schema from `schemars::JsonSchema` derive on the Rust model structs +2. Load plugins, collect auth strategy definitions and their form inputs +3. Merge plugin-defined auth fields into the `authentication` property as a `oneOf` +4. 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. + +1. Create module structure: `commands/workspace.rs`, `commands/request.rs`, etc. +2. Define nested clap enums: + ```rust + enum Commands { + Send(SendArgs), + Workspace(WorkspaceArgs), + Request(RequestArgs), + Folder(FolderArgs), + Environment(EnvironmentArgs), + } + ``` +3. Move existing `Workspaces` logic into `workspace list` +4. Move existing `Requests` logic into `request list` +5. Move existing `Send` logic into `request send` +6. Move existing `Create` logic into `request create` +7. Delete the `Get` command entirely +8. Extract shared setup (DB init, plugin init, encryption) into a reusable context struct + +### Phase 2: Add missing CRUD commands + +1. `workspace show ` +2. `workspace create --name ` (and `--json`) +3. `workspace update --json` +4. `workspace delete ` +5. `request show ` (JSON output of the full request model) +6. `request delete ` +7. `folder list ` +8. `folder show ` +9. `folder create --name ` (and `--json`) +10. `folder update --json` +11. `folder delete ` +12. `environment list ` +13. `environment show ` +14. `environment create --name ` (and `--json`) +15. `environment update --json` +16. `environment delete ` + +### Phase 3: JSON input for create/update + +Both commands accept JSON via `--json ` or as a positional argument (detected +by leading `{`). They follow the same upsert pattern as the plugin API. + +- **`create --json`**: JSON must include `workspaceId`. Must NOT include `id` (or + use empty string `""`). Deserializes into the model with defaults for missing fields, + then upserts (insert). +- **`update --json`**: JSON must include `id`. Performs a fetch-merge-upsert: + 1. Fetch the existing model from DB + 2. Serialize it to `serde_json::Value` + 3. Deep-merge the user's partial JSON on top (JSON Merge Patch / RFC 7386 semantics) + 4. Deserialize back into the typed model + 5. 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 `null` removes it (for `Option` fields), per RFC 7386. + +Implementation: +1. Add `--json` flag and positional JSON detection to `create` commands +2. Add `update` commands with required `--json` flag +3. Implement JSON merge utility (or use `json-patch` crate) + +### Phase 4: Runtime schema generation + +1. Add `schemars` dependency to `yaak-models` +2. Derive `JsonSchema` on `HttpRequest`, `GrpcRequest`, `WebsocketRequest`, and their + nested types (`HttpRequestHeader`, `HttpUrlParameter`, etc.) +3. Implement `request schema` command: + - Generate base schema from schemars + - Query plugins for auth strategy form inputs + - Convert plugin form inputs into JSON Schema properties + - Merge into the `authentication` field + - Print to stdout + +### Phase 5: Polymorphic send + +1. Update `request send` to use `get_any_request` to resolve the request type +2. Match on `AnyRequest` variant and dispatch to the appropriate sender: + - `AnyRequest::HttpRequest` — existing HTTP send logic + - `AnyRequest::GrpcRequest` — gRPC invoke (future implementation) + - `AnyRequest::WebsocketRequest` — WebSocket connect (future implementation) +3. gRPC and WebSocket send can initially return "not yet implemented" errors + +### Phase 6: Top-level `send` and folder/workspace send + +1. Add top-level `yaakcli send ` command +2. Resolve ID by trying DB lookups in order: any_request → folder → workspace +3. For folder: list all requests in folder, send each +4. For workspace: list all requests in workspace, send each +5. Add execution options: `--sequential` (default), `--parallel`, `--fail-fast` + +## Crate Changes + +- **yaak-cli**: restructure into modules, new clap hierarchy +- **yaak-models**: add `schemars` dependency, derive `JsonSchema` on model structs + (current derives: `Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS`)