The plugin rearchitecture in one PR. It has all of the install work: #522 (one cadgen installation) and #523 (install channels and update notices) are folded in here and closed in favour of it, and #527 (the animated logos) is carried too. It also has views by absolute path: the explorer, the home and the app menu, one viewer per port, and no roots. Rebased onto `main` at 0.7.11, after #499 and #526. It ships as a patch release, 0.7.12. ## Before deploying - **Run `apps/docs/src/lib/api/schema.sql` on the analytics database before the release's docs deploy.** It only adds (a `channel` column defaulting to `unknown`, and `source` made optional), so any time before is fine, even now: the live receiver keeps writing meanwhile, and the dashboard's breakdown by `source` keeps working. Until it runs, `/v1/health` fails the deploy check. - **OpenAI's portal acceptance of `env` in the ZIP's `.mcp.json` is unverified.** Confirm it at the next upload. If the portal stripped it, the OpenAI copy would only show the update button. - **Codex tabs restored from an older CAD show "CAD was updated"** until they are closed and CAD is opened again: the page no longer relaunches an older build's launch (a hard cutover, like the protocol bump itself). - **Release right after merging** (patch, 0.7.12). Until then main's configs pin cadgen 0.7.11, which starts fine with them (tested), but it has none of this PR's behaviour. ## Install copy: the README and the docs site **Install opens with "Ask your agent (recommended)":** "Send this message to your agent and it will install text-to-cad for you." The message is `Install text-to-cad from https://github.com/earthtojake/text-to-cad`. **Then installing it yourself, worded for a person or their agent alike.** Nothing is addressed to agents: 1. CAD runs through uv: check that it is installed, and install it if it is missing. 2. Run the commands under your app's heading, or Other Agents if it has none. 3. Restart the app. **Every install path is a sub-section with three commands:** install, update, and remove. To reinstall, remove it and install it again. - Claude Code - Claude Desktop (README only): to update, change the pinned version. - Codex - Cursor - Grok Build - Gemini, which installs with `--consent --auto-update` - Other Agents: the Skills CLI. Its `add` is also its update and its reinstall, and `-g -a <agent> -y` skips its questions. - Request Plugin, which opens a new issue On the docs page, each app's update and remove commands sit under a collapsed **Update or reinstall**. That is where the update button's *Manual installation* link lands: `texttocad.dev/install` redirects to `/#install`. **AGENTS.md now opens by pointing an agent that clones the repository to the README's Install.** **The plugin description drops its "Give your agent CAD superpowers." opening everywhere it appears:** the manifests, the Claude marketplace entry, the README intro (now directly under the title) and the docs site's Overview. The tagline stays as the headline. Store listings pick it up on their own schedules: Cursor when this merges, claude.ai at the next release, OpenAI at the next ZIP upload. **Docs site:** - **Animated logos (#527):** the wordmark draws itself in. The README and the hero show it as a 146 KB SVG in place of the 303 KB PNG. - **Available for these agents**, under the hero. It scrolls skills.sh's logo set (copied into `apps/docs/public/agents/` with approval), plus Grok's glyph from Lobe Icons (MIT). The tiles blend into both themes, and each logo jumps to its install. The 21 logos come to 66 KB: Nous Research's embedded PNG went from 196 KB to 22 KB, at the size the tile shows it. - **Header:** the CAD wordmark is always at a phone's left; on desktop it slides in once the hero's wordmark scrolls away. - **Contributing** section. - Every Copy button is the same size. - The hero's file caption is plain text. **One copy:** - The site's copy lives in `src/lib/content.ts`, which the page and the new prerendered `/llms.txt` both render. - The README and the site share their structure, description, install message, and every install, update and remove command. `test_plugin_manifests.py` checks them, and AGENTS.md and `apps/docs/README.md` state the rule. - The README says more: numbered steps, and fuller notes under each install. ## One cadgen installation cadgen runs as one launch command everywhere: `uvx --no-config --managed-python --python 3.13 --from cadgen==<version> <tool>` (`cadgen._internal.launch`). - **One installation and one warm daemon.** uv keeps one installation per requirement, and cadgen names its daemon after the installation's folder. So every app's server, every Codex thread and every skill command share one installation and one daemon. `--managed-python --python 3.13` makes that installation identical on every machine. - **Server configs:** Claude's, Codex's, Cursor's and Gemini's, and the README's Desktop snippet, run the launch command. A test holds them to cadgen's. - **Skills:** each cadgen skill defines `cadgen` and `python` as the launch command. Their `requirements.txt` files are deleted, and a test forbids any other way of installing cadgen in a skill. - **`cadgen doctor <skill>`** reads the pin from the skill's SKILL.md and reports the installation and daemon in use. - **Playwright is a core dependency** (`playwright==1.63.0`); the `snapshot` extra is gone. - The first snapshot installs the headless shell itself. - On Linux, cadgen installs the browser's libraries when it has root, or prints the exact command. - CI installs the headless shell only, about 350 MB less per run. - **Release:** `sync-version.mjs` stamps every skill's launch pin, and `check-version.sh` checks them. - **Dev installs:** `dev_install.py` points the copied skills at the checkout's `.venv`. `--wheel` builds the checkout's wheel and runs everything through `uvx --from <wheel>`. ## Install channels, a daily version check and the update button **Each plugin's startup config names where its installs come from**, in the server's environment: `CADGEN_INSTALL_CHANNEL=<id>`. Plugins that something else keeps up to date also set `CADGEN_AUTO_UPDATED=1`. The viewer and daemon the server starts inherit both. | Package | Server environment | Told of a release | | --- | --- | --- | | `main`'s `claude.mcp.json`: Claude Code, Grok Build, and Cursor through Claude Code's plugins | `claude-github` | yes | | `main`'s `codex.mcp.json` | `codex-github` | yes | | the README's Claude Desktop config | `claude-desktop` | yes | | the `plugin` branch's `cursor.mcp.json`, a Cursor clone (`plugin_branch.py`) | `cursor-github` | yes | | `main`'s `gemini-extension.json` | `gemini-github`, auto-updated | no: Gemini updates it | | `main`'s `cursor.mcp.json`, which the Cursor Marketplace reads | `cursor-marketplace`, auto-updated | no: the store updates it | | the `plugin` branch's `claude.mcp.json`, the Claude directory (`plugin_branch.py`) | `claude-directory`, auto-updated | no: the store updates it | | the OpenAI ZIP (`plugin_zip.py`, replacing `--install store`) | `openai-directory`, auto-updated | no: the store updates it | | a dev install | `dev` | no | | a skill's command, or the viewer a skill opens | nothing (`unknown`) | the viewer only: a skills-only install | - cadgen never decides by a channel's name, since its core may not name a host (a test holds that). A new store needs only its settings, no cadgen release. - The analytics server lists the channel names. - Environment variables, never flags: main's configs pin the last release, and cadgen ignores a variable it doesn't know but refuses a flag. Released 0.7.11 refuses `cadgen mcp --channel`, so flags would have broken every server installed from main between merging and releasing. **Only a copy nothing else updates hears of a new release.** - An auto-updated copy never checks and is never told, and neither is a dev install. - Every other copy reads `api.texttocad.dev/v1/versions` at most once a day, in one anonymous request, and is told whenever it is behind `latest`. The check never runs in CI, and `CADGEN_UPDATE_CHECK=0` turns it off. - The feed is only `{latest}`: the per-store minimums are gone. **The notice** reads "A new version v0.9.0 of text-to-cad is available (currently on v0.8.1)". Its prompt is worded like the install message: "Update text-to-cad to 0.9.0 from https://github.com/earthtojake/text-to-cad". - **CAD app and browser viewer: a blue update button** (`@text-to-cad/ui/update`): first among the navbar's right-hand controls, and on the home a row of its own, labeled Update. Both apps know the notice before the page draws (a launch carries it; the web page reads it with the server's description), so it draws with the page, and it is read again when the person comes back to the page. It opens the update card in a popover: - **Send to agent** (↑, as in Quick Edit) posts the prompt to the chat where the host takes messages, with a Copy icon beside it; - elsewhere it is **Copy prompt**; - a **Manual installation** link goes to `texttocad.dev/install`. - Nothing dismisses the button: it stays until the install is current. - **Inline cards are now the whole viewer** (Claude Desktop and every other MCP Apps host): navbar with the update button, tools, view cube and Quick Edit. Full size moves into a named slot (`CadViewerProps.fullSize`), last in the navbar and the home's row, and appears only where the host can show a view full size. A card used to be a compact view of the model alone, with no navbar, so the update button could never appear inline. - **Text-only apps:** the line on the first `cad_show` result of each server process. In text mode `cad_show` now returns text only, because Claude Code shows a result's structured content in place of its text and dropped the line. - **Skill commands say nothing, and don't check.** The same skill files ship in every plugin, so a command can't tell which one it came with. The earlier once-a-day stderr line is gone. The browser viewer's GitHub release polling and its old navbar update button are gone. `/__cad/version` is GET only, and both apps read it (the CAD app through its tunnel). **The feed** is served by the docs app (`src/lib/analytics` renamed `src/lib/api`). `latest` is the stamped version. **Analytics** send `channel` instead of `source` (batch schema 2), and `cadgen mcp --install` is gone. **Every released cadgen keeps counting:** the receiver reads every schema a release sends, so 0.7.7–0.7.11 copies (schema 1) are stored with the channel `unknown` and their `source` kept, and `schema.sql` only ever adds (a rule in `apps/docs/README.md`). **Nothing waits on the network** (cadgen law 19): analytics and the version check run in the background and fail silently, and CAD works offline once installed. The audit found one breach, now fixed: `/__cad/version` read a due feed inside the request (up to 5 s on a network that drops packets). The feed is now read only in the background, as a server starts, on each launch and on each `/__cad/version`. Checked against an installed cadgen with every request refused, then hanging: builds, snapshots, the server's tools and the viewer's routes all answer at once; the one wait is the exit's last analytics send (2 s when requests hang). How cadgen is launched (uvx re-checks its index once its cache is stale) is outside the law. **`cad-mcp-setup` is retired, with no replacement.** Installing CAD is uv and the app's own plugin install: the `cad` skill names uv, and the README's section for each app carries its install, update and restart steps, which the update button's prompt links. Store copies need nothing run. The Codex manifest no longer names an onboarding skill. There is no `cad_update` tool. ## Views by absolute path: the explorer, the home and the app menu **No more roots.** A view shows one file, named by its absolute path, or the home, and browses from that file's folder. That holds in a Codex tab, a Claude card and the web viewer alike. Roots, the thread workspace, MCP roots, Codex's sandbox metadata and the `explore` flag are gone. Agents name models by absolute path only. **The navbar:** - **The C logo opens the app menu.** It has Back to files (the home), the person's settings (Share anonymous usage data, Quick edit), Send feedback, GitHub and Discord, then the version and "Made by @…". The Settings popover is gone. The logo, the file name and the ⋯ share one hover and the pointer, with no tooltips. - **The file's name opens the explorer.** It shows the file's folder one folder at a time, the last three folders as a breadcrumb with the rest under "…", and "Filter files..." to search everything under the folder. - **The ⋯ menu** has Copy path and Reveal. - **The right side** is empty unless there's something to show: the update button, a dismissed alert's icon, or Full size. - **Display and Preview sit on top of the view cube again**, and Preview has the Play icon again. Preview's own controls land where they sat. **The home and missing files:** - A view with no file shows the home: Recent Files, Open with the desktop's chooser where one exists, and GitHub, Discord and X under the wordmark (the home has no settings menu). - A missing file shows "File does not exist" with Go home. - *Open with CAD* in Codex shows its file alone, with no Back to files and no explorer. **Viewer (`cadgen viewer`):** - **Absolute paths only.** Every route names files by absolute path and nothing is scanned. The asset route still sends only CAD files and their sidecars. - **New routes:** the explorer has `/__cad/folder` and `/__cad/search` (200 results, 1.5 s, 24 folders deep; hidden and build folders skipped). The home has `/__cad/recents`, `/__cad/thumbnail` and `/__cad/pick`. A model joins the library (`POST /__cad/recents` `open`) once it is on screen, and is counted for analytics there. - **`/__cad/server` says only what a reader uses:** `app`, `identityToken`, `autoReload`, `platform` (which file manager Reveal opens), `user`, `start`, `pick`, `port`, `pid`. - **One viewer per port**, 3245 or `--port N`, started from anywhere: - a launch reuses this user's viewer when its code matches; - it replaces one running other code (`/__cad/shutdown`); - it refuses anything else. - **Development:** `--new` takes a free port. The registry and `cadgen viewer list` are gone. **CAD app (`cadgen mcp`):** - A launch is a model's absolute path or the home (protocol 5). A launch from another build shows "CAD was updated". - **The page has three tools: `cad_sync`, `cad_capture_reply` and `cad_http`.** Everything else it asks of the server is the viewer's own routes, sent over `cad_http` to one in-process viewer shared by every view, which keeps the server's library and analytics: the models, the library and its pictures, Open, Reveal, the analytics answer and features, and the update check. The page's own copies of those (`cad_consent`, `cad_features`, `cad_version`, `cad_launch`, `cad_pick_model`, `cad_recents`, `cad_reveal`) are gone. Only the clipboard stays the host's. - **A text client is listed no page tool, and cannot call one:** a call reaches only a tool this client was listed, so no agent there answers the analytics card for the person. **Web:** the bare URL is the home, and `?file=` takes an absolute path. A relative one resolves where the viewer started, for development. **Skills:** they run the viewer from any folder and link `url?file=<absolute path>`. Copied references are absolute paths. ## Cleanup after the rearchitecture What views by absolute path made redundant, removed: - **Both apps reach the server through the same client.** `@text-to-cad/core/client` has `recents`, `changeRecents`, `thumbnail`, `keepThumbnail`, `pick`, `reveal`, `consent`, `features`, `version` and `reportActivity`. The web app's four fetch adapters and the CAD app's seven page tools are gone; the file menu is the shared `createCadFileActions` in both. - **Shared UI:** the declared-panel column (no renderer declared one), the reload-on-change path every live renderer bypassed, the file source's `subscribe`, `ModelLibrary`'s `pictureFrom` (the viewer pictures cards with its own client and `onThumbnail`) and `failure`, and dead exports. The update hook no longer reads on mount: the host hands it the notice. - **Core:** the client's `workspaceId` (every client had the same one), references' `workspaceId`, and the prompt formatter's `resolvePath` (every caller passed the identity). - **Server:** the MCP transport's outgoing requests and notification handler (used only for MCP roots), the viewer's duplicate pick lock (the chooser refuses a second itself), unread server fields, and dead viewer code (`CadgenOps.shutdown`, `STEP_DESCRIPTOR_NAME`). `/__cad/folder` and `/__cad/search` take paths through the one absolute-path rule, so a UNC path is refused there too. - **Docs and wording:** "Settings" is the app menu everywhere (the privacy policy, the README, `cad_analytics`), and no doc describes a retired surface. ## `scripts/install/dev_install.py <host>` Installs this checkout into `claude`, `codex`, `cursor`, `grok`, `gemini` or `claude-desktop` as `text-to-cad@earthtojake-dev`, with its own version per install. Running it again replaces the install; `--uninstall` removes it. It refuses a host where another copy would load. Tested live in each host; Codex and Grok ran in isolated homes. ## Gemini CLI extension `gemini-extension.json` sits at the root and is stamped on each release. Gemini installs from the latest GitHub Release's source tarball. To list it in Gemini's gallery, add the `gemini-cli-extension` topic to the repo. ## Also - **CONTRIBUTING:** what each store reads, Test In Agent Apps, the install channels table, the version feed. - **Removed one-off scripts**, replaced by `dev_install.py` and `--print-weights`. - **`test.yml`** also runs on `.cursor-plugin/**`, `gemini-extension.json` and `cursor.mcp.json`. Deploy Docs also checks `/v1/versions`. ## Checks - **At 1bdfa29e9 (the follow-ups and the cleanup), all pass:** - Python: cadgen 2215 (viewer and CAD app included); skills 280, 19, 51, 77, 4, 6 and 6; global 169; - core 1281, the core client 15; shared UI: node 746 + browser 53, vitest 336; browser viewer 57; CAD app 34; docs 16; - type checks for core, the shared UI, the CAD app and the browser viewer; the dependency and kit boundaries; - the full bundle, `check-version.sh` and `check-builds.sh`; - the viewer launch smoke test and the real-backend browser suite (format, camera). - **End to end, on this branch's wheel as cadgen 0.7.103 through the plugins' launch line, with fresh state (43/43):** - the launcher on spare ports (start, reuse from another folder, replace other code, stop, refuse a port another program holds) and `/__cad/server`'s nine fields; - the routes by absolute path (catalog, folder, search, the asset gate, recents, the pick guard); - in a browser, from the wheel's own client: a STEP from a cold store draws; the navbar's logo menu, file name and ⋯; Display and Preview over the cube; the explorer in the file's folder, its filter, a pick that opens the file, closes the explorer and joins the library; the app menu; Back to files to the home and its GitHub, Discord and X; "File does not exist"; a relative `?file=`; no page errors; - the CAD app inline (the agent's four tools and the page's three; the tunnel's catalog, folder, library, pictures, Reveal, analytics answer and update check; the clipboard and shutdown refused), as Codex tabs (home, tab, `cad_open`, the tab reopening, *Open with CAD*), and to a text client (two tools, no page tool, a link to the model). - **After views by absolute path (db6a5419e), all pass:** - shared UI: node 750 + browser 53, vitest 342; - core 1280, bench 24, browser viewer 57, CAD app 35; - Python 2215, with one failure that only happens in a shell with `PYTHONDONTWRITEBYTECODE=1` (`test_no_stale_bytecode`; it passes without it); - global 168, and the dependency boundaries; - type checks for the shared UI, the CAD app and the browser viewer; - the full bundle; - the viewer launch smoke test (a cold surface display, reuse, stop); - the real-backend browser suite (format, camera). - **By hand:** - in the web viewer: - the home, the explorer and its search, "File does not exist", a relative `?file=`; - the logo menu, and Display above the cube; - the update button and its card; - an inline card in a local MCP Apps harness: the menu, Back to files, the explorer; - the launcher on a custom port: start, reuse from another folder, refuse a port another program holds, stop. - **Not run for views by absolute path:** Claude Desktop and Codex end to end. - **Full local run after the rebase onto #499 and #526, with the update button (all pass):** - bundle check; - Python: - cadgen 2309; - skills 280, 19, 51, 77, 4, 6 and 6; - global 168; - JS: - core 1282; - bench 24; - shared UI: node 764 + 63, vitest 350; - browser viewer 61; - CAD app 36; - type checks for the shared UI, the CAD app and the browser viewer; - browser viewer and CAD app builds; - docs lint, type check and production build; - `check-version.sh`, `sync-version.mjs --check` and `check-builds.sh`. - **After #527 and the install copy:** - docs check; - `test-docs.sh` (16, with #527's logo tests); - global 168; - `check-builds.sh`. - The docs page was checked in a browser: Update or reinstall at desktop and phone width, and `/install` landing on `#install`. - **End to end, on a simulated fresh machine:** - Setup: - this branch's wheel as cadgen 0.7.99, with the plugin files stamped to it as a release would; - an empty uv cache with no uv Python; - every server started by its package's exact config, every skill command by its SKILL.md's exact launch line. - 28/28 checks: - **Cold install:** first start from a desktop app's bare PATH downloaded uv's Python 3.13.14 and every package and answered the MCP handshake in 7 s. A warm start took 0.2 s. - **One installation:** the server and the skills' `python` and `cadgen`, from a terminal PATH and a bare PATH, all use one uv environment on uv's Python. `cadgen doctor` confirms the pin and the kernel. - **One daemon:** a build run by a skill (`python src/bracket.py`) starts the daemon, and `cadgen daemon status` from either PATH shows the same process, the one `doctor` names. - **All 8 packages:** main's 4 configs, the README's Claude Desktop config, the `plugin` branch's 2 and the OpenAI ZIP's. The told copies get the notice. The auto-updated ones don't. - **Text apps:** a store copy's `cad_show` has no line and its server-started viewer no button; a GitHub copy has both. - **Skill commands** print nothing and read no feed. A viewer a skill starts shows the button. - **Right after merge:** HEAD's real configs (pinned 0.7.11) start on PyPI's 0.7.11. - **Agent apps**, each in a throwaway home (real configs verified unchanged): - Claude Code 2.1.281: marketplace and plugin install, `claude mcp list` shows the server ✔ Connected. - Codex 0.153.4: marketplace and plugin install, with the server and its channel in `codex mcp list`. - Grok Build 1.0.40: install, then `grok mcp doctor` gives a handshake and 14 tools. - **Not run here:** Gemini CLI (not installed on this machine), and the Cursor, Claude Desktop and Codex desktop apps (GUI only, or in use). - **Nested Claude Code agents**, with stand-in CLIs and isolated config: - Sent the update prompt, a copy installed from GitHub ran `claude plugin marketplace update earthtojake` and `claude plugin update text-to-cad@earthtojake`, then said to restart. - A Claude directory copy changed nothing. The agent said the store updates it and offered the switch. - An update line in a CAD result was followed the same way. - **Earlier, against the bundled wheel run through uvx as users get it, with a local feed:** - store and dev copies never asking; - the line on a text client's `cad_show`; - `cad_version` for app clients; - the viewer showing the analytics card, then the update button. - **Not run:** Codex and Claude Desktop end to end, because Codex was in use by another thread. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
143 KiB
143 KiB