airlok 0.3.0 → 0.4.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,25 @@
2
2
 
3
3
  All notable changes to airlok. The format follows Keep a Changelog; versions follow SemVer.
4
4
 
5
+ ## [0.4.0] - 2026-09-11
6
+
7
+ ### Added
8
+
9
+ - Interactive sessions: `airlok` with no task opens a REPL with a `<model> <dir>> ` prompt, `/help`, `/clear`, `/compact`, `/cost`, `/redactions`, `/config`, and `/exit`. Ctrl-C cancels the turn in progress and keeps the partial reply marked as interrupted; Ctrl-D saves and quits.
10
+ - Sessions are saved after every turn under `$XDG_DATA_HOME/airlok/sessions/<dir hash>/` (`~/.local/share/airlok` by default), mode 0600 in a 0700 directory, with the config snapshot, provider and model, plaintext history, redaction map, token counts, and timestamps. The provider API key is stored blank. `--resume` continues the latest session for the directory (`--resume=<id>` a particular one), rebuilding the context block and adding a system note with the resume time. `airlok sessions` lists them, `sessions rm <id>` deletes one, `sessions clean --older-than 30d` prunes every directory.
11
+ - Compaction: once the last request used more than `[agent] compact_at` (0.75) of `[provider] context_window` (200k), the next turn replaces everything but the last `[agent] keep_recent_turns` (4) turns with a model-written summary, requested through the redactor with no tools. Shown as a dim `compacted: X -> Y tokens` line and recorded in the session.
12
+ - Token usage from both providers: Anthropic `message_start`/`message_delta` counts, OpenAI `stream_options.include_usage`. Where a provider reports none, requests are estimated at chars/4 and `/cost` says so.
13
+
14
+ ### Changed
15
+
16
+ - A turn that fails or is aborted no longer leaves its prompt in the history.
17
+
18
+ ## [0.3.1] - 2026-09-11
19
+
20
+ ### Security
21
+
22
+ - The provider API key could be shown in the terminal: it was an ordinary redaction entry, so when the model echoed its placeholder the display path rehydrated it. Redaction entries now have a class. `rehydrate` entries (secrets found in files and tool output) are restored into files and commands and shown masked in the terminal, with `[redact] show_secrets_in_output = true` to opt into full display. `redact-only` entries, the provider API key, are never restored anywhere: the terminal shows `[redacted: the provider API key]` and a tool call carrying the placeholder is refused. `airlok redactions` lists kinds and classes; `--show-redactions` now shows the class.
23
+
5
24
  ## [0.3.0] - 2026-09-11
6
25
 
7
26
  ### Added
package/README.md CHANGED
@@ -15,7 +15,13 @@ brew install airlok-dev/tap/airlok
15
15
  ## Usage
16
16
 
17
17
  ```sh
18
+ airlok # interactive session in the current directory
18
19
  airlok "create hello.txt containing hello" # run one task in the current directory
20
+ airlok --resume # continue the latest saved session here (REPL, or with a task)
21
+ airlok --resume=<id> "summarise what we did" # continue a particular one
22
+ airlok sessions # list saved sessions for this directory
23
+ airlok sessions rm <id> # delete one
24
+ airlok sessions clean --older-than 30d # delete old sessions from every directory
19
25
  airlok -v "..." # debug logs on stderr
20
26
  airlok -y "..." # no confirmations (prints a warning)
21
27
  airlok --provider openai --model gpt-5.5 "..." # override the provider and model for one run
@@ -23,12 +29,33 @@ airlok --show-redactions "..." # list what was redacted (kind an
23
29
  airlok config init # write a commented config to the user path
24
30
  airlok config show # print the effective config and the key source
25
31
  airlok context # print the context block sent with the system prompt, after redaction
32
+ airlok redactions # list what the redactor detects and how each kind is treated
26
33
  ```
27
34
 
28
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.
29
36
 
30
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.
31
38
 
