Muxy

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

PermissionMethods
files:readlist, read, stat
files:writewrite, 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
  • write does not create parent directories — call mkdir first.
  • write rejects UTF-8 content larger than 5 MiB.
  • rename rejects an existing destination; move preserves it and uniquifies the moved entry (report.txtreport 2.txt).
  • rename and move keep any open editor tabs pointed at the moved files.
  • delete moves 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 … — the project selector did not resolve.
  • Anything else surfaces the underlying filesystem error text.

Notes

  • muxy.files is available to extension tabs, panels, popovers, and runScript commands (all in-process). Background scripts get file.changed events but not the files.* calls.
  • The sandbox is the active worktree root; switching the active project/worktree changes what muxy.files sees.