airlok 0.4.1 → 0.6.0

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/CHANGELOG.md CHANGED
@@ -2,6 +2,39 @@
2
2
 
3
3
  All notable changes to airlok. The format follows Keep a Changelog; versions follow SemVer.
4
4
 
5
+ ## [0.6.0] - 2026-09-12
6
+
7
+ ### Added
8
+
9
+ - MCP servers. `[[mcp]]` blocks configure servers over stdio or streamable HTTP, and their tools are offered to the model as `<server>__<tool>`, alongside the built-ins. A server starts on the first turn that can use it; one that fails to start is reported and skipped, and the run continues without it. Tools can be limited with `tools = ["name"]`, and a built-in keeps its name if a server ever claims one. The client is the official `rmcp` SDK.
10
+ - `trust` per server decides the gate: `prompt`, the default, shows the server, the tool, and the arguments and asks with the usual `[y]es / [n]o / [a]ll / [q]uit`, where `a` covers that one server for the run; `allow` never asks; `deny` keeps the server's tools from the model. `[safety] confirm_mcp` turns the prompting off wholesale, and `--yes` includes it.
11
+ - `rehydrate` per server decides what an MCP server receives. It is off by default, so secrets found in your files leave as placeholders rather than values, and the confirmation shows exactly what will be sent. The provider API key is refused in arguments either way, as it is for every tool.
12
+ - Tool descriptions and results from a server reach the model inside markers naming the server and saying the text is data, so a server cannot instruct the model or change airlok's own confirmations and deny list.
13
+ - Secrets for a server come from commands: `env_cmd` and `header_cmd` take their values from stdout, the way `api_key_cmd` does, so no token is written in the config.
14
+ - `airlok mcp list` shows each server, whether it answers, and its tools. `airlok mcp call <server> <tool> '<json>'` calls one tool through the same gates. `/mcp` does the listing in a session, and `/mcp <name>` enables a disabled server for that session.
15
+ - Plan mode offers no MCP tools, as it offers no write tools, and starts no servers.
16
+
17
+ ## [0.5.0] - 2026-09-12
18
+
19
+ ### Added
20
+
21
+ - A status line during each turn: a spinner, what airlok is doing (`thinking`, `reading src/main.rs`, `running cargo test`), the seconds so far, and the tokens used this turn, redrawn in place below the output and cleared before anything else prints. It is off when stdout is not a terminal, with `NO_COLOR`, and with `-v`.
22
+ - Esc cancels a running turn as Ctrl-C does: the partial reply is kept and marked interrupted. The terminal is in cbreak mode only while a turn runs, and is restored when the turn ends, before each confirmation, on a panic, and on SIGTERM. Keys typed during a turn start the next prompt.
23
+ - Plan mode, with `/plan` or `--plan`. The model gets only `read_file`, `glob`, `grep`, and `list_dir`, the system prompt says the rest are unavailable, and it ends its reply with a plan. `/go` runs the plan as the task in normal mode; `/plan` again leaves without running it. Per-model settings are sent unchanged in both modes.
24
+ - `!command` runs a command in the working directory and adds the command and its output to the conversation. `#note` appends a list item to `./AIRLOK.md`, creating it, and rebuilds the context block.
25
+ - `@` completes paths from the working directory, fuzzy and gitignore-aware; Tab inserts the path as plain text. Typing `/` shows the matching commands with their descriptions. A prefix runs the first match, and an unknown command suggests the closest.
26
+ - Alt+Enter inserts a newline, and so does Shift+Enter in terminals that send Esc then Enter for it.
27
+ - Write confirmations show line numbers, syntax highlighting, and +/- gutters, and page at 40 lines; `v` shows the rest.
28
+ - A dim footer after each turn: model, context %, session id.
29
+ - `[models."<id>"]` config sections with `reasoning_effort`, sent by the openai provider only for that model id. Azure's `gpt-6-astra` needs `reasoning_effort = "none"` to use tools on Chat Completions; when a provider rejects its reasoning effort, airlok names the setting to add. `/model` shows the configured effort.
30
+
31
+ ### Changed
32
+
33
+ - The REPL starts with one line: version, model, directory, notes such as a resumed session or confirmations being off, and `/help for commands`.
34
+ - Ctrl-C at an empty prompt does nothing; it used to print a hint.
35
+ - Runs of read-only tool calls show on the status line and end with one `read N files` line, in place of the updating `reading N files...` line.
36
+ - Slash commands are listed harmless and frequent first, since a prefix now runs the first match.
37
+
5
38
  ## [0.4.1] - 2026-09-11
