Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
a970ec1
Add `factorly ui` — localhost web UI for tool management
jsherer May 2, 2026
54d4523
Make factorly ui functional — tool CRUD, vault, workflows, template i…
jsherer May 3, 2026
1e27a0a
Redesign UI with Postman-style response panel and persistent sidebar
jsherer May 4, 2026
e639ff0
Add visual workflow pipeline builder and history page
jsherer May 4, 2026
506d188
Add Lucide icons, full tool config editing, compact workflow steps, a…
jsherer May 4, 2026
89f7f3f
Fix tool save, full step config editing, and htmx navigation polish
jsherer May 4, 2026
fc85b47
Add host validation and per-run token auth to UI server
jsherer May 4, 2026
2cadd6c
Update layout to include factorly logo. Add tool edit to include auth
jsherer May 4, 2026
e8c834b
Add editable auth config for REST tools, fix lint issues
jsherer May 5, 2026
1cbd47b
Show project and global vaults separately with replace value support
jsherer May 5, 2026
d34b5ab
Quick cleanup to allow creating a tool on empty tool page
jsherer May 5, 2026
47c9939
Adds OAuth refresh button to auth page
jsherer May 5, 2026
c701300
Adds mcp hosting under the ui router for a single command
jsherer May 5, 2026
e152507
Add live activity feed with SSE streaming from proxy events
jsherer May 5, 2026
6d92813
Move activity feed from standalone page to slide-out drawer panel
jsherer May 5, 2026
5dcaa1f
Update navigation order
jsherer May 6, 2026
4deda2c
Update nav heights
jsherer May 6, 2026
12cccaf
Restructure UI navigation: separate workflows section, inline rename,…
jsherer May 6, 2026
6432c0e
Fix tool save/delete for multi-tool files, add step reordering and MC…
jsherer May 7, 2026
4972695
Add full CRUD for OAuth providers in the auth management UI
jsherer May 7, 2026
ab9a446
Adds tests for UI
jsherer May 7, 2026
b7cf4fa
Add OpenAPI import UI with preview, filtering, and tool name sanitiza…
jsherer May 7, 2026
f4c666f
Fix path traversal, open redirect, and cookie security from CodeQL audit
jsherer May 7, 2026
6c4f8b2
Harden OpenAPI spec reader and REST file param against CodeQL findings
jsherer May 7, 2026
4678993
Automatically register/unregister tools as they are modified in the UI
jsherer May 7, 2026
bd0bcae
Runtime tool registration, OpenAPI path params, --no-launch flag, ded…
jsherer May 7, 2026
e561aa6
Fix MCP elicitation for CLI tools
jsherer May 7, 2026
0aa9937
Bump templates
jsherer May 7, 2026
aec5986
Browser confirm, shadow sync, vault tracking, path param auto-detect
jsherer May 7, 2026
4b62d5b
Update sonnet references
jsherer May 7, 2026
8d0173c
Adds output filters to the UI
jsherer May 7, 2026
241b9d2
Add hidden tool visibility toggle, graceful vault fallback, runtime t…
jsherer May 7, 2026
29cb5ec
Fix duplicate tool errors from loose YAML files in config directory
jsherer May 7, 2026
84d815b
Fix auth vault fallback, template OAuth install, empty query params, …
jsherer May 7, 2026
1a68db1
Refactor UI: split registerProvider, extract JS, consolidate helpers
jsherer May 8, 2026
53b3fb5
Move inline HTML from handlers to template partials, delete dead code
jsherer May 8, 2026
5bebe4d
Added some sanitization of tool names
jsherer May 8, 2026
22ae983
Security: bind localhost by default, block SSRF and path traversal in…
jsherer May 8, 2026
b9030ee
Fix Telegram template auth, resolve vault refs in REST paths, clear v…
jsherer May 8, 2026
f4c47c6
Add body_type (json/form/raw), param in: field, config reload, shared…
jsherer May 8, 2026
4e7db89
Add headers configuration UI for REST tools
jsherer May 8, 2026
685e453
Ability to quickly duplicate a tool
jsherer May 8, 2026
1eff894
Redact secrets from verbose REST logging without storing secrets in m…
jsherer May 8, 2026
53ed8f3
Redact sensitive headers by name pattern in verbose REST logging
jsherer May 8, 2026
407a1cb
Workflow editor: zero-step support, server-rendered steps, input para…
jsherer May 8, 2026
bbdc60c
Add value expressions for workflow params with 11 functions, proxy-le…
jsherer May 8, 2026
8bda5bc
Unify {{expr:...}} as a resolver backend alongside {{vault:...}} and …
jsherer May 9, 2026
0572989
Update docs for resolver backends
jsherer May 9, 2026
1447901
Add today(), left(), right(), find(), cut(), concat() expression func…
jsherer May 9, 2026
55a5208
Fixed SSE on boot
jsherer May 9, 2026
e9cf353
Update UI for consistency between tools and workflows.
jsherer May 9, 2026
773fa42
Group sidebar tools by dot-prefix, persist collapse state, fix tool r…
jsherer May 9, 2026
52b1242
Add native builtin provider with in-process execution and project-dir…
jsherer May 10, 2026
0d611b2
Add oversight, output filter, and advanced sections to workflows editor
jsherer May 10, 2026
fbaae14
Fix confirm prompts: race elicitation + browser, add SSE reliability
jsherer May 10, 2026
350a1c5
Fix confirm prompts: race elicitation + browser, add SSE reliability
jsherer May 10, 2026
c9a1d50
Update docs and add confirm timeout + verbose logging
jsherer May 10, 2026
6e53610
Fix warnings about localhost vs production
jsherer May 10, 2026
d1843dd
Adds expand/collapse icons and better ux for copy
jsherer May 10, 2026
a727f4b
Fixed non-JSON text now gets text-green-300 (success) or text-amber-3…
jsherer May 11, 2026
341b6df
Updated favicons
jsherer May 11, 2026
35dd85f
Consistency in naming of try it vs run
jsherer May 11, 2026
f0e181f
Adds "text" param type for multi-line editing
jsherer May 11, 2026
0fcb83d
Update with web ui docs
jsherer May 11, 2026
72e784a
Updated README with screenshots of web ui
jsherer May 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 37 additions & 120 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,20 @@
[![MCP](https://img.shields.io/badge/MCP-compatible-8A2BE2)](https://modelcontextprotocol.io)
[![Docs](https://img.shields.io/badge/Docs-docs%2F-informational)](docs/)

**Stop giving your AI agents your API keys.** One command. All your tools. Credentials stay out of your agent's hands.
**Build what your agent can do.**

</center>
Define tools, compose workflows, test and run them.
MCP servers, REST APIs, and CLI commands in one config, one UI, one audit log.

Factorly is a local runtime for agent tool chains. It proxies agent tool calls, injects credentials from an encrypted vault, enforces governance rules, and logs everything.
</center>

REST APIs, CLI commands, and MCP servers run through one config, one audit log, one set of rules.
Factorly is a local runtime for agent tool chains. It manages tool calls, injects credentials from an encrypted vault, enforces governance rules, and logs everything. Your agent sees workflows, tools, and data. Secrets stay secret.

Your agent sees workflows, tool names, and data. Never secrets.

```
┌────────────┐ ┌────────────┐ ┌────────────┐
│ │ │ │ │ │
│ Your Agent │──────▶│ Factorly │──────▶│ Your Tools │
│ │ │ │ │ │
└────────────┘ └────────────┘ └────────────┘
Sees: Vault REST APIs
- tool names Governance CLI commands
- workflows Audit log MCP servers
- data Rate limits

Never sees:
- API keys
- tokens
- credentials
```
![Screenshot of Factorly UI](docs/images/screenshot.png)

---


## Install

Install with your package manager of choice:
Expand All @@ -69,126 +54,58 @@ factorly vault set GITHUB_TOKEN ghp_xxxxxxxxxxxx

# 3. Connect to your agent (auto-detects Claude Code, Cursor, Codex)
factorly sync
```

The agent never sees your credentials. Factorly injected it, made the API call, logged it, and returned the data.

---

---

## Features

MCP servers, REST APIs, CLI commands. One config, one endpoint, one audit log.

Your agent connects to Factorly once and sees all its approved tools.

### Encrypted vault

Your API keys, OAuth tokens, and secrets live in Factorly's fully encrypted local vault, using AES-256-GCM with per-entry encryption. Keys stay on your device. The agent sees tool names and data — never secrets.

```bash
# Store a secret — encrypted on disk, decrypted on demand
$ factorly vault set GITHUB_TOKEN
Enter value: ••••••••••••••••

# Reference it in any tool config
token: "{{vault:GITHUB_TOKEN}}"

# Your agent calls a tool — Factorly injects the secret
Agent sees: data
Agent never sees: ghp_xxxx...
# 3. Optional, start the UI
factorly ui
```

### 36 templates

Pre-built configs for GitHub, Slack, Stripe, Linear, Gmail, Notion, Jira, HubSpot, Salesforce, and more. One command installs. One command connects to Claude Code, Cursor, or Codex.

```bash
$ factorly tools import templates github
$ factorly vault set GITHUB_TOKEN ghp_xxxxxxxxxxxx
$ factorly sync
```
Your agent connects to Factorly as a single MCP server or CLI and sees every tool you've configured. Credentials never leave the vault.

### Wrap any MCP server

Already using an MCP server? Wrap it with zero config:

```bash
$ factorly wrap -- npx @modelcontextprotocol/server-github
```

Same tools, same interface. Now every call is logged, output is compressed, loops are detected, and calls are rate-limited.

### Workflows

Chain tools into governed pipelines. Steps run sequentially with variable passing, conditional branching, and state persistence. One call replaces many.

```yaml
tools:
deploy.staging:
type: workflow
steps:
- tool: make.test
store: output
- tool: make.deploy
require: "contains(output, 'PASS')"
- switch:
- condition: "contains(output, 'PASS')"
tool: slack.post
params: { text: "✓ Deployed" }
- condition: "true"
tool: slack.post
params: { text: "✗ Deploy blocked — tests failed" }
```

`if:` skips steps. `require:` stops the workflow. `switch:` branches. Full [expression language](docs/expressions.md) with comparisons, boolean logic, `contains()`, and `jsonpath()`. Every step is individually logged and governed.
---

### Oversight
## What It Does

Block destructive operations. Require confirmation before writes. Rate-limit calls. Loop detection is always on — Factorly fingerprints identical calls and blocks runaway agents after 12 repeats.
**Define** — one config, every protocol, [36 templates](docs/templates.md) included

```yaml
shadow:
deny: [delete_repository, delete_branch]
confirm: [merge_pull_request, create_release]
rate_limit: 100/hour
```
**Test** — Try tools in the [UI](docs/ui.md), see the response, iterate before giving your agent access

Built-in tools block dangerous patterns like `rm -rf`, `curl | sh`, and `DROP TABLE` out of the box. Write and delete templates ship with `confirm: true` by default.
**Compose** — [workflows](docs/workflows.md) with per-step policies, deterministic sequences

```bash
# Agent tries to run a destructive command
$ factorly call shell --command "rm -rf /"
✗ blocked: command matches deny pattern "rm -rf"
**Govern** — [vault](docs/vault.md), [policies](docs/examples/10-deny-dangerous-operations.md), [audit log](docs/logging.md). Built in, not bolted on.

# Logged and denied. The command never executed.
```
┌────────────┐ ┌────────────┐ ┌────────────┐
│ │ │ │ │ │
│ Your Agent │──────▶│ Factorly │──────▶│ Your Tools │
│ │ │ │ │ │
└────────────┘ └────────────┘ └────────────┘
Sees: Vault REST APIs
- tool names Governance CLI commands
- workflows Audit log MCP servers
- data Rate limits

### Audit trail

Every tool call logged: who called what, when, with what parameters, what was returned, what was blocked. Per-agent identity tracking for multi-agent setups.

```bash
$ factorly logs --tool github --status blocked
$ factorly logs -f # follow in real time
Never sees:
- API keys
- tokens
- credentials
```

### Output compression

Agent tools return too much data. Factorly compresses JSON, deduplicates log output, filters command-specific noise, and truncates to head + tail — saving tokens without losing signal. 27 built-in filters for common commands (git, make, npm, go, cargo, pytest, pip, docker, kubectl, terraform, and more) apply automatically. Savings tracked per-call in the audit log.
---

## Docs

- [Installation](docs/getting-started.md)
- [Getting Started](docs/getting-started.md)
- [Configuration](docs/config-reference.md)
- [CLI Reference](docs/cli-reference.md)
- [Web UI](docs/ui.md)
- [Workflows](docs/workflows.md)
- [Expressions](docs/expressions.md)
- [Output Filters](docs/filters.md)
- [OAuth Setup](docs/oauth.md)
- [Audit Logging](docs/logging.md)
- [Template Library](docs/templates.md)
- [Templates](docs/templates.md)
- [Vault](docs/vault.md)
- [OAuth](docs/oauth.md)
- [Logging](docs/logging.md)
- [OpenAPI Import](docs/openapi-import.md)
- [Examples](docs/examples/)

## License
Expand Down
6 changes: 6 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
| [Config Reference](config-reference.md) | Full YAML schema, auth types, parameters, stdin, interactive, shadow |
| [CLI Reference](cli-reference.md) | All commands, subcommands, flags, and environment variables |

## UI

| Topic | Description |
|-------|-------------|
| [Web UI](ui.md) | Built-in browser interface for browsing, testing, and managing tools |

## Features

| Topic | Description |
Expand Down
21 changes: 11 additions & 10 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

```bash
factorly serve # start MCP server (stdio)
factorly serve --http :3000 # start MCP server (HTTP at /mcp)
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)
Expand Down Expand Up @@ -257,7 +257,8 @@ factorly wrap --url http://localhost:3001/mcp
--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")
--http <addr> # serve wrapped server over HTTP (e.g. ":3000")
--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}})
Expand Down Expand Up @@ -285,7 +286,7 @@ Factorly ships with governed alternatives to common agent tools. These are avail
### Context-aware

