factorly serve # start MCP server (stdio)
factorly serve --port 3000 # start MCP server (HTTP at /mcp)
factorly serve --http-token <tok> # HTTP with Bearer token auth
factorly serve --http-token '{{vault:HTTP_TOKEN}}' # token from vault
factorly init # create .factorly/factorly.yaml (interactive)
factorly init --out factorly.yaml # create at custom path
factorly tools # list all configured tools
factorly tools list # same as above
factorly tools add # add a tool (interactive)
factorly tools add --name x --type cli # add a tool (non-interactive)
factorly tools remove <tool> # remove a tool from config
factorly tools import openapi <spec> # generate tools from OpenAPI spec
factorly blueprint list # list installed blueprints
factorly blueprint install <name> # install a bundled blueprint (e.g. "linear", "gmail")
factorly blueprint install <src> # install from URL, github.com/owner/repo, or local file
factorly blueprint install <src> --dry-run # preview without writing
factorly blueprint install <src> --no-prompt # skip interactive vault-key prompts
factorly blueprint uninstall <name> # remove an installed blueprint
factorly tools record # create a tool from a pasted curl command
factorly tools record --curl '...' # create from inline curl
factorly tools record --dry-run # preview YAML without writing
factorly tools promote --from-sha <prefix> --name <name> # promote a factorly.code audit entry into a named type:code tool
factorly tools promote --from-sha 8e3c --name x.y --overwrite # replace an existing tool of the same name
factorly tools promote --from-sha 8e3c --name x.y --no-confirm # skip the default shadow.confirm:true
factorly call <tool> [--param val] # call a tool (cli, rest, mcp, or workflow)
factorly sync # push MCP config to AI clients
factorly sync --global # sync to user-level config (~/.claude/, etc.)
factorly sync --http localhost:3000 # sync HTTP mode
factorly sync --remove # remove factorly from client configs
factorly tools status # check all tools are reachable
factorly auth login <provider> # OAuth login (opens browser)
factorly auth status [provider] # show OAuth token status
factorly auth logout <provider> # remove stored OAuth tokens
factorly vault set <key> [value] # store a secret (prompts if no value)
factorly vault get <key> # retrieve a secret (raw value to stdout)
factorly vault list # list secret names
factorly vault delete <key> # remove a secret
factorly workspaces # list available workspaces
factorly workspaces show <name> # print a workspace's vars (secrets masked)
factorly workspaces create <name> [-d ...] # create a new workspace YAML
factorly workspaces delete <name> [--force] # remove workspace YAML (vault file untouched)
factorly logs # view recent audit log entries
factorly logs -n 50 # show last 50 entries
factorly logs --tool github # filter by tool name
factorly logs --status blocked # filter by status
factorly logs --detail # show full entry details
factorly logs -f # follow mode (tail -f style)
factorly logs stats # show summary statistics
factorly logs verify # verify hash chain integrity
factorly logs repair # repair broken hash chain
factorly exec -- <command> [args] # run a command with compression + logging
factorly exec --compress json -- curl https://api.example.com # with options
factorly wrap -- <command> [args] # zero-config MCP proxy for any server
factorly wrap --url <url> # proxy a remote MCP server
factorly version # print version (uses 24h cache)
factorly version --check # force version check (bypass cache)-v, --verbose # print debug info to stderr
-c, --config <path> # path to factorly.yaml
--config-dir # load tools from a directory (no config file needed)
-w, --workspace <name> # activate a named workspace overlay (see workspaces.md) --vault-path # explicit path to vault file (overrides auto-detection)
--global # use global vault (~/.config/factorly/vault.enc) instead of project vaultFACTORLY_VAULT_PASSWORD # global vault password (also shared fallback for project vault)
FACTORLY_PROJECT_VAULT_PASSWORD # project vault password (.factorly/vault.enc)
FACTORLY_WORKSPACE_VAULT_PASSWORD_<UPPER_NAME> # per-workspace vault password
FACTORLY_VAULT_PATH # vault file path override
FACTORLY_WORKSPACE # active workspace overlay (fallback for --workspace)
FACTORLY_HTTP_TOKEN # HTTP server auth token (fallback for --http-token)
FACTORLY_NO_LOG # disable call logging when set
FACTORLY_MAX_OUTPUT # global max output bytes (fallback for per-tool max_output)
FACTORLY_DISABLED_TOOLS # comma-separated tool names to disable (applies to call + tools listing)Parameters are passed as --name value pairs:
factorly call api.users --limit 10 --status activeUse - to read a parameter value from stdin. Only one parameter per call can use stdin.
# Pipe input
echo "What is the capital of France?" | factorly call anthropic.ask --prompt -
# Heredoc
factorly call anthropic.ask --prompt - <<'EOF'
Explain this code:
func main() { fmt.Println("hello") }
EOFUse @filename to read a parameter value from a file (like curl's --data @file.json):
factorly call anthropic.ask --prompt @prompt.txt
factorly call api.upload --body @data.jsonTo pass a literal @ prefix, escape with @@:
factorly call tool --email @@user # sends @userWorkflows are tools with type: workflow — call them like any other tool:
factorly call deploy.staging --branch main
factorly call deploy.staging --branch main -v # verbose: see each stepWith -v, each step streams progress to stderr:
[workflow] deploy.staging started (run: a1b2c3d4)
[workflow] 1/4 git.branch running...
[workflow] 1/4 git.branch completed 12ms
[workflow] 2/4 make.test running...
[workflow] 2/4 make.test completed 5.2s
[workflow] 3/4 make.deploy skipped (condition false)
[workflow] 4/4 slack.notify running...
[workflow] 4/4 slack.notify completed 342ms
[workflow] deploy.staging completed (4 steps, 5.6s)
Steps can have if: (skip when false), require: (stop workflow when false), and switch: (multi-branch):
steps:
- tool: make.test
store: output
if: "branch == 'main'" # skip this step if not main
- tool: make.deploy
require: "contains(output, 'PASS')" # stop workflow if tests failed
- switch: # multi-branch
- condition: "contains(output, 'PASS')"
tool: slack.post
params: { text: "✓ passed" }
- condition: "true" # default
tool: slack.post
params: { text: "✗ failed" }if:— skip one step, continuerequire:— stop entire workflow (graceful, status = completed)switch:— first matching condition executes
Conditions use the expression language: comparisons (==, !=, >, <), boolean logic (and, or, not), contains(), jsonpath(), and member access (response.code).
Workflow output is JSON with the full execution trace:
{
"status": "completed",
"steps": [
{"tool": "git.branch", "status": "completed", "duration_ms": 12},
{"tool": "make.test", "status": "completed", "duration_ms": 5200},
{"tool": "make.deploy", "status": "skipped"},
{"tool": "slack.notify", "status": "completed", "duration_ms": 342}
],
"result": "Message posted"
}State is persisted to .factorly/workflows/<run-id>.json after each step.
See Workflows and Expressions for full documentation.
Runs a single shell command through Factorly's full safety layer — the zero-config equivalent of a CLI tool definition. Uses the same code path as config-based CLI tools.
factorly exec -- git status
factorly exec --compress json -- npm test
factorly exec --env-isolation strict -- ./deploy.sh
factorly exec -i -- psql -h localhost mydbSupports {{vault:KEY}} and {{env:VAR}} references in arguments — secrets stay out of shell history. Add |default for fallbacks:
factorly exec -- curl -H "Authorization: Bearer {{vault:GITHUB_TOKEN}}" https://api.github.com/user
factorly exec -- curl "{{env:API_URL|https://api.example.com}}/users"--max-output <bytes> # max output bytes (default: 50000)
--compress <mode> # compression: all, json, logs, none (default: "all")
--env-isolation <mode> # "strict" for minimal env (default: inherit parent)
--env KEY=VALUE # set env var (repeatable; supports {{env:VAR}} and {{vault:KEY}})
-i, --interactive # connect directly to terminal (skip compression, for TTY tools)
--timeout <duration> # execution timeout (e.g. 30s, 5m; default: 30s)Use --env with --env-isolation strict to pass specific vars into a restricted environment:
factorly exec --env-isolation strict --env DISPLAY={{env:DISPLAY}} -- xsel --clipboard
factorly exec --env-isolation strict --env GITHUB_TOKEN={{vault:GITHUB_TOKEN}} -- gh pr list- Resolves
{{env:VAR}}and{{vault:KEY}}references in arguments - Runs the command through the CLI provider (same as config-based tools)
- Applies built-in output filters for recognized commands (git, make, npm, go test, cargo, pip)
- Applies compression and truncation (unless
-iinteractive mode) - Logs to the JSONL audit trail (interface:
exec) - Prints processed output and preserves the command's exit code
Built-in filters automatically reduce noise for common commands — for example, factorly exec -- go test ./... will short-circuit to "ok (all tests passed)" on success, or show only PASS/FAIL lines on failure. See Output Filters for the full list and how to define custom filters.
Wraps any MCP server with Factorly's safety layer (compression, truncation, loop detection, rate limiting) — no config file needed. Tools keep their original names (no server prefix).
# Wrap a stdio MCP server
factorly wrap -- npx @modelcontextprotocol/server-github
# Wrap a remote HTTP MCP server
factorly wrap --url http://localhost:3001/mcp--url <url> # connect to a remote MCP server instead of spawning a subprocess
--rate-limit <spec> # rate limit (e.g. "100/hour")
--max-output <bytes> # max output bytes per call (default: 50000)
--compress <hints> # compression hints: "json", "logs", or "all" (default: "all")
--host <addr> # address to bind (default: 127.0.0.1)
--port <port> # port for HTTP mode
--http-token <tok> # Bearer token for HTTP mode
--env-isolation <mode> # "strict" for minimal env, default inherits parent
--env KEY=VALUE # set env var (repeatable; supports {{env:VAR}} and {{vault:KEY}})
--timeout <duration> # tool call timeout (e.g. 30s, 5m; default: 30s)- Compression:
all - Max output:
50000bytes - Loop detection: always-on
Factorly ships with governed alternatives to common agent tools. These are available automatically — no YAML config needed. All prefixed factorly. to avoid collision with user-defined tools.
| Tool | Description | Default oversight |
|---|---|---|
factorly.shell |
Run a shell command | Confirm required, destructive patterns blocked |
factorly.file.read |
Read a local file | Sensitive paths blocked (.env, .ssh, credentials) |
factorly.file.write |
Write a local file | Confirm required, system paths blocked |
factorly.fetch |
HTTP GET a URL | Cloud metadata + private networks blocked |
factorly.clipboard |
Copy text to clipboard | Confirm required |
In stdio mode (default): all 5 tools available.
In HTTP mode (--port): only factorly.fetch — local tools don't make sense on a remote server.
Built-in tools have default safety restrictions that block dangerous operations before execution:
Shell — blocks rm -rf /, curl | sh, DROP TABLE, shutdown, fork bombs, and similar destructive patterns.
Read/Write — blocks .env, .ssh/id_*, *.pem, credentials.json, /etc/shadow, system directories (/etc/, /usr/, /bin/), and shell configs.
Fetch — blocks cloud metadata (169.254.169.254), localhost, private networks (10.*, 172.16-31.*, 192.168.*), and file:// protocol.
Override default denials for specific cases:
tools:
factorly.shell:
shadow:
allow_patterns: ["rm -rf ./build"] # permit this specific command
factorly.file.read:
shadow:
allow_paths: [".env.example"] # permit reading this file
factorly.fetch:
shadow:
allow_urls: ["http://localhost:8080"] # permit local dev server# Disable specific built-ins
FACTORLY_DISABLED_TOOLS=factorly.shell,factorly.file.writeOr disable all built-ins in config:
disable_builtins: trueWhen running factorly serve --port, you can secure the endpoint with a Bearer token. The token is resolved in order:
--http-tokenflagFACTORLY_HTTP_TOKENenvironment variable
Both support {{vault:KEY}} references — the vault is opened automatically if a vault ref is detected:
# Plain token (visible in ps output — use for dev only)
factorly serve --port 3000 --http-token mytoken
# Environment variable (better for CI/deployment)
FACTORLY_HTTP_TOKEN=mytoken factorly serve --port 3000
# Vault reference (best — token encrypted at rest)
factorly vault set HTTP_TOKEN "my-secret-token"
factorly serve --port 3000 --http-token '{{vault:HTTP_TOKEN}}'
# Vault ref via env var
FACTORLY_HTTP_TOKEN='{{vault:HTTP_TOKEN}}' factorly serve --port 3000When a token is set, all HTTP requests must include Authorization: Bearer <token>. Requests without a valid token receive a 401 response. A warning is printed to stderr when HTTP mode starts without any token configured.
By default, the server binds to 127.0.0.1 (localhost only). If Factorly is running inside a container (Docker, devcontainer, Codespace), bind to all interfaces with --host 0.0.0.0:
factorly serve --host 0.0.0.0 --port 3000 --http-token '{{vault:HTTP_TOKEN}}'
factorly sync --http host.docker.internal:3000 --token '{{vault:HTTP_TOKEN}}'