--- name: treesheets-agent description: >- Talk directly to a running TreeSheets instance over its local agent socket — run Lobster script (inline or from a file) against whatever document is currently open and get results or errors back. Works against any TreeSheets binary (dev build or installed distribution) on macOS, Linux or Windows (including a Windows build under Wine), source tree optional. Use when the user wants to inspect, automate, or script the currently open TreeSheets document, or asks to test/exercise the TreeSheets agent socket. --- TreeSheets can run Lobster script against whatever document is open in a running instance and hand a result back, over a local, token-authenticated socket. It only exists when TreeSheets was launched with `-a` (agent mode) — **and only in a binary built after this feature landed**; if `-a` produces no endpoint, that TreeSheets predates it (see "No socket appears" below). The transport depends on the platform; the protocol on top is the same: | Platform | Endpoint | Token | |---|---|---| | macOS / Linux | Unix domain socket `/tmp/TreeSheets-agent--.sock` | `.token` | | Windows | TCP on `127.0.0.1`, port written to `%TEMP%\TreeSheets-agent--.port` | `.token` | | Windows build under Wine | same `.port` file, inside the Wine prefix (`/drive_c/users//AppData/Local/Temp/`) | `.token` | `` is the TreeSheets process ID, so each instance started with `-i` gets its own endpoint (under Wine it's the Windows PID, not the host one). Builds from before that change use the same names without `-`; the client finds those too. The Windows port is picked by the OS on every launch, so always read it from the `.port` file. Only a Windows binary built after TCP support was added writes one. Nothing in this skill requires the TreeSheets source tree — the socket, the protocol, and `scripts/ts_agent.py` all work against a bare installed binary. Source references below (file paths under `src/`) are call-outs for when you happen to have the source checked out, not a requirement; skip them freely. The harness reports the absolute path to this `SKILL.md`. Resolve `scripts/ts_agent.py` relative to its parent directory — that's this skill's own directory, independent of wherever the TreeSheets binary you're talking to lives. ## Prerequisites TreeSheets must already be running, started with `-a`. Check first rather than assuming: the endpoint and token files from the table above exist only while such an instance is up (substitute your actual username). If nothing is running with `-a`, launch (or relaunch) one — add `-i` too if you want a fresh instance for testing instead of forwarding to one that's already running: - **If you have a TreeSheets source checkout and dev build** (e.g. this repo): `./_build/TreeSheets.app/Contents/MacOS/TreeSheets -a` on macOS, or `./_build/TreeSheets -a` on Linux (see "Dev-build gotcha" below). - **If you only have an installed/binary distribution**, find it first — do not guess a path: - macOS: `mdfind "kMDItemCFBundleIdentifier == 'com.strlen.TreeSheets'"` or `mdfind -name TreeSheets.app`, or just ask the person where it's installed. Then `open -a "" --args -a` (or run the binary inside `Contents/MacOS/` directly, same as the dev-build case, if you need to see its stdout/stderr). - Linux: `command -v treesheets` / `command -v TreeSheets`, or check whatever package manager installed it (`dpkg -L`/`rpm -ql` for the TreeSheets package), or ask. - Windows: `where TreeSheets` in a shell, the Start-menu shortcut's target, or the usual `C:\Program Files\TreeSheets\TreeSheets.exe`, or ask. Launch it as `TreeSheets.exe -a`. - If you can't find an installed copy at all, say so and ask rather than silently giving up or fabricating a path. ### Windows build under Wine (Linux host) A Windows `TreeSheets.exe` (e.g. from the MinGW cross-build in `_build_win32/`) works under Wine, and the Linux-side client can reach it: Wine maps the Windows TCP socket onto a real host socket on `127.0.0.1`. - Images and Lobster's standard modules are compiled into the exe, so it runs straight from the `cmake --build` directory. - Launch it from that folder: `wine TreeSheets.exe -a -i` (add `WINEDEBUG=-all` to silence Wine's console noise). - For testing, use a throwaway prefix instead of relying on `-i`, just like the throwaway `$HOME` advice below: `WINEPREFIX=/tmp/ts_wine wineboot -i` once (about 10 seconds), then `WINEPREFIX=/tmp/ts_wine wine TreeSheets.exe -a -i`. On Windows, TreeSheets keeps its settings (and the list of files to restore on startup) in the registry, which lives inside the prefix. - **Older TreeSheets builds show an error box at startup in a new prefix**: *"Can't open registry key 'HKCU\Control Panel\International\User Profile' (error 2: File not found.)"*. It's harmless: a wxWidgets 3.3 bug logs an error when this key is missing, but the UI language is still detected correctly (fix proposed in wxWidgets/wxWidgets#27067). Current TreeSheets filters that message out (`SetupInternationalization()` in `src/tsapp.h`). For an older binary, the box stays open until someone clicks OK, which gets in the way of unattended runs, so create the key once per prefix before launching: `WINEPREFIX=/tmp/ts_wine wine reg add 'HKCU\Control Panel\International\User Profile' /f`. - The client finds the `.port` file in `$WINEPREFIX` (or `~/.wine`) on its own when no native Linux socket exists, so export the same `WINEPREFIX` for it. Otherwise pass `--endpoint /drive_c/users//AppData/Local/Temp/TreeSheets-agent--.port`. - `eval -f /host/path.lobster` works: the client converts the path to a Windows path that Wine can open (`winepath -w`, or `Z:\...` if `winepath` is missing). The error label then shows that Windows path. - **Closing the window with unsaved changes shows an "are you sure?" prompt whose window title is empty**: `wmctrl -l` lists it as a window with a blank name (class `treesheets.exe`), not under the main window's title. Until someone answers it, the app doesn't exit and the `.port`/`.token` files stay. Answer it rather than killing the process; that leaves a stale autosave file behind, see below. **Dev-build gotcha (only affects a `cmake --build`-only checkout, not an installed distribution):** the resources directory may be missing, which makes the app hang at startup behind an invisible modal alert about missing icons — the socket never appears in that case. On macOS, fix once from the build directory with `cmake --install . --prefix "$(pwd)"`. On Linux this doesn't translate directly (see "Linux" below); a proper installed distribution already has this handled by its installer/package, so this whole gotcha doesn't apply there. ### Linux resource-path notes (dev builds only) By default (unless configured with `-DTREESHEETS_RELOCATABLE_INSTALLATION=ON`), `TREESHEETS_DATADIR`/`TREESHEETS_DOCDIR` are baked into the binary at configure time as *absolute* paths under `CMAKE_INSTALL_PREFIX` (GNUInstallDirs layout, e.g. `/share/TreeSheets`, `/share/doc/TreeSheets`); `ResolvePath()` looks next to the executable first, then falls back to that compiled-in path. So the macOS `cmake --install . --prefix "$(pwd)"` dev trick doesn't line up on Linux — either actually install to the configured prefix (`sudo cmake --install .`), or reconfigure with `-DTREESHEETS_RELOCATABLE_INSTALLATION=ON` first so it behaves like the macOS case. (Sourced from reading `CMakeLists.txt`/`src/tsapp.h`, not verified on an actual Linux machine.) Cleanup on quit: macOS needed an explicit `wxEVT_END_SESSION` handler because Cmd+Q / AppleScript "quit" bypass the normal close chain there (a Cocoa/wx quirk — see `src/tsapp.h` if you have source). **Verified on Linux/GTK**: a real window-close request (`wmctrl -c `, or the window manager's close button) goes through the ordinary close chain and removes both the `.sock` and `.sock.token` files, no workaround needed. `SIGTERM`/`kill` against the process, unsurprisingly, does *not* — that bypasses `OnClosing` entirely, same as it would on any platform, so don't read a leftover socket/token file after a forceful kill as a bug. Sending the `Ctrl+Q` / `Exit` accelerator via a synthetic key event (`xdotool key ctrl+q`) was unreliable in testing (silently did nothing, no dialog, no exit) — if you need to script a quit for testing, prefer a real close request (`wmctrl -c`) over synthesizing the keypress. **A stale autosave file can make the app look hung on launch.** If a `.tmp` file exists next to `.cts` when that file is (re)opened — including via TreeSheets' own session restore on startup, not just an explicit open — `LoadDB()` (`src/system.h` around line 230) pops a blocking `wxMessageBox`: *"A temporary autosave file exists, would you like to load it instead?"*. This is a genuinely separate top-level window (title "Autosave load"), not a child of the main frame, so it's easy to miss in a `wmctrl -l` grep for the main window's title. While it's open: - The tab/document being loaded doesn't finish loading, and normal window controls, plus the actual `Ctrl+Q`/close on that frame, **don't get ack'd until it's dismissed** — that's what "the app won't quit" usually is, not a socket-cleanup bug. - The agent socket itself stays responsive for whatever document *is* already loaded (`ping` and `eval` both keep working against the previously-active tab) — the modal only blocks the one file that's mid-load, not the whole process. - Dismiss it by clicking **No** (keep the saved file, discard the autosave) unless you specifically want the recovered content; after that, a normal close/quit works immediately. - This is caused by an *ungraceful* prior exit of TreeSheets on that same file (crash, `kill -9`, `SIGTERM`) leaving its periodic-autosave `.tmp` behind — so it's easy to trigger by accident while testing this skill itself (e.g. `pkill`ing a test instance instead of closing it), and then hitting it again on your *next* launch since session restore reopens the same files. Prefer closing test instances (`wmctrl -c`) over killing them to avoid seeding this for next time; if you do end up with a stale `.tmp` next to a real document, mention it rather than silently deleting — it lives next to the user's actual data. ### No socket (or `.port` file) appears even after a clean launch Two different causes, worth telling apart: 1. **Missing resources** (dev build only, see above) — the process hangs before ever reaching the point where it opens the socket. 2. **This binary predates the agent-socket feature** — it starts fine, `-a` is silently ignored (unrecognized single-char flags are no-ops), and no socket ever appears. There's nothing to talk to until that TreeSheets is rebuilt from a source tree that has this feature, or a newer release ships it. Don't spend long debugging a launch that "works" but never produces a socket — check this early. ## Talking to it Use the bundled client: ```bash python3 /absolute/path/to/treesheets-agent/scripts/ts_agent.py ping python3 /absolute/path/to/treesheets-agent/scripts/ts_agent.py eval -c "ts.agent_result(string(1 + 1))" python3 /absolute/path/to/treesheets-agent/scripts/ts_agent.py eval -f /path/to/script.lobster ``` It finds the endpoint and reads its per-launch token itself (see the table at the top: the Unix socket, the Windows `.port` file, or a Wine prefix's `.port` file if no native socket exists) — no setup needed beyond TreeSheets running with `-a`. It ignores endpoint files left behind by a killed instance (nothing accepts connections there). If more than one instance is reachable, it lists them and exits with an error instead of guessing: pick one with `--pid `, e.g. the `$!` of an instance you launched yourself (not under Wine, where the PID in the name is the Windows one), or with `--endpoint ` (`--socket` still works as an alias). On Windows, run it with `python` or `py` instead of `python3` if that is how Python is installed. Pass `--json` (after the subcommand, e.g. `eval -c "..." --json`) to get the raw `{"ok":...,"error":...,"result":...}` response instead of the human-readable summary; prefer `--json` when parsing output programmatically. Exit code is 0 on `ok`, 1 otherwise. ## Several sessions at once Each connection is independent, but the instance runs one script at a time on its GUI thread, and every `eval` works on whatever tab is active in the shared app, with no locking between calls: - An `eval` that arrives while another script is still running (only possible while that script shows a modal dialog, e.g. a Save As) gets `{"ok":false,"error":"busy",...}` and is not run. Retry later. `ping` still works then. - Otherwise requests queue. A long `eval` can make another client hit its `--timeout`, but that client's request was already sent and may still run afterwards: don't assume a timed-out `eval` had no effect. - Another session can switch tabs (`new_document`, `load_document`) or edit the grid between your calls, so re-read positions/sizes in the same `eval` that acts on them instead of reusing numbers from an earlier call. - For work that must not interfere, give each session its own instance (`-a -i` with its own throwaway `$HOME`) and address it with `--pid`. ## Writing the Lobster side Look up the `ts.*` API — navigation (`goto_root`, `goto_child`, `goto_parent`, `goto_selection`, `goto_column_row`...), reading/writing cells (`get_text`, `set_text`, `get_note`, `set_note`...), grid ops (`create_grid`, `insert_column`, `insert_row`, `delete`...), document creation (`new_document`), styling, images, and more — from whichever of these is available, in this order: 1. **The running app's own Help > Script reference menu item** — works no matter how TreeSheets was installed. 2. **`TS/docs/script_reference.html`**, if you have the source tree — the generated, authoritative function reference, no build needed to read it. 3. **The installed copy of that same file** next to wherever TreeSheets put its docs (e.g. `Contents/Resources/docs/script_reference.html` inside a macOS `.app`; location varies for Linux packages) — search for `script_reference.html` under the app's install location if unsure. 4. **Regenerate it straight from the binary**, no source or existing docs install required: ` -d`. This writes `builtin_functions_reference.html` (covering every builtin, not just `ts.*`) into a directory derived from that build's own data path, not necessarily the current directory or the binary's directory — after running it, search for the file (e.g. `find -name builtin_functions_reference.html`) rather than assuming where it landed. 5. Only if you have the source tree and everything above is somehow unavailable: read `src/script_interface.h` / `src/lobster_impl.cpp` directly. Other things worth knowing regardless of how you looked up the API: - `ts.agent_result(s)` is the one addition made for this channel: call it to hand a string back in the response's `result` field. Without it, `result` is always `""`. - Every `eval` starts back at the document root (`ScriptRun` resets position each call). Most mutators (`set_text`, etc.) are no-ops on the root cell since it has no parent — `goto_child(n)`/`goto_selection()` first. - Compile errors come back in `error` in Lobster's usual `