In **stdio mode** (default): all 5 tools available.
In **HTTP mode** (`--http`): only `factorly.fetch` — local tools don't make sense on a remote server.
In **HTTP mode** (`--port`): only `factorly.fetch` — local tools don't make sense on a remote server.

### Safety guards

Expand Down Expand Up @@ -331,7 +332,7 @@ disable_builtins: true

## HTTP server authentication

When running `factorly serve --http`, you can secure the endpoint with a Bearer token. The token is resolved in order:
When running `factorly serve --port`, you can secure the endpoint with a Bearer token. The token is resolved in order:

1. `--http-token` flag
2. `FACTORLY_HTTP_TOKEN` environment variable
Expand All @@ -340,25 +341,25 @@ Both support `{{vault:KEY}}` references — the vault is opened automatically if

```bash
# Plain token (visible in ps output — use for dev only)
factorly serve --http :3000 --http-token mytoken
factorly serve --port 3000 --http-token mytoken

# Environment variable (better for CI/deployment)
FACTORLY_HTTP_TOKEN=mytoken factorly serve --http :3000
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 --http :3000 --http-token '{{vault:HTTP_TOKEN}}'
factorly serve --port 3000 --http-token '{{vault:HTTP_TOKEN}}'

# Vault ref via env var
FACTORLY_HTTP_TOKEN='{{vault:HTTP_TOKEN}}' factorly serve --http :3000
FACTORLY_HTTP_TOKEN='{{vault:HTTP_TOKEN}}' factorly serve --port 3000
```

