diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-30 23:04:26 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:17 -0300 |
| commit | 074f113fa95a875a6bf17196f8e46e69806a247c (patch) | |
| tree | 6ad2c8c3894c6f52a759dabd8850f1a7b6574331 /.agents | |
| parent | fb3f5ab5a7758a628a6e343f24a6510a58802599 (diff) | |
| download | pardes-074f113fa95a875a6bf17196f8e46e69806a247c.tar.gz pardes-074f113fa95a875a6bf17196f8e46e69806a247c.zip | |
Scripting over 9P has one chapter, and the served README and the 9P skill shrink to a summary, the traps and a pointer to it
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to '.agents')
| -rw-r--r-- | .agents/skills/pardes-9p/SKILL.md | 185 |
1 files changed, 23 insertions, 162 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md index 83675b74..51e4f2f7 100644 --- a/.agents/skills/pardes-9p/SKILL.md +++ b/.agents/skills/pardes-9p/SKILL.md @@ -6,8 +6,12 @@ description: Inspect and drive a running Pardes editor through its 9P control fi # Pardes over 9P Pardes is files: `cat`, `echo >`, `ls` and shell scripts are the whole -interface. [docs/fs.md](../../../docs/fs.md) is the reference for every -file; this page is recipes. Paths are relative to the repository root. +interface. The recipes are in the Scripting chapter +([docs/typ/scripting.typ](../../../docs/typ/scripting.typ)), every file's +semantics in the Reference chapter +([docs/typ/reference.typ](../../../docs/typ/reference.typ)), and a running +session serves a summary at `$m/README`. Paths are relative to the +repository root. ## Find the session @@ -21,167 +25,24 @@ cat "$m/index" the session socket of the pane you run in, `$PARDES_PANE` that pane's serial. Under `9ns --unix SOCK -- cmd` the session is the mount itself: `m=$NINE_MOUNT`. In a `Tty9p` shell: `m=$PARDES_MOUNT`. A dead session's -entry answers `Input/output error`: name the session, never glob. A running -session serves the binary that started it. - -## Look around - -```sh -cat "$m/index" # serial kind dirty name column, a pane a line -cat "$m/layout" # columns, then `active <serial>` -cat "$m/focus" # the pane with the keyboard -cat "$m/README" # the one-screen guide -cat "$m/commands" # every builtin and where it goes -``` - -Parse an index row with `rsplit(maxsplit=1)` first: names may hold blanks. -Re-read the index after anything that opens or closes panes. - -## Open a file, go to a place - -```sh -echo "$PWD/main.zig:120" > "$m/look"; cat "$m/look" # serial it landed in -echo "main.zig:0/fn main/" > "$m/look" # first match; without 0, the next after dot -``` - -A miss opens nothing, reads back empty and logs an `err <serial> look: ...` -line; the write itself succeeds. `L:C` columns are bytes. - -## Make, fill, name and save a pane - -```sh -n=$(cat "$m/pane/new") # each open makes one: read it once -printf 'hello\n' > "$m/pane/$n/body" # > replaces the text -printf 'more\n' >> "$m/pane/$n/body" # >> appends -echo "$PWD/notes.txt" > "$m/pane/$n/name" -echo Save > "$m/pane/$n/ctl" -rmdir "$m/pane/$n" # close it, saved or not -``` - -## Edit text - -```sh -p=$m/pane/$n -echo 'Edit ,x/foo/c/bar/' > $p/ctl # sam edit, one undo step -echo '/old/' > $p/addr; printf 'new' > $p/data # replace the next match -echo 0 > $p/addr # back to the top -echo '3' > $p/addr; : > $p/data # delete line 3 -cp $p/addr $p/dot; cat $p/sel # select it, read the selection -``` - -`addr` searches on from the last address (it is the pane's, shared by all -clients); after a `data` write it sits just past the text, so write `addr` -before each replacement. A failed `addr` leaves none and `data` refuses: -check the write's status. An Edit block of several lines goes in one open: -`printf 'Edit ,x/foo/{\ni/</\na/>/\n}\n' > $p/ctl`. - -## Run a command - -```sh -echo 'make test' > "$m/exec"; c=$(cat "$m/exec") # a command pane; its serial -grep "^exit $c " "$m/log" | tail -1 # `exit <serial> <N>` once done -``` - -At a terminal's prompt, run and get the status and output in one go: - -```sh -t=$(awk '$2=="term"{print $1; exit}' "$m/index") -exec 3<>"$m/pane/$t/pty/run"; echo 'ls' >&3; cat <&3; exec 3<&- # `exit N`, then output -printf 'q' > "$m/pane/$t/pty/data" # raw keystrokes (\r Enter, \x03 Ctrl-C) -echo 'sig INT' > "$m/pane/$t/pty/ctl" # interrupt -``` - -`busy: <program> is running` means the prompt is not free: a program holds -the terminal, so talk to it through `pty/data`. - -Ask the language server at a file pane's cursor (`addr` then `dot=addr`): - -```sh -echo '/myFunc/' > "$m/pane/$n/addr"; echo 'dot=addr' > "$m/pane/$n/ctl" -echo Hover > "$m/pane/$n/exec" # +Hover; Rename new, Diagnostics, Symbols -echo Lspwhy > "$m/pane/$n/exec" # +Lsp: why the last query found what it did -echo Lspinfo > "$m/pane/$n/exec" # which server, and its state -``` - -## Follow what happens - -```sh -exec 3<>"$m/log"; echo 'follow new' >&3 -timeout 30 cat <&3 # one record a line, as they come -exec 3<&- -``` - -`follow` (without `new`) replays the ring first. `tail -f` sees nothing new. -Through FUSE `read -t` never times out: use `timeout`. After a `Restore` -every connection is cut: dial again and restart the mount. +entry answers `Input/output error`: name the session, never glob. Without a +mount, `test/ninep.py`'s `Client` speaks 9P itself (Scripting, "No mount"). ## Traps - A refused write says only `Invalid argument` or `Input/output error`; the - reason is the `err` line in `$m/log` (`tail -1 "$m/log"`). -- Only writes log: a refused open, truncation (`> data` after a failed - `addr`) or `rmdir` has its errno alone. -- Settings and session words go to `$m/ctl`; pane words (`Undo`, `Save`, - `Del`) to `$m/pane/<n>/ctl`. The wrong one is refused naming the right one. -- `Exit`, `Restore`, `Del`, `get` refuse once over unsaved text; the same word - again discards. -- A word no builtin knows is a shell command (`exit 127` if a typo). -- `lock` needs a held fd: `exec 3>$p/ctl; echo lock >&3; ...; exec 3>&-`. -- plan9port `9p write` truncates: it replaces a whole `body`. -- Through a mount, bash's `printf 'a\nb\n' > ctl` arrives one write per - line, so a block that should be refused whole runs its good lines before - the bad one fails. Send a multi-line block as one write: `cat block > - "$p/ctl"`. - -## No mount: the Python client - -Use [test/ninep.py](../../../test/ninep.py) when nothing is mounted, or when -a fid must stay open (`event`, `pty/data`, a followed `log`). Its paths are -the served root (`/index`, `/pane/2/body`). - -```sh -PYTHONPATH=test python3 -B - "$PARDES_9P" <<'PY' -import sys -from ninep import Client -with Client(sys.argv[1]) as c: - print(c.read('/index').decode(), end='') - n = int(c.read('/pane/new')) - c.write(f'/pane/{n}/body', b'hello\n', truncate=True) - fid = c.open(f'/pane/{n}/event', 0) # hold event: clicks come here - c.write(f'/pane/{n}/exec', b'Msg hi\n') - print(c.read_fid(fid, 0, 4096)) # b'FX0 0 1 6 Msg hi\n' - c.close(fid) - c.remove(f'/pane/{n}') -PY -``` - -`Client` takes a Unix socket path or `(ip, port)`; `client.screen()` returns -the parsed `/screen`. For `event` records and writing them back, see -[fs.md#event](../../../docs/fs.md#event). - -## An isolated session - -Never experiment on a session someone is using. Build a binary with -`zig build install -Dplatform=tty` (a bare `zig build`, or `--prefix -zig-out`, installs over `~/.local/bin`). `test/fs.py` starts a private -session and cleans it up: - -```sh -PYTHONPATH=test python3 -B - "$(realpath zig-out/bin/pardes)" <<'PY' -import sys, tempfile -from pathlib import Path -from fs import session, new_pane, execute -with tempfile.TemporaryDirectory(prefix='pardes-9p-') as d: - with session(sys.argv[1], Path(d), 'probe') as (client, address): - n = new_pane(client, b'fn main() void {}\n') - execute(client, n, 'Msg ready') - assert 'ready' in ''.join(c[0] for c in client.screen()['cells']) -PY -``` - -By hand: `pardes --detach=NAME &` with its own `HOME` and `XDG_*` -directories, then `9ns --mntgen` (the session is `$NINE_MOUNT/pardes/NAME`) -or `9ns --unix $XDG_RUNTIME_DIR/pardes-9p-NAME.sock -- sh`. Strip every -`PARDES_*` variable first, or a file argument goes to the session you are -inside. `session(..., tty=True)` and -[test/agent_session.py](../../../test/agent_session.py) drive terminals. + reason is the last `err` record in `$m/log`. +- `addr` is the pane's and moves on: each `/re/` searches from the last + address, and a `data` write leaves it just past the text. Write `addr` + before each replacement; a failed address leaves none. +- An Edit `x` that matches nothing succeeds: check the text afterwards. +- Each open of `pane/new` makes a pane: read it once. +- Through a mount, `printf 'a\nb\n' > ctl` arrives one write per line: send + a block as one write (`cat block > $p/ctl`), ending in a newline. +- Pane words (`Save`, `Undo`, `Del`) go to `pane/<n>/ctl`, settings and + session words to `$m/ctl`. +- `Exit`, `Restore`, `Del` and `get` refuse once over unsaved text. +- Never experiment on a session someone is using: strip every `PARDES_*` + variable and start your own (Scripting, "An isolated session"); build + with `-Dplatform=tty`, since a bare `zig build` installs over + `~/.local/bin`. |
