From d3b6dad0c069a57d2a677fb9852be786209f0824 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 09:12:52 -0500 Subject: [PATCH 1/2] Docs: link the repo paths the prose names --- CONTRIBUTING.md | 24 +++++++++++----------- desktop/README.md | 14 ++++++------- docs/guides/drive-a-board-from-an-agent.md | 2 +- docs/guides/recover-a-board.md | 4 ++-- docs/how-it-works/wifi-and-bluetooth.md | 10 ++++----- integrations/claude-code-plugin/README.md | 4 ++-- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a774472..a6f97d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,22 +3,22 @@ mpftp is a TypeScript VS Code extension, a Python package that holds the serial session, the firmware engine and the command line, and a browser app. A change to behavior usually lands in the Python package first. A unit test in -`cli/tests/` or a documentation fix is a good first contribution. How the parts +[`cli/tests/`](cli/tests/) or a documentation fix is a good first contribution. How the parts fit together is in [How mpftp works](docs/how-it-works/index.md). ## Where things are | Path | What it holds | |---|---| -| `extension/src/` | The extension host: bridge, panels, terminal, firmware panel and the session server for the command line | -| `extension/media/` | Webview HTML, JS and CSS. `ftp.html`, `ftp.js` and `ftp.css` are the File Transfer view | +| [`extension/src/`](extension/src/) | The extension host: bridge, panels, terminal, firmware panel and the session server for the command line | +| [`extension/media/`](extension/media/) | Webview HTML, JS and CSS. `ftp.html`, `ftp.js` and `ftp.css` are the File Transfer view | | `extension/resources/templates/` | Workspace stub `micropython.cmake` and `manifest-micropython.py` | -| `extension/python/mpftp/` | A vendored copy of `cli/src/mpftp/`, staged by `scripts/stage-vendored-python.sh` so `vsce` can package it. Never edit it; edit `cli/src/mpftp/` | +| `extension/python/mpftp/` | A vendored copy of [`cli/src/mpftp/`](cli/src/mpftp/), staged by [`scripts/stage-vendored-python.sh`](scripts/stage-vendored-python.sh) so `vsce` can package it. Never edit it; edit `cli/src/mpftp/` | | `cli/src/mpftp/` | The `pydevices-mpftp` package: `sidecar.py` (serial session), `cli.py`, `firmware.py` (discover, build, flash, detect), `firmware_build.py` (runs `build_mp.py`), `firmware_download.py` (the official catalog), `pwa.py` (the local web app), `webrepl.py`, `ble.py`, `hold.py`, `config.py`, `uf2.py` | -| `cli/src/mpftp/webui/` | The built browser app. It is committed, not ignored: the release pipeline only runs `python -m build .` with no Node step, so it has to be current on `main`. CI's `ui` job rebuilds it and runs `git diff --exit-code`, so a stale commit fails CI | +| [`cli/src/mpftp/webui/`](cli/src/mpftp/webui/) | The built browser app. It is committed, not ignored: the release pipeline only runs `python -m build .` with no Node step, so it has to be current on `main`. CI's `ui` job rebuilds it and runs `git diff --exit-code`, so a stale commit fails CI | | `cli/tests/` | The Python tests (`unittest`) | -| `ui/` | The browser app's source: TypeScript with esbuild, `@xterm/xterm` and CodeMirror 6. Its File Transfer panel is the extension's own, copied in by `npm run build`, which stages everything into `cli/src/mpftp/webui/`. `src/host.ts` gives `ftp.js` an `acquireVsCodeApi()` that talks to `cli/src/mpftp/panel.py`, the Python port of the extension's message handling, so a change to one host's behavior belongs in the other too | -| `desktop/` | The desktop app, a Tauri 2 window around the browser app that starts a bundled Python running `cli/src/mpftp/desktop_host.py`. `emulator/` is the simulated board, reached as `emu://`. See [desktop/README.md](desktop/README.md) | +| `ui/` | The browser app's source: TypeScript with esbuild, `@xterm/xterm` and CodeMirror 6. Its File Transfer panel is the extension's own, copied in by `npm run build`, which stages everything into `cli/src/mpftp/webui/`. `src/host.ts` gives `ftp.js` an `acquireVsCodeApi()` that talks to [`cli/src/mpftp/panel.py`](cli/src/mpftp/panel.py), the Python port of the extension's message handling, so a change to one host's behavior belongs in the other too | +| `desktop/` | The desktop app, a Tauri 2 window around the browser app that starts a bundled Python running [`cli/src/mpftp/desktop_host.py`](cli/src/mpftp/desktop_host.py). `emulator/` is the simulated board, reached as `emu://`. See [desktop/README.md](desktop/README.md) | | `tools/` | `test_cli_workflows.py` (a workflow test that needs an attached board), `ble_bench.py` and `ble_gate.py` (Bluetooth), and `windows/` (the USB-restart task) | | `scripts/` | The `mpftp` launcher, version and release scripts, `install-cursor-wsl.sh`, `stage-vendored-python.sh` | | `integrations/` | The Claude Code plugin: a skill that teaches agents the CLI | @@ -45,7 +45,7 @@ npm run package # VSIX via @vscode/vsce boards; it skips flashing unless you pass `--install-latest-firmware`. On native Linux, serial needs nothing extra: `npm run package` bundles mpremote -and pyserial (the hashed pins in `extension/vendor-requirements.txt`) into +and pyserial (the hashed pins in [`extension/vendor-requirements.txt`](extension/vendor-requirements.txt)) into `extension/python/_vendor`, and the launcher puts them on the session's path. For a WSL window that runs the extension remotely: @@ -113,7 +113,7 @@ tag-release.yml (push to main touching VERSION) ``` `VERSION` is the single source of truth, not the tag. Every other version site -is committed equal to it, and `scripts/check_versions.py` enforces that in CI on +is committed equal to it, and [`scripts/check_versions.py`](scripts/check_versions.py) enforces that in CI on every pull request. Both publish workflows only confirm the tag they were triggered from agrees with the `VERSION` on the commit they checked out. @@ -150,10 +150,10 @@ gh pr list --state open # the bot's "Release X.Y.Z" PR The bot opens the PR in two steps. It sets `VERSION` and prepends a commit list to `CHANGELOG.md`, then a second commit runs `python scripts/check_versions.py --write`, which sets every other version site -from `VERSION` in the form each takes: `extension/package.json`, -`ui/package.json`, the root entry of both `package-lock.json` files, +from `VERSION` in the form each takes: [`extension/package.json`](extension/package.json), +[`ui/package.json`](ui/package.json), the root entry of both `package-lock.json` files, `.claude-plugin/marketplace.json` and -`integrations/claude-code-plugin/.claude-plugin/plugin.json`. If the second +[`integrations/claude-code-plugin/.claude-plugin/plugin.json`](integrations/claude-code-plugin/.claude-plugin/plugin.json). If the second commit is missing (the workflow's `sync-versions` job failed), run that command on the `release/vX.Y.Z` branch and push. diff --git a/desktop/README.md b/desktop/README.md index db378f3..30c74a1 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -39,24 +39,24 @@ window and `host.log` for the Python side. ## How it fits together -The window is a [Tauri 2](https://v2.tauri.app/) app, `src-tauri/src/main.rs`. +The window is a [Tauri 2](https://v2.tauri.app/) app, [`src-tauri/src/main.rs`](src-tauri/src/main.rs). It shows the same page `python -m mpftp` serves in a browser -(`cli/src/mpftp/webui/`, built from `ui/`), and starts the bundled Python +([`cli/src/mpftp/webui/`](../cli/src/mpftp/webui/), built from `ui/`), and starts the bundled Python running `python -m mpftp.desktop_host` beside it. The page and the host talk through the app over stdin and stdout, not a web server, so no port is open. -`ui/src/rpc.ts` picks that route inside the app and the WebSocket in a +[`ui/src/rpc.ts`](../ui/src/rpc.ts) picks that route inside the app and the WebSocket in a browser, which is the only difference between the two. The host is restarted if it dies, and on Windows it runs in a Job Object, so closing the app never leaves a Python behind. The simulated board is `emulator/`: a window running the WebAssembly runtime, -which the host reaches as an `emu://` port (`cli/src/mpftp/emu.py`). -`emulator/node-emu.mjs` runs the same board core under Node, for trying the +which the host reaches as an `emu://` port ([`cli/src/mpftp/emu.py`](../cli/src/mpftp/emu.py)). +[`emulator/node-emu.mjs`](emulator/node-emu.mjs) runs the same board core under Node, for trying the `emu://` path without the app. Desktop's version is the repo's: `tauri.conf.json` reads it from -`package.json`, which `scripts/check_versions.py` keeps equal to the root +`package.json`, which [`scripts/check_versions.py`](../scripts/check_versions.py) keeps equal to the root `VERSION` file with every other deliverable. ## Build it @@ -109,5 +109,5 @@ npm run build:linux # the .deb It lands in `src-tauri/target/release/bundle/deb/`. `tauri.linux.conf.json` swaps the Windows Python for the Linux one, and -`scripts/smoke_linux.sh` starts a built package and checks that its Python +[`scripts/smoke_linux.sh`](scripts/smoke_linux.sh) starts a built package and checks that its Python host comes up. diff --git a/docs/guides/drive-a-board-from-an-agent.md b/docs/guides/drive-a-board-from-an-agent.md index 0abc866..228adc8 100644 --- a/docs/guides/drive-a-board-from-an-agent.md +++ b/docs/guides/drive-a-board-from-an-agent.md @@ -24,7 +24,7 @@ Codex's Plugins panel reads the same plugin (**Plugins → Add → Add plugin marketplace**, source `PyDevices/mpftp`). Other agents can read this page directly, for example from your project's `AGENTS.md`. Install the CLI first ([Install](../get-started/install.md)); with a clone of the repository, -`./scripts/mpftp` stands in for `mpftp`. +[`./scripts/mpftp`](../../scripts/mpftp) stands in for `mpftp`. ## How commands find a session diff --git a/docs/guides/recover-a-board.md b/docs/guides/recover-a-board.md index 6837a0e..2aad558 100644 --- a/docs/guides/recover-a-board.md +++ b/docs/guides/recover-a-board.md @@ -59,7 +59,7 @@ Two ways through: That pops one UAC prompt and grants nothing that outlives it. The board goes from the stale `303A:4001` node to `303A:1001`, and esptool can flash it with - no button press. `tools/windows/restart-esp-usb.ps1` in the repository does + no button press. [`tools/windows/restart-esp-usb.ps1`](../../tools/windows/restart-esp-usb.ps1) in the repository does this for whatever Espressif board is attached. The order to try is `machine.bootloader()` over `exec`, then the device-node restart, then flash the `1001` port, and the physical BOOT-and-plug only when no one can click. @@ -108,7 +108,7 @@ names the node after the chip's MAC address, colons included `--instance` accepts it. **One elevated install first.** The task needs a one-time install by someone -with administrator rights. Copy `tools/windows/` from the repository to a plain +with administrator rights. Copy [`tools/windows/`](../../tools/windows/) from the repository to a plain Windows path (an elevated shell can't reliably read `\\wsl.localhost\...`), then run, in an administrator PowerShell: diff --git a/docs/how-it-works/wifi-and-bluetooth.md b/docs/how-it-works/wifi-and-bluetooth.md index 67406a5..cfd610b 100644 --- a/docs/how-it-works/wifi-and-bluetooth.md +++ b/docs/how-it-works/wifi-and-bluetooth.md @@ -8,7 +8,7 @@ This page is why each link is built the way it is. To use them, see ## Wi-Fi (WebREPL) -`cli/src/mpftp/webrepl.py` holds a small WebSocket client written with the +[`cli/src/mpftp/webrepl.py`](../../cli/src/mpftp/webrepl.py) holds a small WebSocket client written with the standard library, `WebSocketSerial`. It has the pyserial surface that mpremote's `SerialTransport` uses (`read`, `write`, `inWaiting`, `timeout`), and a subclass of `SerialTransport` is handed that object instead of a port. The only @@ -63,14 +63,14 @@ session keeps it in memory for reconnects, and the activity log redacts it. **Discovery.** Every connect reads the board's uid, hostname and, when Wi-Fi is up, its IP. A `NAME.local` lookup asks the OS resolver first and then sends its own -mDNS query (`cli/src/mpftp/mdns.py`, standard library only); the lookup runs in +mDNS query ([`cli/src/mpftp/mdns.py`](../../cli/src/mpftp/mdns.py), standard library only); the lookup runs in the session, which on WSL is the Windows Python. ## Bluetooth (bledev) Windows owns the computer's radio, so the session reaches the board through bleak in the Windows Python, the same one it uses for `COM` ports. -`cli/src/mpftp/ble.py` dresses the link as a pyserial port the way `webrepl.py` +[`cli/src/mpftp/ble.py`](../../cli/src/mpftp/ble.py) dresses the link as a pyserial port the way `webrepl.py` does, and hands it to `SerialTransport`: - **The REPL.** mpftp finds the board by name, connects, subscribes to the Nordic @@ -97,7 +97,7 @@ does, and hands it to `SerialTransport`: Measured for 20 KB transfers between Windows and an ESP32-S3 on MicroPython 1.30 preview, with mpremote 1.29: median of three runs, each checked byte for byte and -against the board's SHA-256 (`tools/ble_bench.py` runs the benchmark): +against the board's SHA-256 ([`tools/ble_bench.py`](../../tools/ble_bench.py) runs the benchmark): | Path | Up | Down | |---|---|---| @@ -111,5 +111,5 @@ File transfer is about 14 times faster up and 31 times faster down than mpremote's own raw-REPL transfers, and bigger chunks don't close the gap: raw-paste waits a round trip for every window of input the board grants, and each exec's output crosses the same link as text. The upload rate is set by the board, -which writes flash between windows. `tools/ble_gate.py` runs an end-to-end check +which writes flash between windows. [`tools/ble_gate.py`](../../tools/ble_gate.py) runs an end-to-end check of connect, put, get, exec and interrupt in one session. diff --git a/integrations/claude-code-plugin/README.md b/integrations/claude-code-plugin/README.md index 30f296e..437cab5 100644 --- a/integrations/claude-code-plugin/README.md +++ b/integrations/claude-code-plugin/README.md @@ -29,5 +29,5 @@ app's Code panel, have the same flow in the GUI: Settings → Plugins → | Path | Role | |---|---| -| `.claude-plugin/plugin.json` | Plugin manifest; its version tracks the repository's `VERSION` (`scripts/check_versions.py` checks it) | -| `skills/board-tools/SKILL.md` | The skill | +| `.claude-plugin/plugin.json` | Plugin manifest; its version tracks the repository's `VERSION` ([`scripts/check_versions.py`](../../scripts/check_versions.py) checks it) | +| [`skills/board-tools/SKILL.md`](skills/board-tools/SKILL.md) | The skill | From 64e16cf9fc2862a72710f1cddc32650e06c38670 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 09:52:52 -0500 Subject: [PATCH 2/2] Docs: folder links outside the docs tree go to GitHub --- docs/guides/recover-a-board.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/recover-a-board.md b/docs/guides/recover-a-board.md index 2aad558..b413923 100644 --- a/docs/guides/recover-a-board.md +++ b/docs/guides/recover-a-board.md @@ -108,7 +108,7 @@ names the node after the chip's MAC address, colons included `--instance` accepts it. **One elevated install first.** The task needs a one-time install by someone -with administrator rights. Copy [`tools/windows/`](../../tools/windows/) from the repository to a plain +with administrator rights. Copy [`tools/windows/`](https://github.com/PyDevices/mpftp/tree/main/tools/windows) from the repository to a plain Windows path (an elevated shell can't reliably read `\\wsl.localhost\...`), then run, in an administrator PowerShell: