From 8fe92087bbd8574141e992f162e33b65eb7a2354 Mon Sep 17 00:00:00 2001 From: Karn Date: Mon, 24 Aug 2026 22:50:11 +0530 Subject: [PATCH 1/2] reins.tech as one plain document The site sold like a SaaS product and reins is not one. It is an MIT CLI you run on your own machine, and the page now reads the way its README does: mono throughout, zero radius, headings with their markers showing, `*` lists, `$` transcripts, two-column tables, `---` between sections. The vocabulary lives in one file, components/md.tsx, ported from flue's site so the two stay recognisably the same hand. Everything the site draws is one of those primitives. The two mocks are the only exceptions, and they are pictures: they keep their corners and a UI face, because they are pictures of a browser and of the extension popup. Gone with the brochure: the hero section, the reveal-on-scroll wrapper, the animated terminal and its chip switcher, the dark shiki code slab that overflowed its own container, the docs sidebar and floating table of contents, the accordion FAQ, and the gradient CTA. The header is one unsticky line of bracket links; the footer is pipe-separated links and a copyright. The copy was rewritten alongside it, in plain global English: short sentences, common words, no idiom. Em-dashes and en-dashes are gone, and scripts/check-prose.mjs now fails `pnpm build` if one reaches anything a reader is served. Claims that were not true went with them, including "anything you can do signed in, your agent can do on request" (false under the read and deny tiers), "in under a minute", and "nothing leaves your computer" (the browser still reaches the internet; reins is what sends nothing). A Limits section is new, and says what reins does not do: Chromium only, no headless, not built for CI. The extension and CLI error strings lost their em-dashes too, so the docs can go on quoting them verbatim. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 45 +- packages/cli/CHANGELOG.md | 4 +- packages/cli/src/bridge.ts | 4 +- packages/cli/src/integration.test.ts | 2 +- packages/cli/src/rpc.test.ts | 2 +- packages/cli/src/rpc.ts | 2 +- packages/extension/CHANGELOG.md | 2 +- packages/extension/src/lib/policy.ts | 6 +- packages/web/package.json | 3 +- packages/web/public/404.html | 4 +- packages/web/public/llms.txt | 21 +- packages/web/scripts/check-prose.mjs | 161 ++++++ .../web/src/components/animated-terminal.tsx | 111 ---- packages/web/src/components/browser-mock.tsx | 134 +++++ packages/web/src/components/code-block.tsx | 27 - packages/web/src/components/copy-command.tsx | 31 +- packages/web/src/components/copy-markdown.tsx | 24 +- packages/web/src/components/docs-toc.tsx | 278 --------- packages/web/src/components/hero.tsx | 167 ------ packages/web/src/components/ideas-section.tsx | 159 ------ packages/web/src/components/md.tsx | 239 ++++++++ packages/web/src/components/release-list.tsx | 115 ++-- packages/web/src/components/reveal.tsx | 31 - packages/web/src/components/site-footer.tsx | 177 +----- packages/web/src/components/site-header.tsx | 211 ++----- packages/web/src/components/terminal.tsx | 44 -- packages/web/src/components/theme-toggle.tsx | 13 +- packages/web/src/components/ui/accordion.tsx | 64 --- packages/web/src/lib/dom-to-markdown.ts | 4 + packages/web/src/lib/highlight.ts | 23 - packages/web/src/routes/__root.tsx | 4 +- packages/web/src/routes/changelog/route.tsx | 55 +- packages/web/src/routes/docs/architecture.tsx | 158 ++---- packages/web/src/routes/docs/commands.tsx | 282 +++++----- packages/web/src/routes/docs/comparison.tsx | 211 +++---- packages/web/src/routes/docs/faq.tsx | 110 ++-- packages/web/src/routes/docs/index.tsx | 123 ++-- packages/web/src/routes/docs/permissions.tsx | 161 +++--- packages/web/src/routes/docs/route.tsx | 115 ++-- packages/web/src/routes/docs/security.tsx | 132 ++--- packages/web/src/routes/docs/sideload.tsx | 96 ++-- packages/web/src/routes/index.tsx | 528 +++++++++--------- packages/web/src/routes/privacy.tsx | 72 +-- packages/web/src/styles.css | 199 +------ 44 files changed, 1725 insertions(+), 2629 deletions(-) create mode 100644 packages/web/scripts/check-prose.mjs delete mode 100644 packages/web/src/components/animated-terminal.tsx create mode 100644 packages/web/src/components/browser-mock.tsx delete mode 100644 packages/web/src/components/code-block.tsx delete mode 100644 packages/web/src/components/docs-toc.tsx delete mode 100644 packages/web/src/components/hero.tsx delete mode 100644 packages/web/src/components/ideas-section.tsx create mode 100644 packages/web/src/components/md.tsx delete mode 100644 packages/web/src/components/reveal.tsx delete mode 100644 packages/web/src/components/terminal.tsx delete mode 100644 packages/web/src/components/ui/accordion.tsx delete mode 100644 packages/web/src/lib/highlight.ts diff --git a/README.md b/README.md index cac99f4..1fcf320 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,9 @@