When 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.

**Note:** If Factorly is running inside a container (Docker, devcontainer, Codespace), use the host-accessible address — not `localhost`. For Docker, this is typically `host.docker.internal`:
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`:

```bash
factorly serve --http 0.0.0.0:3000 --http-token '{{vault:HTTP_TOKEN}}'
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}}'
```

Expand Down
44 changes: 40 additions & 4 deletions docs/config-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
tools_dir: ./tools # optional, scan directory for tool files
disabled_commands: [vault, exec] # optional, block specific CLI commands
disable_builtins: true # optional, disable all factorly.* built-in tools
disabled_builtins: # optional, disable specific built-in tools
- factorly.shell
- factorly.clipboard

vault_backends: # optional, external secret managers
op: # backend name → use as {{op:KEY}}
Expand All @@ -29,7 +32,7 @@ oauth_providers: # shared across tools

tools:
<tool-name>:
type: cli | rest | mcp | workflow # required
type: cli | rest | mcp | workflow | builtin # required (builtin is internal-only)
description: "..." # optional, shown to agent

# For CLI commands:
Expand Down Expand Up @@ -85,7 +88,7 @@ tools:
required: true
- name: limit
in: query
type: integer # string (default), integer, number, boolean, json
type: integer # string (default), text, integer, number, boolean, json
default: 100 # default value if not provided by caller
min: 1 # clamp to minimum (integers and numbers)
max: 100 # clamp to maximum (integers and numbers)
Expand Down Expand Up @@ -142,6 +145,39 @@ tools:

```