39
+ ### Sessions
40
+
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.
42
+
43
+ | Command | What it does |
44
+ |---|---|
45
+ | `/help` | list the commands |
46
+ | `/clear` | start a new session; the current one stays saved |
47
+ | `/compact` | summarise older turns to free context |
48
+ | `/cost` | tokens used so far, and whether they are estimates |
49
+ | `/redactions` | what was redacted before leaving this machine (kind and length only) |
50
+ | `/config` | the effective configuration |
51
+ | `/exit` | save and quit |
52
+
53
+ 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.
54
+
55
+ 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.
56
+
57
+ Long sessions are compacted. Once the last request used more than `compact_at` (default 0.75) of `context_window` (default 200k tokens; set it for your model), the next turn first asks the model for a summary of everything except the last `keep_recent_turns` turns and replaces those older turns with it. The summary request goes through the redactor like every other request. You see a dim `compacted: 180k -> 22k tokens` line; `/compact` does it on demand. Token counts come from the provider; when a provider reports none, airlok estimates at chars/4 and `/cost` says so.
58
+
32
59
  ## Tools
33
60
 
34
61
  | Tool | Asks? | What it does |
@@ -72,11 +99,14 @@ model = "claude-sonnet-4-6" # per provider: anthropic "claude-sonnet-4-6", ope
72
99
  # base_url = "https://api.openai.com/v1" # openai only; Azure: "https://<resource>.openai.azure.com/openai/v1"
73
100
  # api_key_env = "ANTHROPIC_API_KEY" # env var holding the key
74
101
  # api_key_cmd = "..." # shell command whose stdout is the key
102
+ context_window = 200000 # input tokens the model accepts; used to decide when to compact
75
103
 
76
104
  [agent]
77
105
  max_turns = 50 # model round-trips per run
78
106
  max_tokens = 8192 # output tokens per model reply
79
107
  bash_timeout_secs = 120 # kill a bash tool command after this long
108
+ compact_at = 0.75 # summarise the session once a request uses this fraction of context_window
109
+ keep_recent_turns = 4 # turns kept verbatim after the summary
80
110
 
81
111
  [safety]
82
112
  confirm_writes = true # show a diff and ask before write_file / edit_file
@@ -86,10 +116,17 @@ bash_denylist = ["rm -rf", "git push --force", "sudo"]
86
116
 
87
117
  [context]
88
118
  max_bytes = 32768 # cap on the context block; tree is cut first, then instructions
119
+
120
+ [redact]
121
+ show_secrets_in_output = false # show secrets from files in full in the terminal instead of masked
89
122
  ```
90
123
 
91
124
  For openai, `base_url` falls back to the `OPENAI_BASE_URL` environment variable when the config does not set it.
92
125
 
126
+ ### What comes back, and what never does
127
+
128
+ Every placeholder belongs to one of two classes, listed by `airlok redactions`. Secrets found in your files and in tool output are `rehydrate`: the real value is restored into files and commands, so edits keep working, and shown masked in the terminal (first four characters and the length) unless `[redact] show_secrets_in_output = true`. The provider API key is `redact-only`: it is never restored anywhere. If the model echoes its placeholder it prints as `[redacted: the provider API key]`, and a tool call carrying it is refused with a message to the model. `--show-redactions` lists each placeholder's kind, length, and class, never the value.
129
+
93
130
  The key is read from exactly one place. `api_key_cmd` wins if set, then `api_key_env`, then the provider default: `ANTHROPIC_API_KEY` for anthropic, and `AZURE_OPENAI_API_KEY` then `OPENAI_API_KEY` for openai. A command runs once per process and its output is never logged. `airlok config show` prints the env var name or the command, never the value. Whatever key is in use is also added to the redactor, so it can never leave the machine inside a file or command output either.
94
131
 
95
132
  Allow-list entries match on leading tokens (`git status` allows `git status --short`, not `git push`). Deny-list entries match anywhere, in every part of a chained command, and win over the allow list. Flags are parsed rather than compared as text: the `rm -rf` entry also catches `rm -fr`, `rm -r -f`, `rm -Rf`, and `rm --recursive --force`, and `git push --force` also catches `git push -f`. A command with shell operators (`;`, `&&`, `|`, `>`, `$(`, and so on) always asks, whatever its first word.
@@ -19,7 +19,7 @@
19
19
  "hasInstallScript": true,
20
20
  "license": "MIT OR Apache-2.0",
21
21
  "name": "airlok",
22
- "version": "0.3.0"
22
+ "version": "0.4.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.3.0"
51
+ "version": "0.4.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.3.0"
3
+ "https://github.com/airlok-dev/airlok/releases/download/v0.4.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.3.0",
63
+ "version": "0.4.0",
64
64
  "volta": {
65
65
  "node": "18.14.1",
66
66
  "npm": "9.5.0"