-reins gives your coding agent (Claude Code, Cursor, Codex, Copilot — anything -with a shell) control of the real, logged-in Chromium browser you already use, -through a CLI and a Manifest V3 extension. No MCP server to register, no debug +reins gives your coding agent control of the real, logged-in Chromium browser +you already use, through a CLI and a Manifest V3 extension. Claude Code, Cursor, +Codex, Copilot: anything with a shell. No MCP server to register, no debug profile, no launch flags, no tokens. ## Quick start @@ -38,35 +38,44 @@ npm i -g @karnstack/reins # the CLI (daemon included, starts on demand) npx skills add karnstack/reins # the skill, into your agent(s) of choice ``` -Then install the extension in every Chromium browser you want agents to reach — -Chrome, Brave, Edge, Arc, Dia: +Then install the extension in every Chromium browser you want agents to reach: +Chrome, Brave, Edge, Arc, Dia. -**[Add reins from the Chrome Web Store](https://chromewebstore.google.com/detail/reins/hnjcfgochepemjndccfblpmfmlblkofo)** — it finds the local daemon and connects on its own. +**[Add reins from the Chrome Web Store](https://chromewebstore.google.com/detail/reins/hnjcfgochepemjndccfblpmfmlblkofo)**. It finds the local daemon and connects on its own. -No store access? `reins extension` installs it via Load unpacked instead — see -[docs/SIDELOAD.md](docs/SIDELOAD.md). That's the whole setup; `reins status` -shows what's connected. +No store access? `reins extension` installs it through Load unpacked instead. +See [docs/SIDELOAD.md](docs/SIDELOAD.md). That is the whole setup, and +`reins status` shows what is connected. ## The loop ```bash -reins snapshot # list interactive elements with refs → e5: button "Submit" -reins click --ref e5 # act by ref -reins text # verify (or: reins screenshot → prints an image path) +reins snapshot # look: interactive elements with refs → e5: button "Submit" +reins click --ref e5 # act: by ref +reins text # check: or reins screenshot, which prints an image path ``` Every command takes `--tab ` (default: active tab), `--browser ` (only -when several are connected), and `--json`. `reins help` is self-describing; -`reins cdp` is the escape hatch to the full Chrome DevTools Protocol. +when several are connected), and `--json`. `reins help` is self-describing, and +`reins cdp` reaches the full Chrome DevTools Protocol when the curated commands +are not enough. + +## Limits + +Chromium only, so no Firefox and no WebKit. No headless mode: reins drives a +browser you already have open, which also makes it the wrong tool for CI. With +two browsers connected, commands need `--browser `, because reins never +guesses which one you meant. Releases are still `0.x`, so commands, flags, and +output can change. ## Learn more The full story lives on the site: -- **[Docs](https://reins.tech/docs)** — getting started, architecture, and the complete command reference -- **[How it compares](https://reins.tech/docs/comparison)** — vs agent-browser, dev3000, and playwright-mcp -- **[Security](https://reins.tech/docs/security)** — per-site permissions, `127.0.0.1`-only binding, and the threat model -- **[Site permissions](https://reins.tech/docs/permissions)** — the `deny` / `read` / `full` tiers and how to tighten them +- **[Docs](https://reins.tech/docs)**: getting started, architecture, and the complete command reference +- **[How it compares](https://reins.tech/docs/comparison)**: reins next to agent-browser, dev3000, and playwright-mcp +- **[Security](https://reins.tech/docs/security)**: per-site permissions, `127.0.0.1`-only binding, and the threat model +- **[Site permissions](https://reins.tech/docs/permissions)**: the `deny`, `read`, and `full` tiers, and how to tighten them Report vulnerabilities privately via [GitHub security advisories](https://github.com/karnstack/reins/security/advisories/new). diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index e78833e..8d83ef6 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -19,7 +19,7 @@ ### Patch Changes -- 6885954: Trust the published Chrome Web Store extension (`hnjcfgochepemjndccfblpmfmlblkofo`) out of the box — store installs connect without `reins allow`. +- 6885954: Trust the published Chrome Web Store extension (`hnjcfgochepemjndccfblpmfmlblkofo`) out of the box. Store installs connect without `reins allow`. ## 0.2.0 @@ -27,7 +27,7 @@ - f6b30a4: New `reins extension` command: install the extension without the Chrome Web Store. The npm package now bundles the extension build with a key-pinned, - pre-allowlisted id — `reins extension` stages it at `~/.reins/extension` for + pre-allowlisted id. `reins extension` stages it at `~/.reins/extension` for Chrome's Load unpacked, no `reins allow` step. See docs/SIDELOAD.md. ### Patch Changes diff --git a/packages/cli/src/bridge.ts b/packages/cli/src/bridge.ts index 000f6d4..56546a9 100644 --- a/packages/cli/src/bridge.ts +++ b/packages/cli/src/bridge.ts @@ -232,14 +232,14 @@ export class BridgeHost implements BridgePort { const entry = this.#browsers.get(browserId); if (!entry || entry.ws.readyState !== WebSocket.OPEN) { throw new Error( - `unknown browserId "${browserId}"${roster ? ` — connected: ${roster}` : " — no browsers connected"}`, + `unknown browserId "${browserId}"${roster ? `. Connected: ${roster}` : ". No browsers connected"}`, ); } return { id: browserId, ws: entry.ws }; } if (live.length === 0) throw new Error("extension not connected"); if (live.length > 1) { - throw new Error(`several browsers connected — pass browserId. Connected: ${roster}`); + throw new Error(`several browsers connected. Pass browserId. Connected: ${roster}`); } const only = live[0] as BrowserInfo; const entry = this.#browsers.get(only.id) as ConnectedBrowser; diff --git a/packages/cli/src/integration.test.ts b/packages/cli/src/integration.test.ts index 2105c53..8626dc7 100644 --- a/packages/cli/src/integration.test.ts +++ b/packages/cli/src/integration.test.ts @@ -78,7 +78,7 @@ function standInExtension(port: number): Promise { error: { code: "policy_denied", message: - "blocked by policy: x.com is read-only — grant full access from the reins extension popup", + "blocked by policy: x.com is read-only. Grant full access from the reins extension popup", }, }), ); diff --git a/packages/cli/src/rpc.test.ts b/packages/cli/src/rpc.test.ts index 73a211e..981c023 100644 --- a/packages/cli/src/rpc.test.ts +++ b/packages/cli/src/rpc.test.ts @@ -90,7 +90,7 @@ describe("listAllTabs", () => { it("errors on an unknown browserId, naming the roster", async () => { const bridge = fakeBridge(); await expect(listAllTabs(bridge, "b9")).rejects.toThrow( - 'unknown browserId "b9" — connected: b1 (Chrome)', + 'unknown browserId "b9". Connected: b1 (Chrome)', ); }); }); diff --git a/packages/cli/src/rpc.ts b/packages/cli/src/rpc.ts index 29c1fb4..2ecfe99 100644 --- a/packages/cli/src/rpc.ts +++ b/packages/cli/src/rpc.ts @@ -14,7 +14,7 @@ export async function listAllTabs(bridge: BridgePort, browserId?: string): Promi const targets = browserId ? bridge.browsers.filter((b) => b.id === browserId) : bridge.browsers; if (browserId !== undefined && targets.length === 0) { const roster = bridge.browsers.map((b) => `${b.id} (${b.browser})`).join(", "); - throw new Error(`unknown browserId "${browserId}"${roster ? ` — connected: ${roster}` : ""}`); + throw new Error(`unknown browserId "${browserId}"${roster ? `. Connected: ${roster}` : ""}`); } const results = await Promise.all( targets.map(async (b) => { diff --git a/packages/extension/CHANGELOG.md b/packages/extension/CHANGELOG.md index 4dc24fd..99002ec 100644 --- a/packages/extension/CHANGELOG.md +++ b/packages/extension/CHANGELOG.md @@ -21,5 +21,5 @@ - f6b30a4: New `reins extension` command: install the extension without the Chrome Web Store. The npm package now bundles the extension build with a key-pinned, - pre-allowlisted id — `reins extension` stages it at `~/.reins/extension` for + pre-allowlisted id. `reins extension` stages it at `~/.reins/extension` for Chrome's Load unpacked, no `reins allow` step. See docs/SIDELOAD.md. diff --git a/packages/extension/src/lib/policy.ts b/packages/extension/src/lib/policy.ts index a5bf7e4..21f83fd 100644 --- a/packages/extension/src/lib/policy.ts +++ b/packages/extension/src/lib/policy.ts @@ -82,7 +82,7 @@ export function tightenPolicy(patternInput: string, tier: Tier): Promise existing?.tier ?? effectiveTier(p, pattern.startsWith("*.") ? pattern.slice(2) : pattern); if (!tighterThan(tier, current)) { throw new PolicyDenied( - `policy_tighten can only restrict: "${pattern}" is already ${current} — grants require the extension popup`, + `policy_tighten can only restrict: "${pattern}" is already ${current}. Grants require the extension popup`, ); } const rules = existing @@ -134,8 +134,8 @@ export async function ensureAllowed(method: GatedMethod, host: string | undefine const label = host ?? "this tab"; const err = new PolicyDenied( tier === "deny" - ? `blocked by policy: ${label} is denied — change its tier from the reins extension popup` - : `blocked by policy: ${label} is read-only — grant full access from the reins extension popup`, + ? `blocked by policy: ${label} is denied. Change its tier from the reins extension popup` + : `blocked by policy: ${label} is read-only. Grant full access from the reins extension popup`, ); err.meta = { host, tier }; throw err; diff --git a/packages/web/package.json b/packages/web/package.json index 0fbba31..99b2072 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -5,7 +5,8 @@ "type": "module", "scripts": { "dev": "vite dev --port 3000", - "build": "vite build && pagefind --site dist/client", + "build": "vite build && pagefind --site dist/client && node scripts/check-prose.mjs", + "check:prose": "node scripts/check-prose.mjs", "preview": "vite preview", "typecheck": "tsc --noEmit", "deploy": "wrangler deploy" diff --git a/packages/web/public/404.html b/packages/web/public/404.html index 61bc559..e41e451 100644 --- a/packages/web/public/404.html +++ b/packages/web/public/404.html @@ -4,7 +4,7 @@ - Page not found — reins + Page not found · reins