Skip to content
fswolf edited this page Oct 14, 2026 · 5 revisions

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, ~/Library on a Mac and AppData on 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
  • .env and .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, so tokenizer.json beside 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.

Outside content

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.

Running commands

> "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.

Clone this wiki locally