6
39
 
7
40
  ### Added
package/README.md CHANGED
@@ -5,18 +5,23 @@ airlok is a terminal coding agent with one differentiator: it is a privacy airlo
5
5
  ## Install
6
6
 
7
7
  ```sh
8
- cargo install airlok
9
- # or
10
- npm install -g airlok
8
+ curl -fsSL https://airlok.dev/install.sh | sh
11
9
  # or
12
10
  brew install airlok-dev/tap/airlok
11
+ # or
12
+ npm install -g airlok
13
+ # or, built from source
14
+ cargo install --git https://github.com/airlok-dev/airlok airlok --locked
13
15
  ```
14
16
 
17
+ `install.sh` installs the latest release for macOS or Linux. It checks the release's installer against the sha256 digest GitHub publishes for it, then runs it; set `AIRLOK_NO_MODIFY_PATH=1` to leave your shell profile alone.
18
+
15
19
  ## Usage
16
20
 
17
21
  ```sh
18
22
  airlok # interactive session in the current directory
19
23
  airlok "create hello.txt containing hello" # run one task in the current directory
24
+ airlok --plan # start in plan mode: the model researches, then proposes a plan
20
25
  airlok --resume # continue the latest saved session here (REPL, or with a task)
21
26
  airlok --resume=<id> "summarise what we did" # continue a particular one
22
27
  airlok sessions # list saved sessions for this directory
@@ -30,30 +35,64 @@ airlok config init # write a commented config to the
30
35
  airlok config show # print the effective config and the key source
31
36
  airlok context # print the context block sent with the system prompt, after redaction
32
37
  airlok redactions # list what the redactor detects and how each kind is treated
38
+ airlok mcp list # the configured MCP servers, whether they answer, and their tools
33
39
  ```
34
40
 
35
- Every `write_file` and `edit_file` call shows a unified diff and asks `Apply? [y]es / [n]o / [a]ll / [q]uit`. Every `bash` call that is not on the allow list shows the command and asks the same way. `y` applies this one, `n` sends a rejection back to the model so it can adapt, `a` approves the rest of that kind for the run, and `q` aborts the run with a non-zero exit. Prompts are read from the terminal, not stdin, so piped input still works; without a terminal, pass `--yes` or turn the confirmations off in the config.
41
+ Every `write_file` and `edit_file` call shows a diff with line numbers, three lines of context, +/- gutters, and syntax highlighting, and asks `Apply? [y]es / [n]o / [a]ll / [q]uit`. A diff longer than 40 lines stops at `... N more lines, [v] to view all`; `v` prints the rest and asks again. Every `bash` call that is not on the allow list shows the command and asks the same way. `y` applies this one, `n` sends a rejection back to the model so it can adapt, `a` approves the rest of that kind for the run, and `q` aborts the run with a non-zero exit. Prompts are read from the terminal, not stdin, so piped input still works; without a terminal, pass `--yes` or turn the confirmations off in the config.
36
42
 
37
- Model output is rendered as markdown (headings, emphasis, lists, tables, highlighted code) when stdout is a terminal; set `NO_COLOR` or redirect stdout for plain text. Runs of read-only tool calls collapse to one `reading N files...` line; `-v` shows every call.
43
+ Model output is rendered as markdown (headings, emphasis, lists, tables, highlighted code) when stdout is a terminal; set `NO_COLOR` or redirect stdout for plain text. Runs of read-only tool calls show on the status line and end with one `read N files` line; `-v` shows every call.
38
44
 
