@willowai/cli
v0.0.3
Published
Willow CLI — the developer entrypoint to the Willow AI control platform
Readme
willow
The developer CLI for the Willow AI control platform — the identity, governance, and capability layer for AI agents inside the enterprise.
willow is the single entrypoint for humans and AI agents to talk to the
platform: log in, switch between organizational contexts, browse the catalog of
MCP tools you have access to, and call them from a shell.
Features
- Hierarchical, noun-verb commands —
willow auth login,willow tools call,willow context use ... - Multi-context — same org, but switch between any number of
mcp/toolkittargets - OAuth-first auth — PKCE flow with token cache; static bearer tokens still supported for CI
- Agent-optimized —
willow auth statusandwillow auth tokenare designed to be scripted - Connection pooling — lazy-spawn daemon keeps MCP connections warm between invocations
- Self-diagnosing —
willow doctorchecks env, config, auth, daemon, MCP reachability - Self-updating —
willow updatepulls the latest@willow/clifrom npm - Actionable errors — every error ships with a
Suggestion:line tuned for both humans and LLMs
Quick start
Requires Node.js ≥ 18.
npm install -g @willow/cli
willow init # interactive setup
willow auth login # OAuth, opens browser
willow doctor # sanity-check the environment
willow tools list -d # browse the catalog
willow tools call read_file '{"path":"./README.md"}'Command surface
willow auth login | logout | whoami | status | token
willow context list | use | add | remove
willow tools list | info | grep | call | servers | refresh
willow config get | set | show | edit
willow doctor # diagnose env, auth, daemon, MCP
willow init # create ~/.config/willow/config.json
willow update # self-update via npm
willow uninstall-skill # remove the willow SKILL.md from every detected AI agentGlobal flags
| Flag | Description |
| ------------------------- | ---------------------------------------------------------- |
| -c, --config <path> | Override the config file path |
| --context <name> | Use a specific context for this invocation only |
| -d, --with-descriptions | Include tool descriptions in tools list / tools grep |
| -h, --help | Show help (works at every level: willow tools call --help) |
| -v, --version | Show version |
Auth (willow auth)
willow auth login # OAuth login (opens browser)
willow auth logout # Remove stored OAuth tokens
willow auth whoami # Identity, org, current context
willow auth status # Offline check; exit 0=ok, 4=needs login
willow auth token # Print the current bearer token to stdoutwillow auth status is designed to be the cheap preflight check at the top of
scripts and AI-agent sessions:
willow auth status && willow tools call my_tool '{}'willow auth token prints only the token, suitable for piping:
curl -H "Authorization: Bearer $(willow auth token)" https://example.com/apiContexts (willow context)
A context is a named (mcp, toolkit) pair scoped to your current org. All
contexts share the same org / baseUrl / userAccessKey — only the target on
the gateway changes.
willow context list # show all (active is starred)
willow context add slack-eng --mcp slack --toolkit engineering
willow context add github-platform --mcp github --toolkit platform --use
willow context use slack-eng
willow context remove old-contextEvery command that needs a server uses the active context. You can override the
active context per-invocation with the global --context <name> flag — handy
for one-off calls without flipping state.
Tools (willow tools)
willow tools list # tool names
willow tools list -d # names + descriptions
willow tools info <tool> # JSON schema for one tool
willow tools grep <pattern> # substring search across names
willow tools grep <pattern> -d # ... include descriptions
willow tools servers # list integrations on the gateway
willow tools servers -d # ... include descriptions
willow tools call <tool> '<json>' # call with inline JSON
echo '<json>' | willow tools call <tool> # call with JSON from stdin
willow tools call <tool> --force-auth '<json>' # ensure logged in first
willow tools refresh # clear the cached tool catalogCache invalidation
The daemon caches tools list results in process memory for
settings.cacheTtl seconds. The CLI invalidates this cache automatically when
the underlying state changes:
willow context use <name>— stops the daemon so the next call respawns with the new context's headers.willow auth login— stops the daemon so the next call uses the fresh token instead of the stale one held in the existing connection.
If you need to force a refresh manually (e.g. you added or removed a tool on the platform side and don't want to wait for the TTL), run:
willow tools refreshThis stops the daemon. The next willow tools list or willow tools call
respawns it and re-fetches a fresh catalog.
willow tools servers is the lightweight discovery rung: when you don't yet
know which tool to grep for, it returns the catalog of available integrations
(slug, name, description). Use the slug as a keyword in a follow-up
willow tools grep <slug> to narrow down.
Examples:
willow tools list -d
willow tools info read_file
willow tools grep file
willow tools servers -d
willow tools call search_issues '{"query": "open bugs", "limit": 5}'
# Pipe JSON through jq before calling
jq -n '{path: "./src/index.ts"}' | willow tools call read_filewillow tools call extracts text content from MCP responses and writes it
straight to stdout (no jq needed for the common case). When the response has
no text parts, it falls back to pretty-printed JSON.
Config (willow config)
willow config show # entire config as JSON
willow config get <key> # single value (dot-notation)
willow config set <key> <value> # set a value (dot-notation, type-coerced)
willow config edit # open in $VISUAL / $EDITOR; validates on saveExamples:
willow config set settings.timeout 30
willow config set settings.daemon false
willow config set contexts.default.mcp slack
willow config get settings.timeoutValues written through set are best-effort coerced (true/false/null,
numerics). For anything more structured, use willow config edit.
Doctor (willow doctor)
Runs a checklist over the local Willow environment:
- Node version
- Config file present and valid JSON
- At least one context defined; active context exists
- Auth state (OAuth token present and not expired)
- Daemon process health
- MCP server reachability (HTTP
HEAD/ stdio--version) - Skill install status (per detected AI agent)
Exits non-zero if any item fails, so it's safe to use in CI.
willow doctorUpdate (willow update)
willow update # install the latest @willow/cli globally via npm
willow update --check # only print whether an update is availableUninstalling the agent skill (willow uninstall-skill)
willow init installs a small SKILL.md into every detected AI
agent (Cursor, Codex, Claude Code, Windsurf, GitHub Copilot, Cline, Roo Code,
Continue) so the agent can discover the CLI on its own. If you ever want the
agent to stop opportunistically reaching for willow, remove it with:
willow uninstall-skillThis deletes the per-agent skill directories and the canonical
~/.agents/skills/willow/ directory. It does not uninstall the willow
binary itself, change your config, or revoke any tokens — only the AI-facing
skill file is removed. Reinstall any time by running willow init again.
willow doctor always shows the current install status under "Skill".
Config file format
The config lives at ~/.config/willow/config.json (override via
-c / WILLOW_CONFIG_PATH).
{
"enabled": true,
"org": "acme",
"currentContext": "default",
"contexts": {
"default": { "mcp": null, "toolkit": null },
"slack-eng": { "mcp": "slack", "toolkit": "engineering" }
},
"settings": {
"timeout": 60,
"daemon": true
}
}For static bearer-token auth (rarely needed; OAuth via willow auth login is
the default):
{
"baseUrl": "https://acme.mcp-s.com/mcp",
"token": "${MY_BEARER}",
"currentContext": "default",
"contexts": { "default": {} }
}For stdio mode (runs MCP locally via npx -y @mcp-s/mcp):
{
"org": "acme",
"userAccessKey": "<key>",
"currentContext": "default",
"contexts": { "default": { "mcp": "slack", "toolkit": "engineering" } }
}Top-level fields
| Field | Description |
| ---------------- | ----------------------------------------------------------------- |
| enabled | Master kill-switch. When false, all commands refuse to run. |
| org | Org name — derives URL https://<org>.mcp-s.com/mcp |
| baseUrl | Custom server URL (alternative to org) |
| userAccessKey | User access key — triggers stdio mode via npx @mcp-s/mcp |
| token | Static Bearer token; usually replaced by OAuth (auth login) |
| currentContext | Name of the active context |
| contexts | Map of name → { mcp, toolkit, allowedTools, disabledTools } |
| settings | Per-installation behavioral knobs (see below) |
Environment variable substitution is supported anywhere in the file:
"token": "${MY_TOKEN}".
settings block
| Field | Description | Default |
| --------------- | ---------------------------------------------------- | ------- |
| timeout | Request timeout (seconds) | 1800 |
| maxRetries | Retry attempts for transient errors (0 = disable) | 3 |
| retryDelay | Base retry delay (milliseconds) | 1000 |
| daemon | Enable connection caching (daemon mode) | true |
| daemonTimeout | Idle timeout for cached connections (seconds) | 300 |
| cacheTtl | Tool-list cache TTL in seconds (0 = disable) | 300 |
| history | Append each invocation to history.jsonl | false |
Per-context fields
Each entry under contexts.<name>:
| Field | Description |
| --------------- | -------------------------------------------- |
| mcp | MCP identifier (sent as x-mcp header) |
| toolkit | Toolkit name (sent as x-toolkit header) |
| allowedTools | Glob patterns of tool names to allow |
| disabledTools | Glob patterns of tool names to exclude |
Working with complex JSON arguments
When arguments contain quotes, special characters, or multi-line content, use stdin to avoid shell-escaping pain:
# Heredoc
willow tools call create_ticket <<EOF
{"title": "It's broken", "description": "User said \"it doesn't work\""}
EOF
# From a file
cat args.json | willow tools call some_tool
# Built with jq
jq -n '{query: "open bugs", assignee: "me"}' | willow tools call search_issuesChaining and scripting
# Find issues and extract URLs
willow tools call search_issues '{"query": "priority: high"}' \
| jq -r '.issues[].url'
# Conditional: only act if a precondition holds
willow tools call list_channels '{}' \
| jq -e '.channels[] | select(.name == "engineering")' >/dev/null \
&& willow tools call post_message '{"channel": "engineering", "text": "Deploy complete"}'
# Skip everything if not authenticated
willow auth status || { echo "Run 'willow auth login' first" >&2; exit 1; }Tips:
jq -rfor raw output (no surrounding quotes)jq -eto makejqset the exit code from the filter result2>/dev/nullto silence diagnostics when probing existence- Pipe
willow auth tokenstraight intocurl -H "Authorization: Bearer ..."
Environment variables
| Variable | Description |
| ---------------------- | ------------------------------------------------------ |
| WILLOW_CONFIG_PATH | Override the config file path |
| WILLOW_DEBUG | Enable verbose debug output to stderr |
| WILLOW_STRICT_ENV | false to warn instead of erroring on missing ${VAR}|
| WILLOW_NO_OAUTH | Internal: disable OAuth in spawned subprocesses |
Output streams
| Stream | Content | | ---------- | ---------------------------------------- | | stdout | Tool results, JSON, scriptable output | | stderr | Errors, diagnostics, progress messages |
Exit codes
| Code | Meaning |
| ---- | ---------------------------------------------- |
| 0 | Success |
| 1 | Client error (bad args, missing config, etc.) |
| 2 | Server error (tool execution failed) |
| 3 | Network error (connection failed) |
| 4 | Auth error (willow auth login required) |
Using with AI agents
willow is designed to give AI coding agents direct shell access to the
platform without burning thousands of tokens loading every tool schema into the
prompt. The agent discovers and inspects tools on demand.
A ready-made SKILL.md ships with the package and is installed
automatically by willow init for any detected agent (Claude Code, Cursor,
Gemini CLI, ...). You can also drop the snippet below into your agent's system
prompt:
## Willow
You have access to enterprise tools through the Willow platform via the `willow` CLI.
Preflight (always run first):
willow auth status # exit 0=ready, 4=run `willow auth login`
Discovery → Inspect → Execute:
willow tools grep <query> # find candidate tool names
willow tools info <tool> # get JSON schema
willow tools call <tool> '<json>' # execute (or pipe JSON via stdin)
Examples:
willow tools call search_issues '{"query":"open bugs"}'
echo '{"query":"urgent"}' | willow tools call search_issuesArchitecture
Connection pooling (daemon)
Daemon mode is on by default. The first invocation lazy-spawns a willow-<uid>
worker that keeps the MCP connection open for settings.daemonTimeout seconds
(default: 300s) after the last request, avoiding repeated startup latency.
willow tools call some_tool '{}' # uses cached connection (default)
WILLOW_DEBUG=1 willow tools list # see connection debug output
willow doctor # check daemon healthTo disable, run willow config set settings.daemon false. With the daemon off,
each call opens a fresh connection and tears it down on exit.
Retries
Transient failures are retried automatically with exponential backoff.
- Retried: network errors (
ECONNREFUSED,ETIMEDOUT,ECONNRESET), HTTP429,502,503,504 - Failed immediately: config errors, auth errors (
401,403), tool-not-found, invalid JSON
Error messages
Every error has the same shape, optimized for both humans and LLMs:
Error [TOOL_NOT_FOUND]: Tool "search" not found in server "server"
Details: Available tools: search_issues, create_ticket, list_channels (+5 more)
Suggestion: Run `willow tools list` to see all available toolsContributing
git clone https://github.com/willow-ai/cli.git willow-cli
cd willow-cli
npm install
npx simple-git-hooks # register the pre-commit lint hook (once)Development workflow:
npm run dev # run from source
npm run typecheck # tsc --noEmit
npm run lint # biome
npm test # vitest
npm run build # tsup → dist/The pre-commit hook runs npm run lint:fix automatically and re-stages any
auto-fixed files.
Releasing
- Bump the version in
package.json. - Commit and push to
main. CI runs lint + typecheck + tests. - On a green build with a version bump, the release workflow tags the commit
and publishes
@willow/clito npm.
License
MIT — see LICENSE.
