@lllanyard/cli 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,91 @@
1
+ License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.
2
+ "Business Source License" is a trademark of MariaDB Corporation Ab.
3
+
4
+ Parameters
5
+
6
+ Licensor: Ethan Askew T/A LLLanyard
7
+ Licensed Work: LLLanyard (all versions). The Licensed Work is (c) 2026
8
+ Ethan Askew T/A LLLanyard.
9
+ Additional Use Grant: You may make production use of the Licensed Work only if
10
+ BOTH of the following are true:
11
+
12
+ (1) Integrity. You use the Licensed Work's license-key
13
+ verification and usage limits as published. You do not
14
+ remove, disable, bypass, weaken, forge or tamper with any
15
+ license-key check, signature check, or limit on keys,
16
+ agents, users, seats, tiers or features, and you do not
17
+ help anyone else do so, whether by modifying the Licensed
18
+ Work, by providing modified copies, or by distributing
19
+ license keys or tools that produce them.
20
+
21
+ (2) Entitlement. Your use is either
22
+ (a) within the free-tier limits of the version you are
23
+ using (the limits defined in packages/core/src/limits.ts
24
+ of that version, and without the shared or remote
25
+ serving, dashboard or other features that version
26
+ reserves for paid tiers); or
27
+ (b) covered by a valid, unexpired paid license key
28
+ issued to you by the Licensor, used within its terms
29
+ (including its tier and seat count).
30
+
31
+ In addition, you may not offer the Licensed Work, or a
32
+ product derived from it, to third parties on a hosted,
33
+ managed or embedded basis where it competes with paid
34
+ versions of the Licensed Work offered by the Licensor.
35
+ Using the Licensed Work for the internal purposes of your
36
+ own organization is not such an offering.
37
+
38
+ Non-production use (evaluation, testing, development,
39
+ research) is permitted by the Terms below without
40
+ restriction as to limits.
41
+ Change Date: Four years from the date the Licensed Work is published.
42
+ Change License: Apache License, Version 2.0
43
+
44
+ For information about alternative licensing arrangements for the Licensed Work,
45
+ or to buy a paid license key, please contact Ethan Askew T/A LLLanyard
46
+ (licensing contact: sales@lllanyard.co.uk).
47
+
48
+ Notice
49
+
50
+ Business Source License 1.1
51
+
52
+ Terms
53
+
54
+ The Licensor hereby grants you the right to copy, modify, create derivative
55
+ works, redistribute, and make non-production use of the Licensed Work. The
56
+ Licensor may make an Additional Use Grant, above, permitting limited production use.
57
+
58
+ Effective on the Change Date, or the fourth anniversary of the first publicly
59
+ available distribution of a specific version of the Licensed Work under this
60
+ License, whichever comes first, the Licensor hereby grants you rights under
61
+ the terms of the Change License, and the rights granted in the paragraph
62
+ above terminate.
63
+
64
+ If your use of the Licensed Work does not comply with the requirements
65
+ currently in effect as described in this License, you must purchase a
66
+ commercial license from the Licensor, its affiliated entities, or authorized
67
+ resellers, or you must refrain from using the Licensed Work.
68
+
69
+ All copies of the original and modified Licensed Work, and derivative works
70
+ of the Licensed Work, are subject to this License. This License applies
71
+ separately for each version of the Licensed Work and the Change Date may vary
72
+ for each version of the Licensed Work released by Licensor.
73
+
74
+ You must conspicuously display this License on each original or modified copy
75
+ of the Licensed Work. If you receive the Licensed Work in original or
76
+ modified form from a third party, the terms and conditions set forth in this
77
+ License apply to your use of that work.
78
+
79
+ Any use of the Licensed Work in violation of this License will automatically
80
+ terminate your rights under this License for the current and all other
81
+ versions of the Licensed Work.
82
+
83
+ This License does not grant you any right in any trademark or logo of
84
+ Licensor or its affiliates (provided that you may use a trademark or logo of
85
+ Licensor as expressly required by this License).
86
+
87
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
88
+ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
89
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
90
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
91
+ TITLE.
package/README.md CHANGED
@@ -1,3 +1,344 @@
1
- # Temporary Holding Version
1
+ # LLLanyard
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ By **Ethan Askew T/A LLLanyard**. Source-available under the Business Source License 1.1 (see [License](#license)).
4
+
5
+ Keep API keys out of AI agents. Your keys live in the **OS keychain**; tools like Claude Code, Cursor
6
+ or AutoGPT only ever hold a worthless placeholder token and talk to a local proxy that injects the real key.
7
+
8
+ ```
9
+ agent (token lly_…) ──► 127.0.0.1:3000 ──► api.openai.com / api.anthropic.com
10
+ │ verifies token, rate-limits, adds the real key from the OS keychain
11
+ └─ macOS Keychain · Windows Credential Manager (DPAPI) · Linux Secret Service
12
+ ```
13
+
14
+ ## Quick start
15
+
16
+ ```bash
17
+ npm install -g @lllanyard/cli # needs Node 22 or later; from source: npm install && npm run build && npm link -w packages/cli
18
+
19
+ lllanyard doctor # confirms the OS keychain works
20
+ lllanyard ui # or use the visual app for everything below
21
+ lllanyard add anthropic # hidden prompt – the key never touches argv/history
22
+ lllanyard add openai
23
+ lllanyard setup claude-desktop --scripts-dir ~/Scripts # Claude Desktop on a subscription: see below
24
+ lllanyard agent create claude-code # API-key mode: prints a placeholder token ONCE + the env vars to set
25
+ lllanyard start # proxy on 127.0.0.1:3000
26
+ ```
27
+
28
+ Then, in the shell that launches your tool, set the values `agent create` printed, e.g.:
29
+
30
+ ```bash
31
+ export ANTHROPIC_BASE_URL=http://127.0.0.1:3000/anthropic
32
+ export ANTHROPIC_API_KEY=lly_... # placeholder, useless outside the proxy
33
+ export OPENAI_BASE_URL=http://127.0.0.1:3000/openai/v1
34
+ export OPENAI_API_KEY=lly_...
35
+ ```
36
+
37
+ Compatibility notes:
38
+ - Claude Code reads its base URL and key from these environment variables; if `ANTHROPIC_AUTH_TOKEN` is also set it may take precedence, so unset it. Only API-key mode is proxied, not subscription logins. The Claude Desktop app ignores these variables; for Desktop (and subscription use generally) see [Claude Desktop](#3-claude-desktop-and-subscription-chat-apps) below.
39
+ - Cursor makes some model calls from its own servers, so a localhost base URL may not work there.
40
+
41
+ ## Commands
42
+
43
+ | Command | What it does |
44
+ |---|---|
45
+ | `add <provider> [--label N] [--rotate-days N] [--env-name VAR]` | Store a key (hidden prompt or `--stdin`). `--label` doubles as a *client* name for agency/freelancer use. |
46
+ | `env <provider>[:label] VAR` | The environment variable a key is given to scripts as, e.g. `lllanyard env trmm TRMM_API_KEY`. |
47
+ | `exec <command> [args…]` | Run a script with the keys it uses: `lllanyard exec pwsh -File ./Onboard-Client.ps1 -DryRun`. LLLanyard reads the script, and the helper files it loads (quoted paths, `source`/`.` lines, Python imports), for your keys' variable names and passes only those, for that run only. Keys in the output are redacted; each run is logged and shown by `usage`. `exec --keys trmm,itflow -- <command>` names the keys yourself (needed for programs that aren't scripts). Run from a terminal, the script gets a real terminal of its own (prompts like `Read-Host`, colours and window size work) on macOS, Linux and Windows, and its output is still redacted. |
48
+ | `list`, `remove`, `rotate`, `status` | Manage keys; `status` shows rotation due dates and exits 1 if anything is overdue (cron/CI friendly). |
49
+ | `agent create <name> [--client N] [--providers a,b]` / `list` / `revoke` | Placeholder tokens. `--client acme` pins an agent to the `acme` key; `--providers` limits which APIs it may reach (least privilege). |
50
+ | `provider add <id> --upstream https://… [--auth bearer\|header --header NAME --prefix TXT]` / `list` / `remove` | Teach the proxy a new API host (GitHub, Slack, your own service). Built-ins: openai, anthropic, gemini, openrouter, github, slack, stripe, notion. |
51
+ | `tool add <name> --env VAR=provider[:label] [--allow-host H] [--kill-on-leak] [--pass-env V] [--inherit-env] [--no-scrub] -- <command> [args…]` / `list` / `remove` | Approve a command (e.g. an MCP server) to receive keys as environment variables, with the safeguards below. |
52
+ | `run <name> [-- args…]` | Run an approved tool with its keys injected and scrubbed from its output. Extra args are appended only if the tool was approved with `--allow-args`. For script tools (python, node, sh, pwsh…) the script file is pinned by hash at approval and `run` refuses if it has changed since; `--allow-args` is refused for bare interpreters (`python3 -c`, `-m`) and launchers (`env`, `xargs`, `sudo`, `npx`). `run --env … -- <cmd>` is a one-off that needs a human to confirm at a terminal. |
53
+ | `scan [paths] [--import --apply [--redact]]` | Find plaintext keys in `.env` files, MCP/IDE configs and shell profiles, then move them into the keychain. Dry run unless `--apply`. |
54
+ | `backup [--out FILE] [--passphrase-file F]` | Write one encrypted file (AES-256-GCM, scrypt passphrase) holding your keys and their metadata, custom providers, approved tools, agents and license. Agents are saved as token hashes, so existing agent tokens keep working after a restore. |
55
+ | `restore <file> [--overwrite] [--dry-run] [--passphrase-file F]` | Merge a backup into this machine's keychain or vault. Every item is validated and the key limit checked before anything is written; existing items are kept unless `--overwrite`, and nothing is ever deleted. |
56
+ | `start [--port N]` | Run the proxy (loopback only). Logs JSON lines to stdout. |
57
+
58
+ ## Using it with tools (APIs and MCP servers)
59
+
60
+ Two routes, depending on how the tool talks:
61
+
62
+ **1. HTTP APIs go through the proxy** (the tool only holds the placeholder token):
63
+
64
+ ```bash
65
+ lllanyard add github # hidden prompt
66
+ lllanyard agent create reviewer --providers github,anthropic
67
+ # point the tool's API base URL at http://127.0.0.1:3000/github and use the lly_ token as its key
68
+ lllanyard provider add acme --upstream https://api.acme.dev --auth header --header X-Api-Key # your own API
69
+ ```
70
+
71
+ **2. MCP servers and CLI tools read keys from environment variables**, so the proxy can't help. Approve the command once, then
72
+ reference it by name so no secret ever appears in the MCP config file:
73
+
74
+ ```bash
75
+ lllanyard tool add github-mcp --env GITHUB_PERSONAL_ACCESS_TOKEN=github \\
76
+ --allow-host api.github.com --allow-host registry.npmjs.org --kill-on-leak \\
77
+ -- npx -y @modelcontextprotocol/server-github
78
+ # MCP client config (Claude Desktop / Claude Code / Cursor style):
79
+ # { "command": "lllanyard", "args": ["run", "github-mcp"] }
80
+ ```
81
+
82
+ **3. Claude Desktop and Claude Code on a subscription.** These sign in to an account, not an API key, so there is no API
83
+ call to proxy and no extra API billing. Connect them once, from the command line or from the app's Scripts tab
84
+ ("Connect AI apps"):
85
+
86
+ ```bash
87
+ lllanyard setup claude-desktop --scripts-dir ~/Scripts # --restart quits and reopens Claude Desktop if it's open (macOS)
88
+ lllanyard setup claude-code # adds a short section to ~/.claude/CLAUDE.md
89
+ lllanyard setup # shows what's connected; --remove undoes either
90
+ ```
91
+
92
+ Claude Desktop rewrites its config file when it quits, so a change made while it's open is lost. `setup` therefore only
93
+ changes it while the app is closed: on macOS it offers to quit Claude Desktop, make the change and reopen it (`--restart`
94
+ does this without asking); on Windows and Linux, quit it first (including from the system tray).
95
+
96
+ `setup claude-desktop` adds LLLanyard's MCP server to `claude_desktop_config.json` (macOS `~/Library/Application Support/Claude/`,
97
+ Windows `%APPDATA%\Claude\`, Linux `~/.config/Claude/`). It keeps your other settings, saves the old file as
98
+ `claude_desktop_config.json.lllanyard-backup`, and uses absolute paths plus your current `PATH`, since desktop apps start
99
+ MCP servers with a minimal one. If you move or reinstall Node or LLLanyard, run it again (`setup` warns when the paths are
100
+ stale). `setup claude-code` adds a marked section telling Claude Code to run scripts with `lllanyard exec` and never read
101
+ keys from `.env` files. It leaves the rest of the file alone.
102
+
103
+ In Claude Desktop, Claude then sees `list_scripts` and `run_script` for tools you approved with `lllanyard tool add`:
104
+ the script runs with its keys, the output (keys redacted) and exit code go back to Claude, and the run is logged
105
+ (`lllanyard usage`). It can't run anything you haven't approved, can't change the command, and only adds arguments
106
+ where you said `--allow-args`. Scripts get no input, so ones that prompt (`Read-Host`) need a non-interactive switch.
107
+ Runs stop after 10 minutes.
108
+
109
+ With `--scripts-dir DIR` (repeatable) you don't have to approve scripts one by one. Claude also gets
110
+ `run_file`, which can run any `.ps1`, `.py`, `.sh`, `.zsh` or `.js` file in those folders, but only after **you** click
111
+ Run in a dialog LLLanyard shows (Deny is the default; it gives up after 2 minutes). The dialog shows the script, its
112
+ arguments, the keys it will get (only the ones it uses, as with `exec`), and a warning if the script or a helper changed
113
+ in the last day, since an AI may have written it. The files are hashed before the dialog and checked again at launch,
114
+ so a script edited while you decide isn't run. Symlinks out of the folder, your home directory and `/` are refused.
115
+ Dialogs: macOS built in, Windows built in, Linux needs `zenity`; with no dialog, nothing runs.
116
+
117
+ Why "approve once": if `run` could inject any key into any command, an agent that can edit your MCP config could point it at
118
+ `curl https://evil` and walk off with the key. Approved tools live in the keychain, with the command pinned to its absolute
119
+ path, so editing a file can't change what receives a key. Custom providers are stored in the keychain for the same reason.
120
+
121
+ ### Leak safeguards for tool processes
122
+
123
+ A tool started with `run` holds its key, so LLLanyard puts guard rails around it:
124
+
125
+ | Safeguard | What it does | Default |
126
+ |---|---|---|
127
+ | Minimal environment | The tool inherits only basics (PATH, HOME, temp dirs, locale, CA settings), so it can't see your *other* API keys or cloud credentials. `--pass-env VAR` adds specific variables; `--inherit-env` restores everything. | on |
128
+ | Output scrubbing | Removes the key from the tool's stdout/stderr, including base64, base64url, hex and URL-encoded copies. Streams, so MCP's newline-delimited stdio never stalls. | on |
129
+ | Leak alert / kill | The first time the tool prints its key you get a warning on stderr; with `--kill-on-leak` the tool is stopped at once (exit code 70). A tool echoing its own token is a strong sign it is buggy or compromised. | warn |
130
+ | Egress allowlist | `--allow-host api.github.com` (repeatable, `*.example.com` wildcards) starts a loopback proxy protected by a per-run password; the tool's proxy variables point at it, so it can only open HTTPS tunnels to listed hosts on public IPs. Denied attempts are logged. | off |
131
+ | Clean shutdown | Signals are forwarded and the tool is killed if LLLANYARD exits. Keys go only into the child's environment, never the parent's. | on |
132
+ | Pinned command | The command is stored as an absolute path in the keychain, so a PATH swap or config edit can't change what receives the key. | on |
133
+
134
+ ### Enforced isolation: `--sandbox`
135
+
136
+ Without a sandbox the safeguards above are advisory: a malicious tool can ignore proxy settings. `--sandbox` makes them
137
+ OS-enforced:
138
+
139
+ ```
140
+ lllanyard tool add github-mcp --sandbox --allow-host api.github.com --allow-host registry.npmjs.org \
141
+ --env GITHUB_PERSONAL_ACCESS_TOKEN=github -- npx -y @modelcontextprotocol/server-github
142
+ lllanyard doctor # reports whether a sandbox works on this machine
143
+ ```
144
+
145
+ | | Linux (bubblewrap) | macOS (Seatbelt `sandbox-exec`) |
146
+ |---|---|---|
147
+ | Network | No network at all. The only way out is a unix socket to the host egress proxy (via a tiny in-sandbox bridge), which allows only `--allow-host` hosts. DNS is resolved on the host. | Denied, except loopback to the egress proxy port |
148
+ | Home directory | Replaced by an empty tmpfs: no `~/.ssh`, `~/.aws`, browser profiles, shell history | Reads under `$HOME` denied except the project and shares |
149
+ | Writes | Only the current folder (and `--share-rw`); `/tmp` is private | Only the current folder, `--share-rw` and temp |
150
+ | Your keychain | No D-Bus/Secret Service socket, no `/run`; fresh `/proc` shows no other processes, so the parent's environment can't be read | Keychain services blocked (`SecurityServer`, `securityd`) |
151
+ | Other | `--unshare-all`, all capabilities dropped, dies with LLLANYARD, `--new-session` when non-interactive | |
152
+
153
+ - **Fails closed.** Before each run a throwaway sandbox is started; if it can't start, the tool is **not** run (no silent
154
+ fallback). A tool with `--sandbox` and no `--allow-host` gets no network.
155
+ - Run it from the project folder: only the current directory is writable, and running from your home directory is refused.
156
+ Credential folders (`~/.ssh`, `~/.aws`, `~/.gnupg`, ...) cannot be shared even with `--share-ro/--share-rw`.
157
+ - Linux needs `sudo apt install bubblewrap`. Ubuntu 24.04+ restricts unprivileged user namespaces; `doctor` explains the fix.
158
+ - **Windows:** no sandbox. Use WSL2 (Debian/Ubuntu) to get the Linux one.
159
+ - **Beta.** The sandbox is tested in CI on Linux and macOS, but it is the newest part of LLLanyard. Run `lllanyard doctor`
160
+ first.
161
+
162
+ Honest limits:
163
+
164
+ - **Without `--sandbox`, the egress allowlist is best-effort.** It constrains tools that honour proxy settings (most HTTP
165
+ clients; recent Node via `NODE_USE_ENV_PROXY`, which may print an "experimental" notice). Tools launched with `npx` also
166
+ need `registry.npmjs.org` allowed; pre-installing the package is better.
167
+ - A sandboxed tool still holds its own key and can send it to any allowlisted host, so allow only hosts you trust.
168
+ - **The key is still in the tool's memory and environment**; unsandboxed, it is readable by your OS user (`/proc/<pid>/environ`, Task Manager).
169
+ - **Scrubbing covers UTF-8 text.** Binary output, or a tool that splits or otherwise encodes the key (encryption, custom
170
+ alphabets), gets through. `--no-scrub` exists for binary tools.
171
+ - If LLLANYARD itself is force-killed (SIGKILL), the tool can be orphaned; a normal exit or Ctrl+C stops it.
172
+
173
+ Use `run` for tools you trust, and give each tool the narrowest key you can (a read-only GitHub token, not your admin one).
174
+
175
+ Env: `LLLANYARD_PORT`, `LLLANYARD_RPM` (requests/min per agent, default 120), `LLLANYARD_ALLOW_ORIGIN=1` (allow Electron-style requests that carry an Origin header).
176
+
177
+ ## Security model
178
+
179
+ **Protects against:** keys sitting in `.env`/config files an agent can read; keys leaking into prompts, logs, screenshots or
180
+ git; prompt-injected agents echoing their "key" (it is only a placeholder); runaway request loops (rate limit + kill switch).
181
+
182
+ **Does NOT protect against:** malicious code running *as your OS user*. A fully compromised agent that can run shell
183
+ commands can ask the OS keychain for the key directly (macOS prompts; Windows and Linux generally do not). For
184
+ untrusted agents, run the proxy under a separate OS user or on another machine (see [Sharing with a team](#sharing-with-a-team-pro)).
185
+
186
+ Built-in hardening:
187
+ - Zero runtime dependencies in the proxy (Node built-ins only); the only native dependency is `@napi-rs/keyring`.
188
+ - Binds to `127.0.0.1` only; rejects non-loopback `Host` headers (DNS rebinding) and browser `Origin` requests.
189
+ - Agent tokens are 256-bit, stored only as SHA-256 hashes, compared in constant time.
190
+ - Upstream host is fixed per provider; request paths can't change it; redirects are never followed.
191
+ - Cookies are stripped both ways; logs contain agent/provider/status only (no keys, bodies or query strings).
192
+ - No command ever prints a stored key. `scan` masks keys, and findings hold plaintext in a private field that is
193
+ excluded from JSON/inspect output. `--redact` edits atomically and writes **no backup** (a backup is another plaintext copy).
194
+ - Keys given on the command line are refused by default (`--allow-argv` overrides).
195
+ - Custom providers must use https and a public hostname (no IPs, no credentials in the URL). The proxy also refuses to connect
196
+ to private, loopback, link-local (cloud metadata) or CGNAT addresses, including hostnames that resolve to them, unless a
197
+ provider was explicitly created with `--allow-local` for a loopback server.
198
+ - Built-in provider names can't be redefined; names like `constructor` are never treated as providers.
199
+ - Agents created with `--providers` get a 403 for anything else.
200
+ - `run`: keys go only into the child's environment (never the parent's), blocked variables (`LD_*`, `DYLD_*`, `PATH`,
201
+ `NODE_OPTIONS`...) are refused, and on Windows `.cmd` shims (npx) run through a shell only if every argument is plain.
202
+ - Limits: JavaScript cannot wipe strings from memory; the proxy caches a key for 30 s. Revoking an agent takes ≤10 s to apply.
203
+
204
+ Keys that sat in plaintext should be treated as exposed: after `scan --import`, create new keys at the provider and run `rotate`.
205
+
206
+ ## Platform notes
207
+
208
+ - **macOS / Windows:** work out of the box (Keychain / Credential Manager).
209
+ - **Debian/Ubuntu:** needs a Secret Service: `sudo apt install gnome-keyring libsecret-1-0`, inside a desktop/D-Bus session.
210
+ Headless servers are not supported yet.
211
+
212
+ ## Tiers
213
+
214
+ Free: up to 10 keys and 5 agent tokens, local only. Pro and Team ([pricing](https://lllanyard.app/pricing)) raise the key
215
+ limit and add the shared proxy for a team and the usage dashboard.
216
+
217
+ ### Desktop app and rotation assistant
218
+
219
+ `lllanyard ui` opens a private local page in your browser (`apps/desktop` wraps the same page in a native Tauri window):
220
+ keys with rotation badges, add/replace/remove (keys are write-only: only the last 4 characters are ever shown), agent
221
+ tokens, usage, proxy start/stop, license, and the **rotation assistant**.
222
+
223
+ - The assistant gets **metadata only** (provider, label, age, due date, request counts), never a key, and only talks to a model
224
+ **on this computer** (Ollama at `127.0.0.1:11434` by default, or an OpenAI-compatible local server such as LM Studio or
225
+ llama.cpp). A non-localhost endpoint is refused. With no model running, "Quick review" still gives a rule-based plan.
226
+ - The UI server listens on loopback only and needs a random per-run session secret (in the link's `#` fragment, removed from the
227
+ address bar on load) on every API call. It also checks the Host header (DNS rebinding), refuses foreign Origins and
228
+ cross-site fetches, requires JSON for writes, caps request size, and serves a strict Content-Security-Policy with a per-run
229
+ nonce. All text is inserted with `textContent`, never as HTML.
230
+ - Honest limits: any program running as your user could still read the session secret from the process list or the printed
231
+ link, as it could read your keychain; this protects against web pages and other websites, not against local malware.
232
+
233
+ ### MCP server for agents
234
+
235
+ `lllanyard mcp` is a read-only [MCP](https://modelcontextprotocol.io) server over stdio. Add it to Claude Code, Cursor, etc.:
236
+
237
+ ```json
238
+ { "mcpServers": { "lllanyard": { "command": "lllanyard", "args": ["mcp"] } } }
239
+ ```
240
+
241
+ Tools: `list_providers`, `key_status` (age and rotation due dates), `usage_summary`, `how_to_connect`. It can answer "is my
242
+ OpenAI key due for rotation?" or "what is the base URL for GitHub?" and cannot return a key, a token or any hint of one
243
+ (not even the last 4 characters), nor change anything. `--client acme` limits it to one client's keys. With the file vault the
244
+ passphrase must come from `LLLANYARD_VAULT_PASSPHRASE_FILE` (no prompt is possible on stdio). Note that anything an agent can
245
+ run as your user can also run `lllanyard` directly; the MCP server is a safe *information* channel, not an access boundary.
246
+
247
+ ### Sharing with a team (Pro)
248
+
249
+ One computer (a small server, or an always-on workstation) holds the real keys; teammates get their own agent tokens.
250
+
251
+ ```
252
+ lllanyard serve --host 0.0.0.0 --port 3443 --tls-cert cert.pem --tls-key key.pem --allow-host keys.example.com
253
+ lllanyard agent create alice-laptop --client acme --providers openai --expires 90d
254
+ ```
255
+
256
+ Teammates point their tools at `https://keys.example.com:3443/openai/v1` and use their token as the API key. They can
257
+ *use* a key but never read it. Revoking a token cuts that person off within ~10 seconds.
258
+
259
+ | Safeguard | |
260
+ |---|---|
261
+ | TLS required | Non-loopback binds refuse to start without `--tls-cert/--tls-key` (TLS 1.2+); the key file must be `chmod 600` |
262
+ | Host allowlist | Only the names given with `--allow-host` (plus loopback) are accepted: DNS-rebinding protection stays on |
263
+ | Brute-force lockout | 10 bad tokens in 10 minutes locks that address out for 15 minutes, even for a valid token |
264
+ | Per-person scope | Each token is limited to a client (`--client`), optionally to providers, and can expire (`--expires`) |
265
+ | Audit trail | Every request is logged with agent name and caller address; `lllanyard usage` breaks it down by agent and client |
266
+ | Rate limits + kill switch | Per token, as before |
267
+ | Seats | Free: 5 agent tokens. Licensed: 3 tokens per seat |
268
+
269
+ Put it behind a VPN (Tailscale, WireGuard) or a firewall if you can; the lockout and TLS are a second layer, not a reason to
270
+ expose it to the whole internet. `X-Forwarded-For` is deliberately ignored, so a reverse proxy in front shows as one address.
271
+
272
+ **Headless servers and containers: the encrypted file vault.** The OS keychain needs a desktop login, so on a server use
273
+ `LLLANYARD_BACKEND=file`. Everything (keys, agents, tools, license) then lives in one encrypted file,
274
+ `~/.lllanyard/vault.enc` (override with `LLLANYARD_VAULT_PATH`): AES-256-GCM, key from your passphrase via scrypt, atomic
275
+ writes, safe with several processes. Wrong passphrases and edited files are detected.
276
+
277
+ ```
278
+ echo 'a long random passphrase' > /etc/lllanyard/pass && chmod 600 /etc/lllanyard/pass
279
+ export LLLANYARD_BACKEND=file LLLANYARD_VAULT_PASSPHRASE_FILE=/etc/lllanyard/pass
280
+ lllanyard add openai --stdin < key.txt && lllanyard serve --host 0.0.0.0 ...
281
+ ```
282
+
283
+ On a terminal with no passphrase file you are prompted instead (hidden). Under systemd use `LoadCredential=` and point
284
+ `LLLANYARD_VAULT_PASSPHRASE_FILE` at `$CREDENTIALS_DIRECTORY/pass`. Honest limit: it protects the vault at rest (backups,
285
+ disk images, other users); someone who can read both the vault and the passphrase file as your user can open it. The
286
+ passphrase can't be changed once the vault is created.
287
+
288
+ ### Usage and dashboard
289
+
290
+ The proxy keeps a metadata-only request log (`~/.lllanyard/usage.jsonl`, owner-only, rotates at 10 MB; no keys, bodies or
291
+ query strings). Blocked egress attempts from sandboxed tools are added too.
292
+
293
+ ```
294
+ lllanyard usage --since 7d # free: requests, errors, 429s, kill-switch trips, p50/p95 by provider/agent/client
295
+ lllanyard dashboard # Pro: self-contained HTML page (no scripts, no network requests)
296
+ ```
297
+
298
+ ### Licenses
299
+
300
+ Buy Pro or Team at [lllanyard.app](https://lllanyard.app/pricing). Paid tiers are unlocked by a signed license key that
301
+ is verified **offline** (Ed25519):
302
+
303
+ ```
304
+ lllanyard license activate lly1.… # or --key lly1.…, or --stdin
305
+ lllanyard license status
306
+ lllanyard license refresh # fetch the renewed key after your subscription renews
307
+ ```
308
+
309
+ The only time LLLanyard contacts anything of ours: in the last 7 days before a paid license runs out, `start` and `ui`
310
+ send that license (nothing else) to lllanyard.app to fetch its renewed key. Set `LLLANYARD_NO_LICENSE_REFRESH=1` to turn
311
+ this off; each renewed key is also emailed.
312
+
313
+ Pro lifts the key cap to 250, Team to 500 (`TIER_LIMITS` in `packages/core/src/license.ts`). An expired license drops
314
+ back to free: existing keys keep working, you just can't add past the free cap.
315
+
316
+ **Honest limit:** a local check can be patched in private. The license (see License) forbids it and makes such use
317
+ unlicensed.
318
+
319
+ ## License
320
+
321
+ LLLANYARD is source-available under the **Business Source License 1.1** (`LICENSE.md`), licensor **Ethan Askew T/A LLLanyard**.
322
+ In plain words (the license text is what counts):
323
+
324
+ - You can read, copy, modify and **evaluate or develop** with it freely.
325
+ - **Production use** is free within the free tier (10 keys, 5 agent tokens, local only). Beyond that you need a paid license key
326
+ from Ethan Askew T/A LLLanyard.
327
+ - You **may not remove, bypass or tamper with the license-key checks or usage limits**, or help others do so. Doing it makes
328
+ your use unlicensed, and the license terminates.
329
+ - You may not resell it as a hosted/embedded competing product.
330
+ - Each version converts to Apache 2.0 four years after its release.
331
+
332
+ Licensing questions: sales@lllanyard.co.uk.
333
+
334
+ ## Verifying on your machine
335
+
336
+ `node tools/smoke.mjs` (add `--keychain` to also test your real OS keychain once) runs the real CLI, proxy, UI and MCP server
337
+ against a throwaway vault and prints PASS/FAIL plus a one-line report for bug reports.
338
+
339
+ ## Tests
340
+
341
+ `npm test` runs the suite (tsx + node:test): storage, tokens, scanner and redaction, rotation, provider and tool validation,
342
+ the output scrubber, the `run` engine with real child processes, the egress proxy, the sandbox, and proxy integration tests
343
+ against a fake upstream. CI (`.github/workflows/ci.yml`) runs it on Linux, macOS and Windows with Node 22 and 24, with a full
344
+ type-check and `npm audit`.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};