39
- ### Sessions
45
+ ### Interactive sessions
46
+
47
+ `airlok` with no task opens a session. It starts with one line (version, model, directory, `/help for commands`), the prompt shows the model and the directory (`gpt-5.5 airlok> `), each line you send is one turn, and the conversation carries across turns. After each turn a dim footer shows the model, how full the context is, and the session id.
48
+
49
+ While a turn runs, a status line below the output shows a spinner, what airlok is doing (`thinking`, `reading src/main.rs`, `running cargo test`), the seconds so far, and the tokens used this turn. It is cleared before anything else prints. It is off when stdout is not a terminal, when `NO_COLOR` is set, and with `-v`, whose logs share the terminal.
40
50
 
41
- `airlok` with no task opens a session. The prompt shows the model and the directory (`gpt-5.5 airlok> `), each line you type is one turn, and the conversation carries across turns. Ctrl-C during a turn cancels it: the text streamed so far stays in the history marked as interrupted, tool calls that had not run are dropped, and you get the prompt back. Ctrl-D or `/exit` saves and quits.
51
+ | Key | What it does |
52
+ |---|---|
53
+ | Enter | send the line |
54
+ | Alt+Enter | start a new line in the same message. Shift+Enter does the same in terminals that send Esc then Enter for it; most send a plain Enter |
55
+ | Tab | take the first `/` command or `@` path on offer; Tab again cycles through the rest |
56
+ | Right arrow | take the rest of the `/` command shown after the cursor |
57
+ | Esc or Ctrl-C | during a turn, cancel it |
58
+ | Ctrl-C | at the prompt, clear the line; on an empty line it does nothing |
59
+ | Ctrl-D | save and quit |
60
+ | Ctrl-R | search this run's history; Up and Down step through it |
61
+
62
+ A cancelled turn keeps the text streamed so far in the history, marked as interrupted; tool calls that had not run are dropped, and you get the prompt back. Keys typed while a turn runs are kept and start the next prompt.
63
+
64
+ | Input | What it does |
65
+ |---|---|
66
+ | `/` at the start | a slash command. A menu of the matching commands and what they do shows as you type. A prefix runs the first match in the menu (`/co` is `/cost`); an unknown name gets the closest command suggested |
67
+ | `@` anywhere | `@` and part of a path offers matching files and directories from the working directory, fuzzy and gitignore-aware. Tab inserts the path as plain text, without the `@` |
68
+ | `!` at the start | runs the rest with `sh -c` in the working directory and prints the output. The command and its output go into the conversation for the model's next turn. `bash_timeout_secs` applies, and the command cannot read input |
69
+ | `#` at the start | appends the rest to `./AIRLOK.md` as a list item, creating the file, and rebuilds the context block so it applies from the next turn. A new `AIRLOK.md` takes precedence over a `CLAUDE.md` or `AGENTS.md` beside it, and the confirmation says so. Instructions are read from the repository root, so from a subdirectory the note lands in a file the context block does not read |
42
70
 
43
71
  | Command | What it does |
44
72
  |---|---|
45
73
  | `/help` | list the commands |
46
74
  | `/model [<id>]` | show the model, or use `<id>` for the rest of the session; not validated, the provider rejects a bad id on the next turn |
75
+ | `/mcp` | list the MCP servers and their tools; `/mcp <name>` enables a disabled one for this session |
76
+ | `/plan` | turn plan mode on or off |
77
+ | `/go` | carry out the plan from plan mode, back in normal mode |
47
78
  | `/provider [<name>]` | show the provider, or switch to `anthropic` or `openai` if a key is available for it; says which key is missing otherwise |
48
- | `/clear` | start a new session; the current one stays saved |
49
- | `/compact` | summarise older turns to free context |
50
79
  | `/cost` | tokens used so far, and whether they are estimates |
51
- | `/redactions` | what was redacted before leaving this machine (kind and length only) |
80
+ | `/compact` | summarise older turns to free context |
52
81
  | `/config` | the effective configuration |
