Files
muxy.files gives extensions read/write access to the active project workspace — list a directory, read a file, stat, and create, write, rename, move, or delete entries. This is the API surface that replaces the old built-in file tree: build your own tree, picker, or editor on top of it.
On tabs/panels/popovers these methods return a Promise (use await); in runScript commands the same calls are synchronous and return the value directly. Every path is relative to the active worktree root (the same root the app shows for the active project). Pass { project } (a project id, name, or path) as the last argument to target a specific project; omit it to use the active one.
Paths are sandboxed to the workspace root. Any path that escapes it — via .. or a symlink pointing outside — is rejected. Pass "" or "." to list or stat the root, or to use it as a move destination. The root itself cannot be written, created, renamed, moved, or deleted.
Permissions
| Permission | Methods |
|---|---|
files:read | list, read, stat |
files:write | write, mkdir, rename, move, delete |
Every write also prompts the user for runtime consent the first time, remembered as an allow/deny rule per operation for the extension.
{
"name": "files-tree",
"version": "0.1.0",
"permissions": ["files:read", "files:write"]
}
Read methods
muxy.files.list(path, opts?)
const entries = await muxy.files.list("src");
// [{ name, path, isDirectory, isIgnored }]
Directories sort before files. path is relative to the root; pass "" or "." for the root itself. isIgnored reflects .gitignore.
muxy.files.read(path, opts?)
const file = await muxy.files.read("README.md");
// { path, content, size }
Reads UTF-8 text. Files larger than 5 MiB or non-UTF-8 content reject.
muxy.files.stat(path, opts?)
await muxy.files.stat("src/main.swift");
// { name, path, isDirectory, size }
Write methods
All writes prompt for consent on first use.
await muxy.files.write("notes/todo.md", "# Todo\n"); // overwrite/create => { path }
await muxy.files.mkdir("notes"); // => { path }
await muxy.files.rename("todo.md", "done.md"); // => { path }
await muxy.files.move(["a.txt", "b.txt"], "archive"); // => [path, path]
await muxy.files.delete(["old.log"]); // moves to Trash
writedoes not create parent directories — callmkdirfirst.writerejects UTF-8 content larger than 5 MiB.renamerejects an existing destination;movepreserves it and uniquifies the moved entry (report.txt→report 2.txt).renameandmovekeep any open editor tabs pointed at the moved files.deletemoves entries to the system Trash, not a permanent removal.
Watching for changes
Subscribe to the file.changed event (from a background script) to react when the workspace changes on disk:
muxy.events.subscribe("file.changed", ({ path, projectPath }) => {
// refresh your tree
});
Errors
A rejected promise carries a message string:
permission denied (files:read|files:write)— missing manifest permission.user denied consent for files.<op>— the write consent prompt was denied.path '…' escapes the workspace root— the path resolved outside the sandbox.project not found …— theprojectselector did not resolve.- Anything else surfaces the underlying filesystem error text.
Notes
muxy.filesis available to extension tabs, panels, popovers, andrunScriptcommands (all in-process). Background scripts getfile.changedevents but not thefiles.*calls.- The sandbox is the active worktree root; switching the active project/worktree changes what
muxy.filessees.