-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig.def.zig
More file actions
476 lines (463 loc) · 24.4 KB
/
Copy pathconfig.def.zig
File metadata and controls
476 lines (463 loc) · 24.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
//! atty configuration — the Suckless config.h equivalent.
//!
//! Edit this file. Recompile. That is the entire configuration model.
//!
//! Every subsystem is a struct with per-field defaults in
//! `src/defaults.zig`. Declare only the fields you want to override —
//! the rest fall through to the type's defaults, and new fields added
//! upstream flow in automatically. `git pull` rarely conflicts here.
//!
//! Track your config outside the repo with
//! `-Dconfig=/path/to/mine.zig` (or `make CONFIG=/path/to/mine.zig build`).
const atty = @import("atty");
// ───── Modules ──────────────────────────────────────────────────────────
//
// Order = priority. Short-circuiting modules first (Guardrail). The
// default tuple is dependency-free: { guardrail, history }. The
// history module reads/writes your shell's own ~/.bash_history /
// ~/.zsh_history — no `atuin` binary required.
//
// Want atuin instead of (or alongside) history? Uncomment and edit:
//
// pub const modules = .{
// atty.modules.guardrail.configure(.{
// // .behavior is per-rule:
// // .confirm (default) press Enter again to confirm
// // .confirm_once same, then never asks again this session
// // .block banner + clear line; command can't run
// // .warn banner + forward (audit, no friction)
// // .authors restricts a rule to user-typed or llm-injected
// // commits (default = both).
// //
// // `extra_rules` PREPENDS to whatever `rules` resolves
// // to (defaults to the shipped `default_rules` unless
// // you also override `rules`). Under first-match-wins
// // your rules check first. Use this for the common
// // "I just want a couple more rules" case.
// // .extra_rules = &.{
// // .{ .name = "git-force", .match = .{ .substring = "git push --force" },
// // .reason = "force-pushing", .behavior = .confirm_once },
// // },
// //
// // `rules` REPLACES the entire default list — use when you
// // want a minimal custom policy.
// // .rules = &.{
// // // `.glob` anchors to both ends — only the literal
// // // `rm -rf /` matches, so `rm -rf /home/x` falls
// // // through to a broader rule below.
// // .{ .name = "rm-rf-root", .match = .{ .glob = "rm -rf /" },
// // .reason = "rm -rf on root", .behavior = .block },
// // .{ .name = "rm-rf-llm", .match = .{ .substring = "rm -rf" },
// // .reason = "rm -rf (llm)",
// // .authors = .{ .user = false, .llm = true },
// // .behavior = .block },
// // },
// //
// // .warning_style = atty.style.presets.danger,
// }),
// atty.modules.atuin.configure(.{
// // .suggestion_ttl_ms = 0, // 0 = fish-style, no fadeout
// // .sync_after_records = 10,
// // .sync_interval_ms = 60_000,
// // .delete_scope = .exact, // .exact / .prefix /
// // // .full_text / .fuzzy —
// // // controls Ctrl+Shift+D's
// // // reach into atuin. Default
// // // .exact uses atuin fuzzy +
// // // `^line$` anchors so only
// // // the typed line is removed.
// // .tag_llm_author = true, // opt-in: pass
// // // `--author atty:llm` on
// // // LLM-injected commits.
// // // Requires atuin v18.3+.
// // .author_tag_prefix = "atty", // shared-DB disambiguator;
// // // e.g. "ws01" if multiple
// // // hosts sync to the same
// // // atuin account.
// }),
// atty.modules.history.configure(.{}), // optional fallback after atuin
// };
//
// LLM-powered command generation. Type `#: <prompt>` + Enter and the
// module replaces the typed line with the model's response. Needs an
// OpenAI-compatible chat endpoint at $LLM_API_BASE (falls back to
// $OLLAMA_HOST + "/v1"). Optional $LLM_API_KEY for hosted services.
// `#` is the shell's comment character so a missed dispatch is a
// silent no-op, not an executed command.
//
// pub const modules = .{
// atty.modules.guardrail.configure(.{}),
// atty.modules.atuin.configure(.{}),
// atty.modules.history.configure(.{}),
// atty.modules.llm.configure(.{
// // ── Transport ──────────────────────────────────────────────
// //
// // HTTP (OpenAI-compatible /chat/completions) is the default.
// // The endpoint is discovered via $LLM_API_BASE, then
// // $OLLAMA_HOST + "/v1". Override per-config with .api_base:
// //
// // .provider = .{ .http = .{
// // .api_base = "http://localhost:11434/v1",
// // }},
// //
// // …or grab a preset constant — Claude Code variants
// // ship with model id, JSON output, and the right argv:
// //
// // .provider = atty.modules.llm.providers.claude_sonnet_4_6,
// // .provider = atty.modules.llm.providers.claude_opus_4_7,
// // .provider = atty.modules.llm.providers.claude_haiku_4_5,
// // .provider = atty.modules.llm.providers.claude_default, // CLI picks
// //
// // …or the Gemini CLI (bakes in --skip-trust so headless
// // runs don't refuse in an untrusted workspace):
// //
// // .provider = atty.modules.llm.providers.gemini_2_5_pro,
// // .provider = atty.modules.llm.providers.gemini_2_5_flash,
// //
// // …or hosted OpenAI + an OPENAI_API_KEY in env:
// //
// // .provider = atty.modules.llm.providers.openai,
// //
// // …or roll your own via the factories:
// //
// // .provider = atty.modules.llm.providers.claudeCode(.{
// // .model = "claude-sonnet-4-6",
// // .extra_argv = &.{ "--permission-mode", "acceptEdits" },
// // }),
// // .provider = atty.modules.llm.providers.geminiCli(.{
// // .model = "gemini-2.5-pro",
// // }),
// // .provider = atty.modules.llm.providers.simonwLlm(.{
// // .model = "gpt-4o-mini",
// // }),
// //
// // …or any prompt-in / text-out CLI by hand:
// //
// // .provider = .{ .subprocess = .{
// // .argv = &.{ "your-cli", "--flag" },
// // .prompt_via = .stdin,
// // .output = .raw,
// // }},
// //
// // .prefix = "#: ", // trigger; default
// // .shell = null, // null → derive from $SHELL
// // .system_prompt = "...", // override the system message
// // .with_explanation = true, // ask for + render one-line summary
// // // above the status bar
// // .context_env_vars = &.{ // env vars exposed to the model
// // "PATH_BASE", // alongside the prompt — one-line
// // "PROJECT", // "KEY=value, …" appended to the
// // }, // user message
// // // ⚠ DO NOT list secret-bearing vars
// // // here (AWS_ACCESS_KEY_ID, GH_TOKEN,
// // // anything *_TOKEN / *_KEY / *_SECRET).
// // // The values ship verbatim to the
// // // model — local Ollama is OK, hosted
// // // APIs are not.
// // .system_context = .{ // auto-detected env (default ON)
// // .enabled = true, // master switch
// // .os = true, // /etc/os-release + uname → static
// // .pwd = true, // bash's cwd via OSC 7 / subprocess
// // .git = true, // branch from <cwd>/.git/HEAD
// // }, // (works in worktrees + submodules)
// // // Suppressed entirely when atty is in
// // // incognito mode (Ctrl+Shift+I).
// // .inline_chat_rows = 10, // Alt+C panel height in rows (>=3)
// // .chat_persist_enabled = true,
// // // Persist each dialog to its own
// // // NDJSON file. ON by default.
// // .chat_persist_dir = "", // Empty → atty picks
// // // $XDG_STATE_HOME/atty/dialogs/
// // // (fallback $HOME/.local/state/atty/).
// // // Override for per-project archives.
// }),
// };
// ───── security_guard ───────────────────────────────────────────────────
//
// Pre-Enter intercept for high-risk command shapes:
// - `curl|sh` (and `wget`/`fetch` variants piped to a shell)
// - `npm install <flagged-pkg>` (tiny hardcoded bad-pkg list)
// - `bash -c "<long base64>"` payloads
//
// On match: Enter is swallowed, a banner explains what was matched, and
// the next keystroke decides: `y` allow once, `a` allow always (this
// session, in-memory only), `t` trust permanently (SHA-256 of
// category+match added to atty's in-memory cache + mirrored to the
// daemon's per-UID `commands.trusted.txt` if the daemon is reachable
// — best-effort: a daemon-less install loses the trust on atty exit),
// `B` block host forever (session, in-memory only), anything else
// cancels (Ctrl+U clears readline).
//
// Off by default — opt in by adding `atty.modules.security_guard` to
// `modules` AND setting `.enabled = true`. See
// `docs/security-guard-design.md` for the V2 sidecar (atty-guard +
// eBPF + encoder SLM) roadmap.
//
// pub const modules = .{
// atty.modules.security_guard.configure(.{ .enabled = true }),
// atty.modules.guardrail.configure(.{}),
// atty.modules.history.configure(.{}),
// };
//
// // Skip the static-pattern check in incognito mode, or point at an
// // `atty-guard` sidecar daemon:
// // atty.modules.security_guard.configure(.{
// // .enabled = true,
// // .skip_in_incognito = false, // protect EVERYWHERE; incognito
// // // only stops *recording*.
// // // Opt into the V2 atty-guard sidecar. When set + reachable,
// // // every Enter queries the daemon BEFORE in-proc patterns; any
// // // I/O error falls back to the V1 static rules and latches the
// // // session into in-proc-only mode. Build atty-guard from the
// // // `atty-guard/` Rust crate at the repo root.
// // .daemon_socket_path = "", // e.g. "/run/atty-guard/atty-guard.sock"
// // // (the post-#140 system-daemon path;
// // // user must be in the `atty` group)
// // .daemon_timeout_ms = 50, // per-classify keystroke budget
// // // Live security-profile UX (talks to atty-guard over the UDS):
// // .show_profile = true, // statusbar segment "🛡 <profile>"
// // // (polled + cached, default on)
// // .profile_switch_mode = .sudo, // what Alt+P does. Default .sudo:
// // // stages `sudo atty-guard profile
// // // set <next>` into your prompt to
// // // run (per-action sudo auth, atty
// // // never handles the password). Or
// // // .daemon (switch directly — needs
// // // root / the daemon's [profile]
// // // allow_user_switch), or .off (keep
// // // M-p).
// // .profile_poll_ms = 3000, // profile-segment refresh cadence
// // }),
// ───── Dashboard metrics (metrics_exporter) ─────────────────────────────
//
// Opt-in telemetry for the `attop` dashboard (docs/dashboard.md). Bumps
// per-session counters (commands committed today, …) and flushes them to
// the atty-guard daemon's `report_metrics` UDS on a batched tick, so the
// dashboard can show "what is atty doing for me" + a fleet view. Counts
// only — never command content. Best-effort: with no daemon it's a no-op.
//
// Incognito policy (default `.security_only`): while incognito the session
// reports its existence + the guard_* security counters but suppresses the
// productivity counters + its cwd. `.nothing` reports nothing at all;
// `.normal` reports as usual.
//
// pub const modules = .{
// atty.modules.metrics_exporter.configure(.{
// // .daemon_socket_path = "/run/atty-guard/atty-guard.sock",
// // .report_interval_ms = 5000,
// // .incognito_policy = .security_only, // .nothing | .normal
// }),
// atty.modules.guardrail.configure(.{}),
// atty.modules.history.configure(.{}),
// };
// ───── Mouse-click → editor (mouse_links) ───────────────────────────────
//
// Click any path token in compiler/grep/ls/git output to inject
// `<editor> +LINE 'path'\n` into the shell — runs through your normal
// readline + history. Detects `:LINE` and `:LINE:COL` suffixes (col is
// dropped — see `mouse_links/inject.zig` for why). Output rows are
// captured into a per-process ring; SGR + OSC sequences are stripped
// before path detection. Streaming-line model only — TUIs in the
// alt-screen (vim, htop) bypass the intercept and keep their own mouse.
//
// Two prerequisites:
// 1. `mouse.enabled = true` so atty emits the SGR-1006 DECSET trio
// and routes click events to modules.
// 2. The module added to your `modules` tuple. Default editor comes
// from `$EDITOR` / `$VISUAL`; override via `.editor`.
//
// pub const mouse: atty.Mouse = .{ .enabled = true };
//
// pub const modules = .{
// atty.modules.mouse_links.configure(.{}),
// atty.modules.guardrail.configure(.{}),
// atty.modules.history.configure(.{}),
// };
//
// // Override $EDITOR, shrink the capture ring, or restrict to absolute
// // paths only:
// // atty.modules.mouse_links.configure(.{
// // .editor = "nvim",
// // .ring_rows = 64,
// // .accept_relative = false,
// // }),
// ───── Mouse-click → browser (mouse_urls) ───────────────────────────────
//
// Click any URL in terminal output to open in `xdg-open` (or your
// configured opener) — but only if the host is in `url_whitelist`.
// Default mode is `.whitelist_only`: the paranoid posture so a
// curl'd page or hostile log line can't trigger a phishing tab on
// click. Switch to `.never` on shared / multi-tenant hosts where
// even whitelisted opens are too much surface.
//
// Same `mouse.enabled = true` prereq as mouse_links — the modules
// share the proxy's click dispatch.
//
// pub const modules = .{
// atty.modules.mouse_urls.configure(.{
// .mode = .whitelist_only,
// .url_whitelist = &.{
// "github.com",
// "*.github.com",
// "docs.zig.dev",
// },
// // .opener = "firefox", // override $BROWSER
// }),
// atty.modules.mouse_links.configure(.{}),
// atty.modules.guardrail.configure(.{}),
// atty.modules.history.configure(.{}),
// };
//
// // Interactive — `[y]es / [a]llow / [t]rust / cancel` banner on
// // click of an untrusted host. `[a]` adds to in-memory
// // session-trust (subsequent clicks fast-path); `[t]` surfaces the
// // `sudo atty-guard urls allow <host>` guidance for permanent
// // persistence (atty can't write the daemon's trust file itself
// // — `urls allow` is an EUID-0 RPC). Place `mouse_urls` BEFORE
// // `guardrail` in `modules` so the banner's `y`/`a`/`t` keystrokes
// // beat guardrail's armed-prompt consumption.
// // atty.modules.mouse_urls.configure(.{ .mode = .ask_each }),
//
// // Paranoid posture — click is always a no-op + hint:
// // atty.modules.mouse_urls.configure(.{ .mode = .never }),
// ───── Proxy ────────────────────────────────────────────────────────────
//
// pub const proxy: atty.Proxy = .{
// .tick_interval_ms = 50, // default 100
// };
// ───── Ghost overlay ────────────────────────────────────────────────────
//
// `atty.Style` is the shared styling primitive (ghost overlay,
// statusbar segments, guardrail warning, …). Presets in
// `atty.style.presets`, or write literals:
// `.{ .dim = true, .italic = true, .fg = 244 }`.
//
// pub const ghost: atty.Ghost = .{
// .style = atty.style.presets.muted_italic,
// // Multi-row pick list below the prompt. 0 disables (default);
// // 3 shows the next three matches after the inline ghost. Bound
// // to Ctrl+1..Ctrl+9 (kitty kbd) and Esc+1..Esc+9 (legacy /
// // Alt+digit fallback). Dynamic — scrolls the prompt up to make
// // room when needed, releases on line clear.
// // .list_count = 3,
// // .list_style = atty.style.presets.muted,
// };
// ───── Terminal protocol ────────────────────────────────────────────────
//
// ON by default (enable_kitty_keyboard = true). atty pushes the kitty
// keyboard protocol's `disambiguate` flag at startup so terminals that
// support it (Ghostty/kitty/foot/WezTerm/…) emit distinct CSI-u
// sequences for keys that otherwise collide with control bytes (e.g.
// Ctrl+Shift+I, Ctrl+Tab). At the shell prompt atty handles CSI-u in the
// stdin path: a matched binding fires; otherwise the sequence is
// translated back to its legacy encoding when one exists (so Ctrl+D /
// Ctrl+C / arrows reach the shell as usual), and CSI-u with no legacy
// form is dropped rather than echoed as mojibake. Once a TUI owns the
// alt-screen (vim/htop/…) atty stops matching bindings; CSI-u that has a
// legacy form is still translated, and only untranslatable CSI-u is
// passed through raw for the app to decode natively. Set to false to opt
// OUT (e.g. a terminal that mishandles the protocol); bindings that
// require it then fall back to their legacy encodings where one exists.
//
// pub const terminal: atty.Terminal = .{ .enable_kitty_keyboard = false };
// ───── Debug / feedback capture ─────────────────────────────────────────
//
// An in-memory recorder of the recent I/O window — stdin (keystrokes), the
// shell's raw output, and atty's own bytes to the terminal — kept in RAM only
// until you press Alt+Shift+D, which dumps it (plus context) to a JSON report
// under $XDG_DATA_HOME/atty/reports (or ~/.local/share/atty/reports). Nothing
// touches disk until you capture, so there's no passive log. Use it to file /
// reproduce ghost-text, LLM, or render-artifact bugs. Opt-in: recording input
// is sensitive, so it's off by default.
//
// pub const debug: atty.Debug = .{
// .enabled = true,
// .ring_bytes = 256 * 1024, // one half of the double buffer; window is 1–2×
// .report_dir = "", // "" → XDG data dir; or set an explicit path
// };
// ───── Key bindings ─────────────────────────────────────────────────────
//
// Defaults: Right / End / Ctrl+F / Ctrl+Tab → ghost_accept;
// Ctrl+Right → ghost_accept_word; Ctrl+Shift+I (+ Alt+i fallback) →
// incognito_toggle; Ctrl+Shift+D → delete_history_match; Alt+Shift+W →
// security_guard_show_warnings. The LLM module adds its own bindings
// when enabled (Alt+letter chat keys plus Ctrl+Shift+X, PageUp/Down,
// Shift+Up/Down, … — the `default_bindings` decl on the type
// `atty.modules.llm.configure(...)` returns; see src/modules/llm.zig).
// `atty.keymap.key("…")` resolves at compile time — typos error the
// build. See src/keymap.zig for supported names (Ctrl+Shift+Right,
// Alt+f, F1–F12, …). Your entries are scanned first, so an override
// wins over the default for the same key.
//
// pub const keymap: atty.Keymap = .{
// .bindings = &.{
// .{ .bytes = atty.keymap.key("Ctrl+F"), .action = .ghost_accept },
// .{ .bytes = atty.keymap.key("Alt+i"), .action = .incognito_toggle },
// },
// };
// ───── Bottom status bar ────────────────────────────────────────────────
//
// Reserves rows at the bottom of the terminal via DECSTBM. Modules can
// contribute segments via the optional `statusText` hook (joined with
// " │ "). Off by default — opt in if you want it.
//
// pub const statusbar: atty.StatusBar = .{
// .enabled = true,
// .reserve_rows = 3, // hint row + blank pad + status row (default)
// .style = atty.style.presets.muted,
// .base_text = "atty", // proxy-level prefix
// .incognito_style = .{ .dim = true, .fg = 1 }, // muted red 🔒 segment
// };
// ───── Subprocess context ──────────────────────────────────────────────────
//
// At every OSC 133 `;C` transition (requires shell integration:
// Ghostty's `shell-integration-features = osc-133`, ble.sh,
// zsh4humans, or VS Code's snippet), atty inspects the line you
// just committed. If it matches `ssh` / `mosh` / `kubectl exec` /
// `docker exec` / `lxc exec` / `incus exec` / `sudo bash|-s|-i` /
// `su`, atty pushes a frame onto an in-memory stack and tags
// subsequent recorded commits with an encoded `--cwd`. Unresolved
// path segments emit `?` rather than asserting a value we can't
// verify (atty doesn't read kubeconfig or capture local cwd via
// OSC 7 at depth==0 yet — both are known follow-ups):
//
// ssh://user@host/<remote-cwd-or-?>
// k8s://<context-or-?>/<ns-or-?>/<pod>/<remote-cwd-or-?>
// docker://<container>/<remote-cwd-or-?>
// container://<name>/<remote-cwd-or-?>
// sudo:? (today; sudo:<local-cwd> after local OSC 7 lands)
// su:? (bare su, no user)
// su:<user>:? (su with user, same TODO for local cwd)
//
// atuin's Ctrl+R `[ DIRECTORY ]` filter then scopes searches per
// remote target. Stack pops on `;D` so nested ssh chains work.
// History module ignores subprocess-typed commits — your
// `~/.bash_history` / `~/.zsh_history` stays free of unrunnable
// lines.
//
// pub const subprocess: atty.Subprocess = .{
// // Fork `ssh -G <args>` to resolve aliases / Match blocks /
// // ProxyJump from ~/.ssh/config. Disable if ssh isn't on
// // $PATH, you don't want the (typically <100ms) latency, or
// // you don't use ssh aliases.
// // .use_ssh_g = true,
// //
// // Path to ssh used for -G resolution.
// // .ssh_binary = "ssh",
// //
// // `→ ssh:user@host` segment in the status bar while
// // a recognised subprocess is active.
// // .show_in_statusbar = true,
// //
// // Style of the subprocess statusbar segment.
// // .segment_style = .{ .dim = true, .fg = 6 },
// //
// // Per-target incognito list — commands typed at matching
// // targets are dropped (same as a manual Ctrl+Shift+I). Match
// // is on the resolved frame name (e.g. "prod@bastion",
// // "prod/apps/db", "nginx", "sudo").
// // .incognito_targets = &.{
// // "prod@bastion.example.com",
// // "prod/apps/db",
// // },
// };