53
- | `/exit` | save and quit |
82
+ | `/redactions` | what was redacted before leaving this machine (kind and length only) |
83
+ | `/clear` | start a new session; the current one stays saved |
84
+ | `/exit` | save and quit; `/quit` works too |
85
+
86
+ #### Plan mode
87
+
88
+ `/plan`, or `--plan` at startup, switches to plan mode, and the prompt shows `[plan]`. The model gets only the read-only tools (`read_file`, `glob`, `grep`, `list_dir`): `write_file`, `edit_file`, and `bash` are left out of the request, the system prompt says they are unavailable, and a call to one of them anyway is refused. The model researches the task and ends its reply with a plan. `/go` leaves plan mode and sends that plan back as the task, with every tool available again. `/plan` a second time leaves without running anything. Per-model settings such as `reasoning_effort` are sent the same way in both modes. Plan mode is not saved with the session, so `--resume` starts in normal mode unless `--plan` is given.
89
+
90
+ #### Providers in a session
54
91
 
55
92
  `[provider]` settings (`api_key_cmd`, `api_key_env`, `base_url`) belong to the configured provider. After `/provider` switches to the other one, it runs on that provider's default model and its default key variables (`ANTHROPIC_API_KEY`; `AZURE_OPENAI_API_KEY` or `OPENAI_API_KEY`); switching back restores the configured one. Both changes are written to the session file, and `--resume` continues on the session's last model when it used the configured provider and `--model` is not given.
56
93
 
94
+ ### Sessions
95
+
57
96
  Every turn, one-shot or interactive, is saved under `$XDG_DATA_HOME/airlok/sessions/<hash of the directory>/` (`~/.local/share/airlok` by default). `airlok --resume` picks up the latest session for the directory; it rebuilds the context block, so the model sees the current tree and git state, and adds a note with the resume time. `airlok sessions` lists what is there.
58
97
 
59
98
  A session file holds the plaintext conversation and the real value behind every placeholder, because that is what rehydration on resume needs. That is why the file is written with mode 0600 in a 0700 directory, and why the provider API key is the one thing left out: it is stored blank and re-read from your config on every start.
