mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-08-16 00:21:55 +02:00
Move CLI facts out of the skill and into the CLI
The skill had grown into a reference manual: body type tables, auth strategy lists, a template function table, field-by-field OAuth 2.0 config. All of it goes stale, because the user's CLI version and their installed plugins decide what actually exists. On this machine a faker plugin contributes 274 template functions; the skill listed eleven, and got some names wrong. The CLI should carry that knowledge, so: - `yaak template-function list [filter]` and `template-function show <name>` report what the loaded plugins actually provide, the same way `request schema http` already merges in plugin auth strategies. - `yaak folder schema` now exists, so folder payloads are discoverable like every other model. Required deriving JsonSchema on Folder. - The request schema documents `bodyType` and `body` shapes, and states that setting a body type does not add a Content-Type header. The skill drops to a single 135-line SKILL.md that teaches the model, the workflows, and how to interrogate the CLI, and says outright that the CLI wins when the two disagree.
This commit is contained in:
@@ -11,256 +11,125 @@ description: >
|
||||
call, exercise, or smoke test an HTTP or REST endpoint, to save or organize
|
||||
API requests for reuse, to set up API requests for manual testing, to add auth
|
||||
to a saved request, to turn an OpenAPI or Postman collection into runnable
|
||||
requests, or to run a saved request suite against staging versus production. Prefer this over one-off `curl` commands whenever
|
||||
the requests should be saved, reused, shared, or run as a set.
|
||||
requests, or to run a saved request suite against staging versus production.
|
||||
Prefer this over one-off `curl` commands whenever the requests should be
|
||||
saved, reused, shared, or run as a set.
|
||||
allowed-tools: Bash(yaak:*), Bash(which:*), Bash(command:*), Bash(npm:*), Bash(npx:*)
|
||||
---
|
||||
|
||||
# Use Yaak
|
||||
|
||||
Yaak is a desktop API client. The `yaak` CLI reads and writes the **same local
|
||||
database as the desktop app**, so anything created here shows up in the app
|
||||
database as the desktop app**, so anything you create shows up in the app
|
||||
immediately, and vice versa. There is no server and no sign-in: `yaak auth` is
|
||||
only for publishing plugins to the Yaak registry, not for any workflow below.
|
||||
only for publishing plugins to the Yaak registry.
|
||||
|
||||
Two consequences worth holding onto. Requests you create are permanent user data
|
||||
in an app they use, not scratch files, so name them the way the user would and
|
||||
clean up anything created just to test. And because the app is right there, the
|
||||
CLI is usually the wrong place to *read* a response in detail; it is the right
|
||||
place to build, organize, and run requests.
|
||||
|
||||
## The CLI describes itself
|
||||
|
||||
**This skill deliberately does not list fields, body types, auth strategies, or
|
||||
template functions.** The user's CLI version and installed plugins decide what
|
||||
exists, so any list written here would eventually be wrong. Ask the CLI:
|
||||
|
||||
```bash
|
||||
yaak --help # commands, plus agent hints at the bottom
|
||||
yaak <command> --help # flags for one command
|
||||
yaak request schema http --pretty # full request model, with guidance per field
|
||||
yaak template-function list [filter] # template functions from installed plugins
|
||||
yaak template-function show <name> # one function's arguments
|
||||
```
|
||||
|
||||
`request schema http` is generated from the real model and merges in the auth
|
||||
strategies contributed by plugins, so it is the authoritative answer for what a
|
||||
request payload may contain and what each auth strategy needs. `workspace`,
|
||||
`environment`, and `folder` have `schema` subcommands too.
|
||||
|
||||
Read the relevant schema before writing a JSON payload you are not certain of.
|
||||
That is faster than a failed send, and it stays correct as Yaak changes.
|
||||
|
||||
## Resource model
|
||||
|
||||
- **Workspace** (`wk_…`) is the top-level container. It owns everything else.
|
||||
- **Folder** (`fl_…`) groups requests inside a workspace and can nest. Folders
|
||||
carry headers and authentication that child requests inherit.
|
||||
- **Request** (`rq_…`) is a single HTTP, gRPC, or WebSocket request.
|
||||
- **Environment** (`ev_…`) holds variables. Every workspace has one base
|
||||
environment ("Global Variables") plus any number of sub-environments; a
|
||||
sub-environment overrides base variables of the same name.
|
||||
- **Cookie jar** (`cj_…`) stores cookies per workspace. The oldest jar is used
|
||||
by default; no setup needed.
|
||||
- **Workspace** (`wk_…`) is the top-level container.
|
||||
- **Folder** (`fl_…`) groups requests and can nest. Folders carry headers and
|
||||
authentication that child requests inherit, which is the usual way to apply
|
||||
one token to a whole group.
|
||||
- **Request** (`rq_…`) is a single HTTP, gRPC, or WebSocket request. The CLI can
|
||||
currently only create and send HTTP ones.
|
||||
- **Environment** (`ev_…`) holds variables. Each workspace has a base
|
||||
environment plus any number of sub-environments; a sub-environment overrides
|
||||
base variables of the same name and is chosen per send with `-e`.
|
||||
- **Cookie jar** (`cj_…`) stores cookies per workspace. The oldest is used by
|
||||
default, so this normally needs no attention.
|
||||
|
||||
IDs are stable and prefix-typed, so you can always tell what an ID refers to.
|
||||
Most commands take a workspace ID positionally and **infer it when the machine
|
||||
has exactly one workspace**. Pass it explicitly once a second workspace exists.
|
||||
IDs are prefix-typed, so you can always tell what an ID refers to. Commands that
|
||||
take a workspace ID infer it when exactly one workspace exists.
|
||||
|
||||
## Preflight
|
||||
## Getting oriented
|
||||
|
||||
```bash
|
||||
yaak --version || npm install -g @yaakapp/cli
|
||||
yaak workspace list
|
||||
```
|
||||
|
||||
`workspace list` prints `wk_… - Name` per line, or `No workspaces found`. Pick
|
||||
the workspace that matches the user's project before mutating anything; create
|
||||
one only when nothing fits.
|
||||
Pick the workspace matching the user's project before changing anything, and
|
||||
create one only when nothing fits. If a documented command is unrecognized, the
|
||||
CLI is older than this skill: update it with `npm install -g @yaakapp/cli@latest`
|
||||
and re-run `yaak agent install`, then tell the user to restart their coding tool.
|
||||
**When the CLI and this skill disagree, the CLI is right.**
|
||||
|
||||
If a subcommand documented here is not recognized, the CLI is older than this
|
||||
skill. Update it, then refresh the skill so the two stay in lockstep:
|
||||
## Core workflows
|
||||
|
||||
```bash
|
||||
npm install -g @yaakapp/cli@latest && yaak agent install
|
||||
```
|
||||
**Start from a spec when one exists.** `yaak import <file>` auto-detects OpenAPI,
|
||||
Swagger, Postman, Insomnia, cURL, and Yaak exports, and beats authoring requests
|
||||
by hand every time.
|
||||
|
||||
Mention to the user that they need to restart their coding tool for the
|
||||
refreshed skill to load; the current session keeps using the old copy.
|
||||
**Make the host swappable.** Put the base URL in a base-environment variable,
|
||||
reference it as `${[ base_url ]}`, then add a sub-environment per deployment
|
||||
target. Now `yaak -e ev_staging send <wk_id>` runs everything against staging.
|
||||
|
||||
## Command map
|
||||
|
||||
| Goal | Command |
|
||||
|---|---|
|
||||
| List | `yaak {workspace,folder,request,environment,cookie-jar} list` |
|
||||
| Inspect one | `yaak {workspace,folder,request,environment} show <id>` |
|
||||
| Create | `yaak {workspace,folder,request,environment} create` |
|
||||
| Update | `yaak {workspace,folder,request,environment} update --json '{"id":"…",…}'` |
|
||||
| Delete | `yaak … delete <id> --yes` |
|
||||
| Inspect the model | `yaak request schema http --pretty`, `yaak workspace schema`, `yaak environment schema` |
|
||||
| Send one request | `yaak request send <rq_id>` |
|
||||
| Send a folder or whole workspace | `yaak send <fl_id\|wk_id>` |
|
||||
| Import an existing API | `yaak import <file>` |
|
||||
| Export | `yaak export <file> [workspace_id…]` |
|
||||
|
||||
Global flags go anywhere but are clearest before the subcommand:
|
||||
`-e/--environment <ev_id>`, `--cookie-jar <cj_id>`, `-v/--verbose`,
|
||||
`--data-dir <path>` (point at an isolated database, useful for scratch work).
|
||||
|
||||
## Creating and updating
|
||||
|
||||
Simple requests take flags:
|
||||
|
||||
```bash
|
||||
yaak request create wk_abc123 --name "List Pets" --method GET --url "https://api.example.com/pets"
|
||||
```
|
||||
|
||||
Anything richer than name/method/URL takes a JSON payload, either positionally
|
||||
or via `--json`. **Read the schema before writing a payload you are unsure
|
||||
of** — it is generated from the real model and includes the plugin-provided
|
||||
authentication variants:
|
||||
|
||||
```bash
|
||||
yaak request schema http --pretty
|
||||
```
|
||||
|
||||
That schema is also the authoritative list of **authentication strategies**,
|
||||
including ones contributed by plugins. Each appears as a named variant under
|
||||
`authentication`, with its own fields, required list, and enums, so there is
|
||||
never a reason to guess auth config:
|
||||
|
||||
```bash
|
||||
# every installed strategy, with the value to use for authenticationType
|
||||
yaak request schema http | jq -r '.properties.authentication.oneOf[]
|
||||
| select(.title) | "\(.title): \(.description)"'
|
||||
|
||||
# the full shape of one of them
|
||||
yaak request schema http | jq '.properties.authentication.oneOf[]
|
||||
| select(.title == "OAuth 2.0")'
|
||||
```
|
||||
|
||||
The first prints lines like `OAuth 2.0: Authentication values for strategy
|
||||
'oauth2'`. **The title is a display label, not the value** — `authenticationType`
|
||||
takes the quoted strategy name, so NTLM Auth is `windows` and AWS Signature is
|
||||
`awsv4`.
|
||||
|
||||
The second prints the fields. OAuth 2.0 has fifteen of them plus an enum of valid
|
||||
`grantType` values (`authorization_code`, `implicit`, `password`,
|
||||
`client_credentials`), which is exactly the sort of thing that comes out wrong
|
||||
when guessed. The command loads plugins, so it reflects what is actually
|
||||
installed rather than a fixed list, and it returns in well under a second.
|
||||
|
||||
Rules that are easy to get wrong:
|
||||
|
||||
- **Setting `bodyType` does not add a `Content-Type` header.** The app adds one
|
||||
when you pick a body type in the UI, but creating a request from the CLI skips
|
||||
that step, and the body goes out untyped. Add the header yourself, using the
|
||||
same value as `bodyType` (with `other` → `text/plain` and `graphql` →
|
||||
`application/json`). Multipart is the exception: leave it alone, the sender
|
||||
supplies the boundary.
|
||||
- **Path parameters must keep the leading colon.** For `/pets/:petId`, the
|
||||
`urlParameters` entry is named `:petId`, not `petId`. Get it wrong and the
|
||||
placeholder stays literal in the path while the value is appended to the query
|
||||
string, which usually 404s with no error.
|
||||
- **Create** payloads must omit `id` (or set it to `""`).
|
||||
- **Update** payloads must include `id`, and are applied as a JSON merge patch:
|
||||
keys you omit are left alone, and a key set to `null` is deleted. There is no
|
||||
need to send the whole object.
|
||||
- Flags and JSON cannot be combined on the same command.
|
||||
- `request create` and `request list` are HTTP-only. gRPC and WebSocket requests
|
||||
exist in the model and can be sent from the app, but the CLI cannot yet create
|
||||
or send them.
|
||||
|
||||
The first two fail silently, so verify a new request with `yaak -v request send
|
||||
<id>` and check the `> ` lines actually show the path and headers you intended.
|
||||
|
||||
```bash
|
||||
yaak request create wk_abc123 --json '{
|
||||
"name": "Create Pet", "method": "POST", "url": "${[ base_url ]}/pets",
|
||||
"bodyType": "application/json",
|
||||
"body": {"text": "{\"name\":\"Rex\"}"},
|
||||
"headers": [{"name": "Content-Type", "value": "application/json", "enabled": true}]
|
||||
}'
|
||||
```
|
||||
|
||||
See [requests.md](references/requests.md) for bodies, headers, authentication,
|
||||
path parameters, and folder inheritance.
|
||||
|
||||
## Template variables
|
||||
|
||||
Yaak's template syntax is `${[ … ]}`, **not** `{{ … }}`. It works in URLs,
|
||||
headers, bodies, and auth fields:
|
||||
**Chain instead of shell-plumbing.** A request can read another request's
|
||||
response directly, and Yaak sends the dependency first if it needs to:
|
||||
|
||||
```
|
||||
${[ base_url ]}/pets/${[ pet_id ]}
|
||||
${[ response.body.path(request='rq_abc123', path='$.token') ]}
|
||||
${[ response.body.path(request='rq_login', path='$.token') ]}
|
||||
```
|
||||
|
||||
Referencing a variable that no active environment defines is a hard error and
|
||||
the request is not sent, so an unresolved variable can never silently reach the
|
||||
network. See [environments.md](references/environments.md) for variable scoping
|
||||
and [chaining.md](references/chaining.md) for pulling values out of earlier
|
||||
responses.
|
||||
Run `yaak template-function show response.body.path` for its arguments,
|
||||
including how to control when the upstream request re-sends. Chain when a
|
||||
request genuinely depends on another's response; to merely run requests in
|
||||
order, `yaak send <fl_id>` already does that.
|
||||
|
||||
## Sending
|
||||
**Run a set.** `yaak send` accepts a folder or workspace ID, with `--fail-fast`
|
||||
and `--parallel`. Workspace and request IDs survive an export/import, so a
|
||||
committed `yaak export` plus `--data-dir ./.yaak` gives a runnable suite in CI.
|
||||
|
||||
```bash
|
||||
yaak request send rq_abc123 # body only, on stdout
|
||||
yaak -e ev_staging request send rq_abc123 # against a sub-environment
|
||||
yaak -v request send rq_abc123 # request/response metadata too
|
||||
yaak send fl_abc123 --fail-fast # every request in a folder
|
||||
yaak send wk_abc123 --parallel # every request in a workspace
|
||||
```
|
||||
## Reading results
|
||||
|
||||
**Reading the result.** A plain send writes only the response body to stdout,
|
||||
with no trailing newline. Like `curl`, the exit code reflects whether the
|
||||
request completed, not the HTTP status — a 404 or 500 exits 0. Use `-v` when the
|
||||
status matters:
|
||||
A plain send writes only the response body to stdout. Add `-v` for the request
|
||||
and response metadata, where lines are prefixed `*`, `>`, and `<`:
|
||||
|
||||
```bash
|
||||
yaak -v request send rq_abc123 2>&1 | grep '^< HTTP'
|
||||
```
|
||||
|
||||
Under `-v`, connection/request/response lines (`*`, `>`, `<`) and the body all
|
||||
go to stdout, with the body following the last `<` header line. Grep for the
|
||||
prefixes you need rather than assuming a clean split.
|
||||
|
||||
Exit code 1 means the send itself failed: an unresolved template variable, an
|
||||
unreachable host, a TLS failure. For folders and workspaces the last line is
|
||||
`Send summary: N succeeded, M failed`, per-request errors follow on stderr, and
|
||||
the exit code is 1 if any request failed.
|
||||
|
||||
## Running a suite in CI
|
||||
|
||||
Workspace and request IDs survive an export/import, so a committed export gives
|
||||
a stable, runnable suite on a machine that has never seen the app:
|
||||
|
||||
```bash
|
||||
npm install -g @yaakapp/cli
|
||||
yaak --data-dir ./.yaak import ./api-export.json
|
||||
yaak --data-dir ./.yaak -e ev_ci send wk_abc123 --fail-fast
|
||||
```
|
||||
|
||||
`--data-dir` keeps the run isolated from any real Yaak install, and the IDs in
|
||||
the export are the same ones you used locally. Produce the export with
|
||||
`yaak export ./api-export.json wk_abc123`, adding
|
||||
`--include-private-environments` only if the suite needs values you are willing
|
||||
to commit — otherwise keep secrets in a CI-only environment and inject them.
|
||||
|
||||
**The caveat that matters here:** a failing *assertion* is not a concept Yaak
|
||||
has, and HTTP error statuses do not fail the run. A workspace of requests that
|
||||
all return 500 exits 0. The exit code catches unreachable hosts, TLS failures,
|
||||
and unresolved variables only. To gate CI on status codes, run with `-v` and
|
||||
check the `< HTTP` lines yourself. Say this plainly rather than implying a green
|
||||
run means the API is healthy.
|
||||
|
||||
## Importing
|
||||
|
||||
`yaak import` auto-detects OpenAPI/Swagger, Postman, Insomnia, cURL, and Yaak
|
||||
exports, and creates a new workspace by default:
|
||||
|
||||
```bash
|
||||
yaak import ./openapi.yaml
|
||||
yaak import ./collection.json --workspace-id wk_abc123 # merge into an existing one
|
||||
```
|
||||
|
||||
This is almost always faster than authoring requests by hand when a spec exists.
|
||||
See [import-export.md](references/import-export.md).
|
||||
|
||||
## Routing
|
||||
|
||||
| Task | Reference |
|
||||
|---|---|
|
||||
| Request bodies, headers, auth, path/query params, folder inheritance | [requests.md](references/requests.md) |
|
||||
| Environment hierarchy, variables, per-environment runs | [environments.md](references/environments.md) |
|
||||
| Using one response inside the next request; template functions | [chaining.md](references/chaining.md) |
|
||||
| OpenAPI/Postman/Insomnia/cURL import, exporting workspaces | [import-export.md](references/import-export.md) |
|
||||
Exit code 1 means the send did not complete: an unresolved template variable, an
|
||||
unreachable host, a TLS failure. **HTTP error statuses are not failures.** Like
|
||||
`curl`, a 404 or 500 exits 0, and a folder of requests that all return 500
|
||||
reports success. Never tell the user an API is healthy based on a clean exit;
|
||||
check the status yourself with `-v`.
|
||||
|
||||
## Execution rules
|
||||
|
||||
1. Resolve the workspace before mutating. Do not create a second workspace when
|
||||
an existing one matches the user's project.
|
||||
2. Read the schema before writing a non-trivial JSON payload. Do not guess field
|
||||
names.
|
||||
3. Prefer `update` merge patches over re-sending whole objects.
|
||||
4. Deletes require `--yes` in a non-interactive shell; otherwise they block on a
|
||||
prompt. Confirm intent with the user before deleting anything.
|
||||
1. Resolve the workspace before mutating, and prefer an existing one.
|
||||
2. Read the schema rather than guessing field names, auth fields, or body shapes.
|
||||
3. `update` takes a JSON merge patch keyed by `id`: send only what changes, and
|
||||
note that arrays are replaced wholesale, not merged.
|
||||
4. Deletes need `--yes` in a non-interactive shell. Confirm with the user first.
|
||||
5. Never write a real secret into an environment variable on the user's behalf.
|
||||
Reference one (`${[ api_token ]}`) and let the user fill in the value — see
|
||||
[environments.md](references/environments.md).
|
||||
6. After creating requests, verify by sending one, and report the actual HTTP
|
||||
status from `-v` rather than inferring success from exit code 0.
|
||||
7. Requests you create are permanent user data in their app, not scratch. Name
|
||||
them the way the user would, and clean up anything you created purely to test.
|
||||
Reference one and let them fill in the value.
|
||||
6. Verify what you built by sending it, and report the real HTTP status.
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
# Chaining requests
|
||||
|
||||
The usual way to feed one response into the next request is a shell pipeline:
|
||||
send, pipe through `jq`, stash in a variable, interpolate into the next command.
|
||||
Yaak does not need that. A request can reference another request's response
|
||||
directly, and Yaak resolves the dependency at send time — including sending the
|
||||
upstream request first if it has to.
|
||||
|
||||
This is the highest-leverage thing the CLI offers, because the chain is stored
|
||||
in the workspace. The user can re-run it from the app, and it keeps working
|
||||
after the shell session is gone.
|
||||
|
||||
## Reading a value out of another response
|
||||
|
||||
```
|
||||
${[ response.body.path(request='rq_login', path='$.token') ]}
|
||||
```
|
||||
|
||||
`request` is the upstream request's ID. `path` is JSONPath for JSON responses
|
||||
and XPath for XML. So a login-then-call-the-API pair is two requests and no glue:
|
||||
|
||||
```bash
|
||||
yaak request create wk_abc123 --json '{
|
||||
"name": "Login",
|
||||
"method": "POST",
|
||||
"url": "${[ base_url ]}/auth/login",
|
||||
"bodyType": "application/json",
|
||||
"body": {"text": "{\"user\":\"demo\",\"pass\":\"${[ password ]}\"}"},
|
||||
"headers": [{"name": "Content-Type", "value": "application/json", "enabled": true}]
|
||||
}'
|
||||
# -> Created request: rq_login
|
||||
|
||||
yaak request create wk_abc123 --json '{
|
||||
"name": "List Orders",
|
||||
"method": "GET",
|
||||
"url": "${[ base_url ]}/orders",
|
||||
"authenticationType": "bearer",
|
||||
"authentication": {"token": "${[ response.body.path(request='rq_login', path='$.token') ]}"}
|
||||
}'
|
||||
```
|
||||
|
||||
Sending "List Orders" now sends "Login" first when it needs to, extracts
|
||||
`$.token`, and puts it in the `Authorization` header.
|
||||
|
||||
Note the quoting: template function arguments use **single quotes**, so inside a
|
||||
single-quoted shell string write the payload to a file, or escape as above, or
|
||||
switch the outer shell quoting to double quotes and escape the inner JSON.
|
||||
Writing the JSON payload to a file and using `--json "$(cat payload.json)"` is
|
||||
the least error-prone for anything complex.
|
||||
|
||||
## Controlling when the upstream request re-sends
|
||||
|
||||
The `behavior` argument decides whether the dependency is actually sent:
|
||||
|
||||
| `behavior` | Meaning |
|
||||
|---|---|
|
||||
| `smart` (default) | Send only if there is no stored response yet |
|
||||
| `always` | Send every time |
|
||||
| `ttl` | Send if the newest response is older than `ttl` seconds (`0` never expires) |
|
||||
|
||||
```
|
||||
${[ response.body.path(request='rq_login', path='$.token', behavior='ttl', ttl='300') ]}
|
||||
```
|
||||
|
||||
`smart` is right for a token you fetch once. `ttl` matches a real token lifetime
|
||||
and is usually the best choice for auth. `always` is for values that must be
|
||||
fresh on every call, like a nonce.
|
||||
|
||||
## Other response accessors
|
||||
|
||||
```
|
||||
${[ response.header(request='rq_login', header='X-Request-Id') ]}
|
||||
${[ response.body.raw(request='rq_login') ]}
|
||||
```
|
||||
|
||||
`response.body.path` also accepts `behavior`/`ttl`, and has an alias of plain
|
||||
`response`.
|
||||
|
||||
## Other useful template functions
|
||||
|
||||
These come from bundled plugins and work anywhere a value is rendered:
|
||||
|
||||
| Function | Use |
|
||||
|---|---|
|
||||
| `uuid.v4()`, also `v1`, `v3`, `v5`, `v6`, `v7` | Idempotency keys, unique record names |
|
||||
| `timestamp.unix()`, `timestamp.unixMillis()`, `timestamp.iso8601()` | Timestamps in bodies or signatures |
|
||||
| `timestamp.format(...)`, `timestamp.offset(...)` | Formatted or relative times |
|
||||
| `random.range(min='1', max='100', decimals='0')` | Sample data |
|
||||
| `hash.sha256(input='…', encoding='hex')` | Digests — also `md5`, `sha1`, `sha512` |
|
||||
| `hmac.sha256(input='…', key='…', encoding='hex')` | Signed request signatures |
|
||||
| `base64.encode(input='…')`, `base64.decode(...)` | Encoded values |
|
||||
| `url.encode(input='…')`, `url.decode(...)` | Escaping values for URLs |
|
||||
| `fs.readFile(path='/abs/path', trim='true')` | Pull a value from a file on disk |
|
||||
| `cookie.value(name='session')` | Read a cookie from the jar |
|
||||
| `1password.item(...)` | Fetch a secret from 1Password rather than storing it |
|
||||
|
||||
Note the shapes that are easy to misremember: the encoders are `base64.encode`
|
||||
and `url.encode`, not `encode.base64`; the random function is `random.range`,
|
||||
not `random.number`; and there is no `timestamp.now`. Argument names vary per
|
||||
function, and a wrong name renders as an error rather than an empty string, so
|
||||
check the function in the app's template editor when unsure.
|
||||
|
||||
## When to chain and when not to
|
||||
|
||||
Chain when the dependency is part of the API's real shape: log in, then call;
|
||||
create a resource, then fetch it by the returned ID. The workspace becomes a
|
||||
runnable description of the API, which is the point.
|
||||
|
||||
Do not chain to smuggle in shell logic. If a value needs a conditional, a
|
||||
computation, or a retry, do that in the shell and set an environment variable.
|
||||
And do not build a long chain just to run a group of requests — `yaak send
|
||||
<fl_id>` already sends every request in a folder sequentially, with
|
||||
`--fail-fast` to stop at the first failure and `--parallel` when order does not
|
||||
matter. Reach for a chain when a request genuinely *depends* on another's
|
||||
response, not merely when it should run after it.
|
||||
@@ -1,122 +0,0 @@
|
||||
# Environments and variables
|
||||
|
||||
## The hierarchy
|
||||
|
||||
Every workspace gets a base environment named **Global Variables** for free. Its
|
||||
`parentModel` is `workspace`, and its variables apply to every send regardless of
|
||||
which environment is selected.
|
||||
|
||||
Sub-environments (`parentModel: "environment"`) sit under the base and are chosen
|
||||
per send with `-e`. A sub-environment variable overrides a base variable of the
|
||||
same name; names it does not define fall through to the base.
|
||||
|
||||
```bash
|
||||
yaak environment list wk_abc123
|
||||
# ev_staging - Staging (environment)
|
||||
# ev_production - Production (environment)
|
||||
# ev_base - Global Variables (workspace)
|
||||
```
|
||||
|
||||
The trailing parenthetical is `parentModel`, which is how you tell the base
|
||||
environment from the rest.
|
||||
|
||||
## Setting variables
|
||||
|
||||
Variables are an array on the environment, so an update replaces the whole list.
|
||||
Read first, then write the full array back:
|
||||
|
||||
```bash
|
||||
yaak environment show ev_base
|
||||
yaak environment update --json '{
|
||||
"id": "ev_base",
|
||||
"variables": [
|
||||
{"name": "base_url", "value": "https://api.example.com", "enabled": true},
|
||||
{"name": "api_version", "value": "v1", "enabled": true}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
Create a sub-environment with its variables in one step:
|
||||
|
||||
```bash
|
||||
yaak environment create wk_abc123 --json '{
|
||||
"name": "Staging",
|
||||
"parentModel": "environment",
|
||||
"variables": [{"name": "base_url", "value": "https://staging.example.com", "enabled": true}]
|
||||
}'
|
||||
```
|
||||
|
||||
`enabled: false` keeps a variable defined but inert, which is how the app models
|
||||
a commented-out value.
|
||||
|
||||
## Using them
|
||||
|
||||
`${[ name ]}` resolves anywhere a value is rendered — URL, headers, body, and
|
||||
authentication fields:
|
||||
|
||||
```bash
|
||||
yaak request create wk_abc123 --json '{
|
||||
"name": "List Pets",
|
||||
"method": "GET",
|
||||
"url": "${[ base_url ]}/${[ api_version ]}/pets",
|
||||
"authenticationType": "bearer",
|
||||
"authentication": {"token": "${[ api_token ]}"}
|
||||
}'
|
||||
```
|
||||
|
||||
Then run the same request against different targets:
|
||||
|
||||
```bash
|
||||
yaak request send rq_abc123 # base environment only
|
||||
yaak -e ev_staging request send rq_abc123 # staging overrides base_url
|
||||
yaak -e ev_production send fl_smoke_tests # whole folder against production
|
||||
```
|
||||
|
||||
`-e` is global, so it applies to `send`, `request send`, folder sends, and
|
||||
workspace sends alike.
|
||||
|
||||
## Unresolved variables fail loudly
|
||||
|
||||
Referencing a name no active environment defines aborts before anything is sent:
|
||||
|
||||
```
|
||||
Error: Failed to render request templates: Render Error: Variable "api_token" is not defined in active environment
|
||||
```
|
||||
|
||||
Exit code 1. This is a feature worth relying on — a typo in a variable name can
|
||||
never quietly send a request to `https:///pets`. When a send fails this way, the
|
||||
fix is either the variable name in the request or the `-e` environment, not a
|
||||
retry.
|
||||
|
||||
## Secrets
|
||||
|
||||
Do not write real credentials into environment variables on the user's behalf.
|
||||
Create the reference and let the user supply the value:
|
||||
|
||||
```bash
|
||||
yaak environment update --json '{"id":"ev_base","variables":[{"name":"api_token","value":"","enabled":true}]}'
|
||||
```
|
||||
|
||||
Then tell the user which variable to fill in, and in which environment.
|
||||
|
||||
Environments have a `public` flag that mirrors the app's Sharable/Private toggle.
|
||||
`public: false` (the default) marks the environment Private, which keeps it out
|
||||
of `yaak export` unless `--include-private-environments` is passed. Secrets
|
||||
belong in a private environment; values safe to commit or share belong in a
|
||||
sharable one.
|
||||
|
||||
## Cookies
|
||||
|
||||
Cookie jars are per-workspace and require no setup — the oldest jar is used
|
||||
automatically. Sending cookies is off by default per request; enable it with the
|
||||
inherited setting, on the folder if it should apply to a whole group:
|
||||
|
||||
```json
|
||||
"settingSendCookies": {"enabled": true, "value": true},
|
||||
"settingStoreCookies": {"enabled": true, "value": true}
|
||||
```
|
||||
|
||||
With both on, a login request stores its `Set-Cookie` and later requests in the
|
||||
same jar send it back, which is often simpler than threading a token by hand.
|
||||
Use `--cookie-jar cj_abc123` to select a non-default jar, and
|
||||
`yaak cookie-jar list <wk_id>` to see the jars and their cookie counts.
|
||||
@@ -1,71 +0,0 @@
|
||||
# Importing and exporting
|
||||
|
||||
## Import
|
||||
|
||||
```bash
|
||||
yaak import ./openapi.yaml
|
||||
```
|
||||
|
||||
One command, one positional file path. The format is auto-detected — OpenAPI 3,
|
||||
Swagger 2, Postman collections, Postman environments, Insomnia exports, cURL
|
||||
commands, and Yaak's own export format are all supported by bundled importer
|
||||
plugins. Output is a one-line summary:
|
||||
|
||||
```
|
||||
Imported 1 workspace, 1 environment, 1 folder, 3 HTTP requests
|
||||
```
|
||||
|
||||
By default this creates a **new workspace**. To merge into one that already
|
||||
exists:
|
||||
|
||||
```bash
|
||||
yaak import ./collection.json --workspace-id wk_abc123
|
||||
```
|
||||
|
||||
Not every importer honours `--workspace-id`; the flag applies where the importer
|
||||
supports it, and otherwise a new workspace is still created. Run
|
||||
`yaak workspace list` afterwards to see what you actually got.
|
||||
|
||||
**Reach for this first.** When the user has an OpenAPI spec, a Postman
|
||||
collection, or even a directory of `curl` commands in a README, importing beats
|
||||
authoring requests by hand — it is one command, it preserves names and grouping,
|
||||
and it will not typo a URL. Author requests by hand when there is no spec, or
|
||||
when the user wants a small hand-picked set rather than every endpoint.
|
||||
|
||||
A cURL import is a fast way to turn something the user already has into a saved
|
||||
request:
|
||||
|
||||
```bash
|
||||
echo "curl -X POST https://api.example.com/pets -H 'Content-Type: application/json' -d '{\"name\":\"Rex\"}'" > /tmp/req.txt
|
||||
yaak import /tmp/req.txt
|
||||
```
|
||||
|
||||
## After importing
|
||||
|
||||
An imported spec gives you requests pointing at whatever `servers` the spec
|
||||
declared. The usual follow-up is to make the host swappable:
|
||||
|
||||
1. `yaak request list <wk_id>` to see what landed.
|
||||
2. Put the host in a base environment variable (`base_url`).
|
||||
3. Update the imported requests to use `${[ base_url ]}`, then add a
|
||||
sub-environment per deployment target.
|
||||
|
||||
See [environments.md](environments.md). At that point `yaak -e ev_staging send
|
||||
<wk_id>` runs the whole imported API against staging.
|
||||
|
||||
## Export
|
||||
|
||||
```bash
|
||||
yaak export ./backup.json # the only workspace, when there is one
|
||||
yaak export ./backup.json wk_abc123 # a specific workspace
|
||||
yaak export ./backup.json wk_abc wk_def # several
|
||||
yaak export ./backup.json --all # everything
|
||||
```
|
||||
|
||||
Private environments are **excluded** unless you pass
|
||||
`--include-private-environments`. That default exists so an export can be
|
||||
committed to a repository without leaking credentials — do not add the flag
|
||||
just to make an export look complete. Add it only when the user explicitly wants
|
||||
a full backup, and say so when you do.
|
||||
|
||||
The output is Yaak's own format, so `yaak import ./backup.json` round-trips it.
|
||||
@@ -1,231 +0,0 @@
|
||||
# Authoring requests
|
||||
|
||||
Everything here is a field on the HTTP request model. Run
|
||||
`yaak request schema http --pretty` to see the full, current schema, including
|
||||
the authentication variants contributed by installed plugins.
|
||||
|
||||
## Two ways to create
|
||||
|
||||
Name, method, and URL have flags. Everything else needs JSON:
|
||||
|
||||
```bash
|
||||
yaak request create wk_abc123 --name "List Pets" --method GET --url "https://api.example.com/pets"
|
||||
```
|
||||
|
||||
```bash
|
||||
yaak request create wk_abc123 --json '{
|
||||
"name": "Create Pet",
|
||||
"method": "POST",
|
||||
"url": "https://api.example.com/pets",
|
||||
"bodyType": "application/json",
|
||||
"body": {"text": "{\"name\":\"Rex\",\"species\":\"dog\"}"},
|
||||
"headers": [{"name": "Content-Type", "value": "application/json", "enabled": true}]
|
||||
}'
|
||||
```
|
||||
|
||||
Flags and JSON cannot be mixed on the same command. The workspace ID may be
|
||||
omitted when only one workspace exists, and may also be carried inside the
|
||||
payload as `workspaceId`.
|
||||
|
||||
## Bodies
|
||||
|
||||
`bodyType` decides how `body` is encoded onto the wire, and `body` is a
|
||||
free-form object whose shape depends on that type. For text-ish types the
|
||||
content lives in `body.text` as a **string**, so a JSON payload is
|
||||
double-encoded: a JSON string containing JSON.
|
||||
|
||||
| `bodyType` | `body` |
|
||||
|---|---|
|
||||
| `application/json` | `{"text": "{\"key\":\"value\"}"}` |
|
||||
| `text/xml` | `{"text": "<root/>"}` |
|
||||
| `other` | `{"text": "…"}` |
|
||||
| `graphql` | `{"query": "{ pets { id } }", "variables": "{\"n\":1}", "operationName": ""}` |
|
||||
| `application/x-www-form-urlencoded` | `{"form": [{"name":"a","value":"1","enabled":true}]}` |
|
||||
| `multipart/form-data` | `{"form": [{"name":"file","file":"/abs/path","enabled":true}]}` |
|
||||
| `binary` | `{"filePath": "/abs/path"}` |
|
||||
| `null` (omitted) | no body |
|
||||
|
||||
For `graphql`, note that `variables` is a **string** of JSON, not an object, and
|
||||
that a GraphQL request sent with method `GET` moves query/variables/operationName
|
||||
into the query string and sends no body at all.
|
||||
|
||||
For `multipart/form-data`, each entry is either a text field (`value`) or a file
|
||||
(`file`, an absolute path), and may carry its own `contentType`.
|
||||
|
||||
**Add the `Content-Type` header yourself.** In the desktop app, choosing a body
|
||||
type also writes a matching `Content-Type` into the request's headers, so it is
|
||||
stored on the request rather than inferred at send time. Creating a request from
|
||||
the CLI skips that step: `bodyType` alone controls how the body is *encoded*, and
|
||||
nothing adds the header. A JSON body with no `Content-Type` goes out as untyped
|
||||
bytes, which many APIs answer with 400 or 415.
|
||||
|
||||
```json
|
||||
"bodyType": "application/json",
|
||||
"body": {"text": "{\"name\":\"Rex\"}"},
|
||||
"headers": [{"name": "Content-Type", "value": "application/json", "enabled": true}]
|
||||
```
|
||||
|
||||
Use the same value as `bodyType`, with two exceptions the app also makes: `other`
|
||||
pairs with `text/plain`, and `graphql` pairs with `application/json`. Multipart is
|
||||
the one case to leave alone — the sender replaces that header with one carrying
|
||||
the generated boundary.
|
||||
|
||||
Requests created this way end up identical to app-created ones, which matters
|
||||
because the user will open them in the app afterwards.
|
||||
|
||||
## Headers
|
||||
|
||||
```json
|
||||
"headers": [
|
||||
{"name": "Accept", "value": "application/json", "enabled": true},
|
||||
{"name": "X-Debug", "value": "1", "enabled": false}
|
||||
]
|
||||
```
|
||||
|
||||
`enabled: false` keeps a header in the app for the user to toggle without
|
||||
sending it. Values accept template variables.
|
||||
|
||||
## URL parameters
|
||||
|
||||
One array covers both query string entries and path placeholders. A parameter
|
||||
fills a path placeholder only when its **name starts with a colon** and matches
|
||||
the placeholder in the URL. Everything else becomes a query string entry:
|
||||
|
||||
```json
|
||||
"url": "https://api.example.com/pets/:petId/visits",
|
||||
"urlParameters": [
|
||||
{"name": ":petId", "value": "42", "enabled": true},
|
||||
{"name": "limit", "value": "10", "enabled": true}
|
||||
]
|
||||
```
|
||||
|
||||
That sends `https://api.example.com/pets/42/visits?limit=10` — `:petId` is
|
||||
substituted into the path and dropped from the query string, `limit` is not.
|
||||
|
||||
This is the single easiest thing to get wrong here, and it fails **silently**.
|
||||
Naming the parameter `petId` instead of `:petId` leaves `/pets/:petId/visits` in
|
||||
the path as literal text and appends `?petId=42`, which most servers answer with
|
||||
a 404. Always include the colon, and confirm with `yaak -v request send <id>`
|
||||
that the `> GET …` line shows a substituted path.
|
||||
|
||||
## Authentication
|
||||
|
||||
`authenticationType` names a strategy and `authentication` holds its values. The
|
||||
strategy names are not always what you would guess — the built-ins are `basic`,
|
||||
`bearer`, `apikey`, `jwt`, `oauth1`, `oauth2`, `awsv4` (not "aws"), and
|
||||
`windows` (not "ntlm"). Installed plugins can add more.
|
||||
|
||||
```json
|
||||
"authenticationType": "bearer",
|
||||
"authentication": {"token": "${[ api_token ]}"}
|
||||
```
|
||||
|
||||
```json
|
||||
"authenticationType": "basic",
|
||||
"authentication": {"username": "admin", "password": "${[ admin_password ]}"}
|
||||
```
|
||||
|
||||
```json
|
||||
"authenticationType": "apikey",
|
||||
"authentication": {"location": "header", "key": "X-Api-Key", "value": "${[ api_key ]}"}
|
||||
```
|
||||
|
||||
OAuth 2.0 is the one to look up rather than attempt from memory. It has fifteen
|
||||
fields, six of them required, and `grantType` is an enum:
|
||||
|
||||
```json
|
||||
"authenticationType": "oauth2",
|
||||
"authentication": {
|
||||
"grantType": "client_credentials",
|
||||
"clientId": "${[ client_id ]}",
|
||||
"clientSecret": "${[ client_secret ]}",
|
||||
"accessTokenUrl": "https://auth.example.com/oauth/token",
|
||||
"scope": "read:pets",
|
||||
"credentials": "body",
|
||||
"tokenName": "access_token",
|
||||
"headerName": "Authorization",
|
||||
"usePkce": false,
|
||||
"useExternalBrowser": false
|
||||
}
|
||||
```
|
||||
|
||||
`grantType` accepts `authorization_code`, `implicit`, `password`, or
|
||||
`client_credentials`, and which other fields matter depends on which you pick:
|
||||
`authorization_code` also wants `authorizationUrl` and `redirectUri`, while
|
||||
`client_credentials` does not.
|
||||
|
||||
The exact fields for every strategy, and which are required, come from the
|
||||
schema, which enumerates each installed strategy as a named variant under
|
||||
`authentication`:
|
||||
|
||||
```bash
|
||||
# display name plus the value to use for authenticationType
|
||||
yaak request schema http | jq -r '.properties.authentication.oneOf[]
|
||||
| select(.title) | "\(.title): \(.description)"'
|
||||
|
||||
# the full shape of one strategy
|
||||
yaak request schema http | jq '.properties.authentication.oneOf[]
|
||||
| select(.title == "OAuth 2.0")'
|
||||
```
|
||||
|
||||
Because the list is built by loading plugins, it covers plugin-contributed
|
||||
strategies too, not just the built-ins. Read it rather than guessing.
|
||||
|
||||
Set `authenticationType` to `null` to send no auth and stop inheriting from the
|
||||
parent folder.
|
||||
|
||||
## Folders and inheritance
|
||||
|
||||
Folders are containers *and* a place to put shared configuration. Headers and
|
||||
authentication set on a folder apply to every request inside it, so the common
|
||||
pattern is one folder per API surface holding the auth:
|
||||
|
||||
```bash
|
||||
yaak folder create wk_abc123 --name "Admin API"
|
||||
yaak folder update --json '{
|
||||
"id": "fl_abc123",
|
||||
"authenticationType": "bearer",
|
||||
"authentication": {"token": "${[ admin_token ]}"},
|
||||
"headers": [{"name": "X-Api-Version", "value": "2024-01-01", "enabled": true}]
|
||||
}'
|
||||
yaak request create wk_abc123 --json '{"name":"List Users","method":"GET","url":"${[ base_url ]}/users","folderId":"fl_abc123"}'
|
||||
```
|
||||
|
||||
The request above sends both the folder's bearer token and its version header
|
||||
without repeating either. A request that sets its own `authenticationType`
|
||||
overrides the folder's.
|
||||
|
||||
Nest folders by setting a folder's `folderId`. `yaak send <fl_id>` sends every
|
||||
request in the folder recursively.
|
||||
|
||||
## Per-request settings
|
||||
|
||||
Each `setting*` field is an inherited toggle shaped
|
||||
`{"enabled": bool, "value": …}`, where `enabled` means "override the inherited
|
||||
value" rather than "turn the feature on":
|
||||
|
||||
```json
|
||||
"settingFollowRedirects": {"enabled": true, "value": false},
|
||||
"settingRequestTimeout": {"enabled": true, "value": 5000}
|
||||
```
|
||||
|
||||
Available: `settingFollowRedirects`, `settingRequestTimeout` (ms, `0` for none),
|
||||
`settingValidateCertificates`, `settingSendCookies`, `settingStoreCookies`.
|
||||
|
||||
## Updating
|
||||
|
||||
Updates are JSON merge patches keyed by `id`. Send only what changes:
|
||||
|
||||
```bash
|
||||
yaak request update --json '{"id":"rq_abc123","method":"PATCH"}'
|
||||
```
|
||||
|
||||
Arrays are replaced wholesale, not merged — to add one header, read the current
|
||||
list with `yaak request show rq_abc123` and write the full new array back.
|
||||
Setting a key to `null` removes it.
|
||||
|
||||
## Ordering
|
||||
|
||||
`sortPriority` (a float) controls display order in the app sidebar. Leave it at
|
||||
`0` unless the user cares; requests created with the same priority fall back to
|
||||
creation order.
|
||||
@@ -86,6 +86,36 @@ pub enum Commands {
|
||||
|
||||
/// Environment commands
|
||||
Environment(EnvironmentArgs),
|
||||
|
||||
/// Template function commands
|
||||
#[command(alias = "func")]
|
||||
TemplateFunction(TemplateFunctionArgs),
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct TemplateFunctionArgs {
|
||||
#[command(subcommand)]
|
||||
pub command: TemplateFunctionCommands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum TemplateFunctionCommands {
|
||||
/// List template functions provided by installed plugins
|
||||
List {
|
||||
/// Only show functions whose name contains this text
|
||||
#[arg(value_name = "FILTER")]
|
||||
filter: Option<String>,
|
||||
},
|
||||
|
||||
/// Show a template function's arguments as JSON
|
||||
Show {
|
||||
/// Template function name (for example: response.body.path)
|
||||
name: String,
|
||||
|
||||
/// Pretty-print JSON output
|
||||
#[arg(long)]
|
||||
pretty: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
@@ -361,6 +391,13 @@ pub enum FolderCommands {
|
||||
workspace_id: Option<String>,
|
||||
},
|
||||
|
||||
/// Output JSON schema for folder create/update payloads
|
||||
Schema {
|
||||
/// Pretty-print schema JSON output
|
||||
#[arg(long)]
|
||||
pretty: bool,
|
||||
},
|
||||
|
||||
/// Show a folder as JSON
|
||||
Show {
|
||||
/// Folder ID
|
||||
|
||||
@@ -5,7 +5,9 @@ use crate::utils::json::{
|
||||
apply_merge_patch, is_json_shorthand, merge_workspace_id_arg, parse_optional_json,
|
||||
parse_required_json, require_id, validate_create_id,
|
||||
};
|
||||
use crate::utils::schema::append_agent_hints;
|
||||
use crate::utils::workspace::resolve_workspace_id;
|
||||
use schemars::schema_for;
|
||||
use yaak_models::models::Folder;
|
||||
use yaak_models::util::UpdateSource;
|
||||
|
||||
@@ -14,6 +16,7 @@ type CommandResult<T = ()> = std::result::Result<T, String>;
|
||||
pub fn run(ctx: &CliContext, args: FolderArgs) -> i32 {
|
||||
let result = match args.command {
|
||||
FolderCommands::List { workspace_id } => list(ctx, workspace_id.as_deref()),
|
||||
FolderCommands::Schema { pretty } => schema(pretty),
|
||||
FolderCommands::Show { folder_id } => show(ctx, &folder_id),
|
||||
FolderCommands::Create { workspace_id, name, json } => {
|
||||
create(ctx, workspace_id, name, json)
|
||||
@@ -31,6 +34,18 @@ pub fn run(ctx: &CliContext, args: FolderArgs) -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
fn schema(pretty: bool) -> CommandResult {
|
||||
let mut schema = serde_json::to_value(schema_for!(Folder))
|
||||
.map_err(|e| format!("Failed to serialize folder schema: {e}"))?;
|
||||
append_agent_hints(&mut schema);
|
||||
|
||||
let output =
|
||||
if pretty { serde_json::to_string_pretty(&schema) } else { serde_json::to_string(&schema) }
|
||||
.map_err(|e| format!("Failed to format folder schema JSON: {e}"))?;
|
||||
println!("{output}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list(ctx: &CliContext, workspace_id: Option<&str>) -> CommandResult {
|
||||
let workspace_id = resolve_workspace_id(ctx, workspace_id, "folder list")?;
|
||||
let folders =
|
||||
|
||||
@@ -7,4 +7,5 @@ pub mod import_export;
|
||||
pub mod plugin;
|
||||
pub mod request;
|
||||
pub mod send;
|
||||
pub mod template_function;
|
||||
pub mod workspace;
|
||||
|
||||
@@ -122,6 +122,20 @@ fn enrich_schema_guidance(schema: &mut Value, request_type: RequestSchemaType) {
|
||||
"For path segments like `/foo/:id/comments/:commentId`, put concrete values in `urlParameters` using names that keep the leading `:` (for example `:id`, `:commentId`). A name without the `:` is sent as a query string parameter instead, leaving the placeholder in the path.",
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(body_type_schema) = properties.get_mut("bodyType").and_then(Value::as_object_mut) {
|
||||
append_description(
|
||||
body_type_schema,
|
||||
"Known values: `application/json`, `text/xml`, `application/x-www-form-urlencoded`, `multipart/form-data`, `graphql`, `binary`, `other`, or null for no body. This selects how `body` is encoded; it does NOT add a `Content-Type` header. Add that header yourself, matching the body type (`other` pairs with `text/plain` and `graphql` with `application/json`). Multipart is the exception: its header is generated at send time to carry the boundary.",
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(body_schema) = properties.get_mut("body").and_then(Value::as_object_mut) {
|
||||
append_description(
|
||||
body_schema,
|
||||
"Shape depends on `bodyType`. Text-ish types (`application/json`, `text/xml`, `other`) use `{\"text\": \"...\"}` where the value is a string, so JSON bodies are a JSON string containing JSON. Form types use `{\"form\": [{\"name\": \"a\", \"value\": \"1\", \"enabled\": true}]}`, and a multipart entry may use `file` (an absolute path) and `contentType` instead of `value`. `binary` uses `{\"filePath\": \"/abs/path\"}`. `graphql` uses `{\"query\": \"...\", \"variables\": \"{}\", \"operationName\": \"\"}` where `variables` is a string of JSON.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn append_description(schema: &mut Map<String, Value>, extra: &str) {
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
use crate::cli::{TemplateFunctionArgs, TemplateFunctionCommands};
|
||||
use crate::context::CliContext;
|
||||
use yaak_plugins::events::{PluginContext, TemplateFunction};
|
||||
|
||||
type CommandResult<T = ()> = std::result::Result<T, String>;
|
||||
|
||||
pub async fn run(ctx: &CliContext, args: TemplateFunctionArgs) -> i32 {
|
||||
let result = match args.command {
|
||||
TemplateFunctionCommands::List { filter } => list(ctx, filter.as_deref()).await,
|
||||
TemplateFunctionCommands::Show { name, pretty } => show(ctx, &name, pretty).await,
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(()) => 0,
|
||||
Err(error) => {
|
||||
eprintln!("Error: {error}");
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Template functions come from plugins, so the only accurate list is the one the
|
||||
/// installed plugins report right now.
|
||||
async fn all(ctx: &CliContext) -> CommandResult<Vec<TemplateFunction>> {
|
||||
let plugin_context = PluginContext::new_empty();
|
||||
let summaries = ctx
|
||||
.plugin_manager()
|
||||
.get_template_function_summaries(&plugin_context)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list template functions: {e}"))?;
|
||||
|
||||
let mut functions: Vec<TemplateFunction> =
|
||||
summaries.into_iter().flat_map(|summary| summary.functions).collect();
|
||||
functions.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
Ok(functions)
|
||||
}
|
||||
|
||||
async fn list(ctx: &CliContext, filter: Option<&str>) -> CommandResult {
|
||||
let mut functions = all(ctx).await?;
|
||||
|
||||
if let Some(filter) = filter {
|
||||
let needle = filter.to_lowercase();
|
||||
functions.retain(|f| f.name.to_lowercase().contains(&needle));
|
||||
}
|
||||
|
||||
if functions.is_empty() {
|
||||
match filter {
|
||||
Some(filter) => println!("No template functions matching '{filter}'"),
|
||||
None => println!("No template functions found"),
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for function in functions {
|
||||
let args = function.args.iter().filter_map(arg_name).collect::<Vec<_>>().join(", ");
|
||||
match function.description {
|
||||
Some(description) if !description.is_empty() => {
|
||||
println!("{}({}) - {}", function.name, args, description)
|
||||
}
|
||||
_ => println!("{}({})", function.name, args),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn show(ctx: &CliContext, name: &str, pretty: bool) -> CommandResult {
|
||||
let functions = all(ctx).await?;
|
||||
let function = functions
|
||||
.iter()
|
||||
.find(|f| {
|
||||
f.name == name
|
||||
|| f.aliases.as_ref().is_some_and(|aliases| aliases.iter().any(|a| a == name))
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
let names = functions.iter().map(|f| f.name.as_str()).collect::<Vec<_>>();
|
||||
format!("No template function named '{name}'. Available: {}", names.join(", "))
|
||||
})?;
|
||||
|
||||
let output = if pretty {
|
||||
serde_json::to_string_pretty(function)
|
||||
} else {
|
||||
serde_json::to_string(function)
|
||||
}
|
||||
.map_err(|e| format!("Failed to serialize template function: {e}"))?;
|
||||
|
||||
println!("{output}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn arg_name(arg: &yaak_plugins::events::TemplateFunctionArg) -> Option<String> {
|
||||
use yaak_plugins::events::{FormInput, TemplateFunctionArg};
|
||||
|
||||
let TemplateFunctionArg::FormInput(input) = arg;
|
||||
let base = match input {
|
||||
FormInput::Text(v) => &v.base,
|
||||
FormInput::Editor(v) => &v.base,
|
||||
FormInput::Select(v) => &v.base,
|
||||
FormInput::Checkbox(v) => &v.base,
|
||||
FormInput::File(v) => &v.base,
|
||||
FormInput::HttpRequest(v) => &v.base,
|
||||
FormInput::KeyValue(v) => &v.base,
|
||||
// Layout-only inputs have no value of their own
|
||||
FormInput::Accordion(_)
|
||||
| FormInput::HStack(_)
|
||||
| FormInput::Banner(_)
|
||||
| FormInput::Markdown(_) => return None,
|
||||
};
|
||||
|
||||
if base.name.trim().is_empty() { None } else { Some(base.name.clone()) }
|
||||
}
|
||||
@@ -37,6 +37,13 @@ async fn main() {
|
||||
|
||||
let exit_code = match command {
|
||||
Commands::Agent(args) => commands::agent::run(args),
|
||||
Commands::TemplateFunction(args) => {
|
||||
let mut context = CliContext::new(data_dir.clone(), app_id);
|
||||
context.init_plugins(CliExecutionContext::default()).await;
|
||||
let exit_code = commands::template_function::run(&context, args).await;
|
||||
context.shutdown().await;
|
||||
exit_code
|
||||
}
|
||||
Commands::Auth(args) => commands::auth::run(args).await,
|
||||
Commands::Import(args) => {
|
||||
let mut context = CliContext::new(data_dir.clone(), app_id);
|
||||
|
||||
@@ -949,7 +949,7 @@ pub struct ParentHeaders {
|
||||
pub headers: Vec<HttpRequestHeader>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
#[enum_def(table_name = "folders")]
|
||||
|
||||
Reference in New Issue
Block a user