## Parameter types

| Type | Description |
|------|-------------|
| `string` | Single-line text (default) |
| `text` | Multi-line text — same as string at the protocol level, but renders as a textarea in the UI |
| `integer` | Whole number — supports `min`/`max` clamping |
| `number` | Decimal number — supports `min`/`max` clamping |
| `boolean` | `true` or `false` |
| `json` | Arbitrary JSON value |

The `text` type is useful for parameters that accept long-form content like prompts, code snippets, or message bodies. It behaves identically to `string` in the MCP schema and execution — the difference is only in UI rendering (multiline textarea vs single-line input).

```yaml
tools:
summarize:
type: rest
base_url: https://api.openai.com/v1
method: POST
path: /chat/completions
auth:
type: bearer
token: "{{vault:OPENAI_KEY}}"
parameters:
- name: content
type: text # renders as textarea in the UI
required: true
description: "Text to summarize"
- name: model
type: string
default: "gpt-4o"
```

## Secret references

Use `{{env:ENV_VAR}}` for environment variables or `{{vault:KEY}}` for encrypted vault secrets. Both are resolved at startup before the agent sees anything.
Expand Down Expand Up @@ -192,7 +228,7 @@ The `type` field controls how body parameters are serialized in JSON:

| Type | JSON output | Example |
|------|------------|---------|
| `string` (default) | Quoted | `"claude-sonnet-4-20250514"` |
| `string` (default) | Quoted | `"claude-sonnet-4-6"` |
| `integer` | Unquoted number | `1024` |
| `number` | Unquoted number | `0.95` |
| `boolean` | Unquoted | `true` |
Expand Down Expand Up @@ -260,7 +296,7 @@ tools:
description: The question for Claude
required: true
- name: model
default: claude-sonnet-4-20250514
default: claude-sonnet-4-6
- name: max_tokens
default: "1024"
```
Expand Down
15 changes: 13 additions & 2 deletions docs/examples/11-require-confirmation.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,19 @@ factorly call email --to "team@acme.com" --subject "Deploy complete"
1. The agent calls a tool that matches a confirm rule.
2. **CLI mode** (`factorly call`): Factorly prompts on stderr — `⚠ Tool "x" requires confirmation. Proceed? (y/n)`. The call only executes if you type `y`.
3. **MCP mode** (`factorly serve`): Factorly uses MCP elicitation. The AI client (Claude Code, Cursor) displays the confirmation prompt in the chat UI.
4. If denied, the agent receives an error and the rejection is logged.
5. `confirm: true` guards every call on that tool. `confirm: [action1, action2]` only guards the listed sub-tools.
4. **UI mode** (`factorly ui`): Confirmation prompts appear as a modal in the browser. If an MCP agent is also connected, both the browser and the agent see the prompt — **first response wins**.
5. If no response is received within **60 seconds**, the call is automatically **denied**.
6. If denied, the agent receives an error and the rejection is logged.
7. `confirm: true` guards every call on that tool. `confirm: [action1, action2]` only guards the listed sub-tools.

### Verbose logging

With `--verbose`, Factorly logs confirmation routing:

```
[confirm] factorly.shell: waiting for approval (browser, elicitation)
[confirm] factorly.shell: approved via browser
```

---

Expand Down
Loading
Loading