@@ -74,6 +113,49 @@ Long sessions are compacted. Once the last request used more than `compact_at` (
74
113
 
75
114
  Tool results over 50 KiB are cut with a marker telling the model to page with `read_file`'s `offset` and `limit`.
76
115
 
116
+ ## MCP servers
117
+
118
+ airlok can offer the model tools from external [MCP](https://modelcontextprotocol.io) servers. They are configured with `[[mcp]]` blocks, which merge across the user and project files like every other setting:
119
+
120
+ ```toml
121
+ [[mcp]]
122
+ name = "files" # its tools reach the model as files__<tool>
123
+ command = "npx"
124
+ args = ["-y", "@modelcontextprotocol/server-filesystem", "."]
125
+ # env = { NODE_ENV = "production" }
126
+ # env_cmd = { TOKEN = "op read op://vault/item/token" } # stdout is the value
127
+
128
+ [[mcp]]
129
+ name = "docs"
130
+ transport = "http"
131
+ url = "https://example.com/mcp"
132
+ headers = { Accept = "application/json" }
133
+ header_cmd = { Authorization = "printf 'Bearer %s' $(cat ~/.docs-token)" }
134
+ tools = ["search"] # or "all", the default
135
+ trust = "prompt" # "prompt" (default), "allow", "deny"
136
+ rehydrate = false # the default
137
+ timeout_secs = 30 # starting the server, and every call
138
+ enabled = true
139
+ ```
140
+
141
+ `env_cmd` and `header_cmd` take their values from a command's stdout, the way `api_key_cmd` does, so a token never sits in the file. `airlok config show` prints the command, never what it produced.
142
+
143
+ Servers start on the first turn that can use them, not when airlok starts. One that fails to start prints a line and is skipped, and the run continues without it. Tools are named `<server>__<tool>`; a built-in keeps its name if a server ever claims one.
144
+
145
+ `trust` decides the gate. The default, `prompt`, shows the server, the tool, and the arguments, and asks with the same `[y]es / [n]o / [a]ll / [q]uit` prompt as a shell command, where `a` approves that one server for the rest of the run. `allow` never asks. `deny` keeps a server's tools from the model, while `airlok mcp list` still shows them.
146
+
147
+ `rehydrate` decides what the server receives. By default airlok sends placeholders: a secret found in your files leaves as `<<SECRET_1>>` rather than as the value, because an MCP server is a third party in the same way the model is, and the confirmation prompt shows you exactly what will be sent. Set `rehydrate = true` for a server that genuinely needs the value. The provider API key is refused either way, as it is for every tool.
148
+
149
+ A server is untrusted input. Its tool descriptions and its results reach the model inside markers saying they are data from that server, so a description reading "ignore previous instructions" is quoted text and nothing more. Nothing a server sends changes the deny list, the confirmations, or anything else about how airlok behaves.
150
+
151
+ | Command | What it does |
152
+ |---|---|
153
+ | `airlok mcp list` | every configured server, whether it answers, and the tools it offers |
154
+ | `airlok mcp call <server> <tool> '<json>'` | call one tool with the same gates, for debugging |
155
+ | `/mcp` | in a session, the same list; `/mcp <name>` enables a disabled server for this session |
156
+
157
+ Plan mode offers no MCP tools, just as it offers no write tools, and it starts no servers.
158
+
77
159
  ## Context and AIRLOK.md
78
160
 
79
161
  Every run starts with a context block in the system prompt: the working directory, OS and shell, the git branch with `git status --short` and the last five commit subjects, a gitignore-aware file tree (four levels, at most 200 entries), and project instructions. Instructions come from `AIRLOK.md` in the repository root, or `CLAUDE.md` or `AGENTS.md` if there is no `AIRLOK.md`, followed by `~/.config/airlok/AIRLOK.md`. The block goes through the redactor like everything else, and `airlok context` prints exactly what would be sent.
@@ -123,6 +205,9 @@ max_bytes = 32768 # cap on the context block; tree is cut first, then ins
123
205
 
124
206
  [redact]
125
207
  show_secrets_in_output = false # show secrets from files in full in the terminal instead of masked
208
+
209
+ [models."gpt-6-astra"] # settings for one model id (the deployment name on Azure); none by default
210
+ reasoning_effort = "none" # openai only, sent as reasoning_effort; not validated
126
211
  ```
127
212
 
128
213
  For openai, `base_url` falls back to the `OPENAI_BASE_URL` environment variable when the config does not set it.
@@ -145,6 +230,8 @@ base_url = "https://<resource>.openai.azure.com/openai/v1"
145
230
  api_key_cmd = "az cognitiveservices account keys list -n <resource> -g <resource-group> --query key1 -o tsv"
146
231
  ```
147
232
 
233
+ Azure's `gpt-6-astra` accepts tools on Chat Completions only with reasoning off, so it needs `[models."gpt-6-astra"]` with `reasoning_effort = "none"`. Without it, airlok shows the provider's error and names that setting. The entry applies however the model is chosen, `/model gpt-6-astra` included.
234
+
148
235
  With this in the user config, plain `airlok "..."` works. The key is sent in Azure's `api-key` header, chosen from the host. No shell wrapper or exported variable is needed.
149
236
 
150
237
  ## Licence
@@ -19,7 +19,7 @@
19
19
  "hasInstallScript": true,
20
20
  "license": "MIT OR Apache-2.0",
21
21
  "name": "airlok",
22
- "version": "0.4.1"
22
+ "version": "0.6.0"
23
23
  },
24
24
  "node_modules/detect-libc": {
25
25
  "engines": {
@@ -48,5 +48,5 @@
48
48
  }
49
49
  },
50
50
  "requires": true,
51
- "version": "0.4.1"
51
+ "version": "0.6.0"
52
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "artifactDownloadUrls": [
3
- "https://github.com/airlok-dev/airlok/releases/download/v0.4.1"
3
+ "https://github.com/airlok-dev/airlok/releases/download/v0.6.0"
4
4
  ],
5
5
  "bin": {
6
6
  "airlok": "run-airlok.js"
@@ -60,7 +60,7 @@
60
60
  "zipExt": ".tar.xz"
61
61
  }
62
62
  },
63
- "version": "0.4.1",
63
+ "version": "0.6.0",
64
64
  "volta": {
65
65
  "node": "18.14.1",
66
66
  "npm": "9.5.0"