commit 78da8fbbdec74584ab21383655f1ac017cabbb8e Author: Hermes Dev Date: Sun Nov 1 17:40:06 2026 +0200 theseus.x site lives under WebsiteDev; whitepaper source saved beside the page The website sources are grouped under WebsiteDev/. site-theseus-x moves there for real (the other site folders are still junctions from WebsiteDev to the root); a junction at the old root path keeps existing scripts working and is ignored so the files are tracked once. Docs, the icon script and the mirror example follow the new path. whitepaper/whitepaper.md is the Markdown the published page was built from, so the text can be edited and rebuilt without the original document. diff --git a/assets/favicon.svg b/assets/favicon.svg new file mode 100644 index 0000000..9d76aa7 --- /dev/null +++ b/assets/favicon.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/assets/logo.svg b/assets/logo.svg new file mode 100644 index 0000000..8e034b4 --- /dev/null +++ b/assets/logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + theseus.x + + diff --git a/assets/thread.svg b/assets/thread.svg new file mode 100644 index 0000000..6523c3e --- /dev/null +++ b/assets/thread.svg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/brand/avatar.svg b/brand/avatar.svg new file mode 100644 index 0000000..b36d5fe --- /dev/null +++ b/brand/avatar.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/brand/banner.svg b/brand/banner.svg new file mode 100644 index 0000000..c298e06 --- /dev/null +++ b/brand/banner.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + theseus.x + + + + + The browser that speaks Bitcoin Cash names + + + + + NATIVE BCNR RESOLUTION · WINDOWS + ANDROID + + diff --git a/brand/favicon.svg b/brand/favicon.svg new file mode 100644 index 0000000..d70de43 --- /dev/null +++ b/brand/favicon.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/brand/index.html b/brand/index.html new file mode 100644 index 0000000..94eda48 --- /dev/null +++ b/brand/index.html @@ -0,0 +1,353 @@ + + + + + +Brand — Theseus.X + + + + + + + + + + + + + + + + + + +
+
🎨
+

Theseus.X brand kit

+

The Navigator compass mark, wordmark, avatar, banner, favicon, and colour palette. + Every asset ships as SVG so it stays crisp at any size and reads correctly in light and dark + contexts. Free to reuse with attribution.

+
+ +
+ + + +
+

Avatar & favicon

+

Square icons for social profiles, Nostr, GitHub, and browser tabs.

+
+
+
theseus.x avatar
+

Avatar SVG · 512×512

+

Square profile picture. Social platforms apply + their own round mask; the corner radius keeps the compass intact when clipped.

+ +
+
+
theseus.x favicon
+

Favicon SVG · 64×64, 12px radius

+

Rounded browser-tab icon. Scales down to 16×16 + cleanly; used on every Theseus.X page.

+ +
+
+
+ + + +
+

Colour palette

+

Silent Mode family colours. Dark chromes are the default; acid green is the + accent that signals "this is Theseus.X", and bearing red is reserved for the compass mark's + north triangle — never used for links, buttons, or generic UI accents.

+
+
Acid#d6ff3d — accent, links, active nav
+
Bearing#e04e4e — compass north, mark only
+
Background#0b0e14 — page canvas
+
Panel#141a24 — cards, elevated surfaces
+
Panel 2#18202c — nested / step items
+
Ink#e7eaf1 — body text on dark
+
Muted#8b98a9 — secondary text
+
Dim#5e6678 — tertiary text, dividers
+
OK#4fd1a5 — success / verified chain
+
Warn#ffc75f — caution, holds
+
+
+ +
+

Typography

+

System font stack, so nothing needs to load and every OS renders the wordmark in + the same family the reader is used to.

+
+

The quick brown fox

+

jumps over the lazy dog · 0123456789 · theseus.x

+
system-ui, -apple-system, Segoe UI, Roboto, sans-serif
+
+
The wordmark's .x is a <tspan fill="#d6ff3d"> — + don't render the whole word in acid. The compass north triangle is the only place + bearing red appears; don't add red anywhere else in the chrome.
+
+ +
+

Usage & trademark

+

These files are the marks of Theseus and Silent Mode, not open-licensed artwork: + the code is MPL-2.0, the name and the compass are reserved, and the + trademark note says what that allows. Use them to refer to Theseus in + third-party tools that integrate with Theseus.X (BCNR name explorers, wallet bridges, blog + posts, review sites), never as your own mark. Keep the compass proportions intact and don't + recolour the acid to a similar-but-different green — palette drift is what makes brands look + untrustworthy over time.

+
    +
  • Do use the mark to indicate "opens in Theseus" or "supports BCNR".
  • +
  • Do shrink to the favicon at 16×16 — the SVG scales cleanly.
  • +
  • Do use the monochrome logo where the acid green would clash.
  • +
  • Don't imply endorsement of a specific product without asking.
  • +
  • Don't ship a fork, a product or a name under this name or mark — a fork is welcome under its own.
  • +
  • Don't stretch, skew, or re-tint the compass.
  • +
  • Don't introduce a second accent colour — acid green is the only one.
  • +
+
+ +
+ + + + + + diff --git a/brand/logo-mono.svg b/brand/logo-mono.svg new file mode 100644 index 0000000..c52f8a8 --- /dev/null +++ b/brand/logo-mono.svg @@ -0,0 +1,17 @@ + + + + + + + + + + theseus.x + + diff --git a/brand/logo.svg b/brand/logo.svg new file mode 100644 index 0000000..bcc33e2 --- /dev/null +++ b/brand/logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + theseus.x + + diff --git a/dev/claude-connector/DESIGN-claude-connector.md b/dev/claude-connector/DESIGN-claude-connector.md new file mode 100644 index 0000000..e962646 --- /dev/null +++ b/dev/claude-connector/DESIGN-claude-connector.md @@ -0,0 +1,208 @@ +# Claude connector — design and plan + +An opt-in MCP server built into Theseus, so a Claude client on the same +computer (Claude Code, Claude Desktop) can work inside the user's real, +signed-in profile: open a tab, read it, click, type, paste rich text into an +editor, upload a file. + +## Why + +On 2026-10-22 a Claude Code session had to paste a prepared story into a +Medium draft as the user's author account. The Claude desktop app's built-in +browser could not do it: its synthetic paste arrived empty and its typed text +never reached Medium's editor. The two other routes are worse: + +- `--remote-debugging-port` on the main profile opens a browser-wide DevTools + port. Any local process can attach and read every tab, cookie and session. +- Theseus is a desktop app, and these sessions have no computer-use access to + drive it from outside. + +So the browser itself has to offer a narrow, user-controlled door. + +## Shape + +A built-in add-on, `bundled-addons/claude-connector/`, following the Pithos +pattern: `activate(api)` starts a small HTTP server on `127.0.0.1` from the +main process and registers a sidebar panel. What it needs from the browser +that no add-on API offered yet comes through one new host capability, +`tab-control`, which only built-in add-ons can hold. + +``` +Claude Code ──HTTP (Bearer token)──► 127.0.0.1:/mcp ─┐ +Claude Desktop ─stdio─► stdio-shim.cjs ──HTTP──────────────┤ + ▼ + claude-connector add-on (main process) + policy: allowlist · password guard · upload folders · log + │ api.tabControl (tab-control capability) + ▼ + main.js: tabs, open/close/navigate, indicator + │ webContents.debugger (CDP, per tab) + ▼ + one tab's page +``` + +### Off by default + +- The add-on ships with `"defaultEnabled": false`. main.js puts any built-in + add-on with that flag into `settings.disabledAddons` the first time it sees + it (remembered in `settings.addonsDefaultOffSeen`, so the user's later + choice is never overridden). The user turns it on in **Settings › + Extensions › Claude connector**. +- While it is off, nothing listens. When it is on, the panel has a second + switch, **Accept connections**, on by default once the extension is + turned on, so the stop button can close the port without uninstalling + anything. + +### Transport and authentication + +- MCP **Streamable HTTP** at `http://127.0.0.1:/mcp`. POST carries + JSON-RPC; GET opens an SSE stream (kept alive with comments, used as the + "connected" signal); DELETE ends a session. +- Binds `127.0.0.1` only. The port is picked once per install (first free in + 47800–47899, else any) and kept, so the config the user pasted stays valid. +- A random 256-bit token per install, base64url. Every request needs + `Authorization: Bearer `, compared in constant time. +- DNS-rebinding and browser guard: the `Host` header must be + `127.0.0.1:` or `localhost:`, and any request carrying an + `Origin` header is refused. A web page cannot reach the server even if it + guessed the token. +- **Revoke** in the panel issues a new token, which drops every client at + once. **Stop** (toolbar chip or panel) closes the port. +- Claude Desktop speaks stdio, so the add-on ships `stdio-shim.cjs`, run by + the Theseus executable itself in Node mode (`ELECTRON_RUN_AS_NODE=1`, the + same mode the extension sandbox already uses). It reads the port from + `extensions-data/claude-connector/endpoint.json` and the token from + `THESEUS_MCP_TOKEN`, and relays line-delimited JSON-RPC to the HTTP endpoint. + +### Per-tab control, no browser-wide port + +- Each controlled tab gets `webContents.debugger` (CDP 1.3) attached on its + own webContents. No `--remote-debugging-port`, no DevTools target list. +- main.js already attaches that debugger to freeze background tabs + (`setTabFrozen`). The connector shares the attachment: it attaches only if + nothing is attached, and detaches only what it attached itself. +- Only ordinary web tabs (`http:`, `https:`) are controllable. Settings, + add-on pages, the home page, `file:` and internal schemes are refused: + a Settings tab would let a client switch the connector's own safety + settings. +- A dormant restored tab is loaded and a frozen one thawed before it is used. + While a tab is controlled it gets focus emulation (`Emulation. + setFocusEmulationEnabled`), so editors that check `document.hasFocus()` + accept input while the Theseus window is in the background. + +### Host capability `tab-control` (addons-host.js + main.js) + +`api.tabControl`, built-in add-ons only, manifest must declare `tab-control`: + +| call | does | +| --- | --- | +| `list()` | every tab: id, title, url, kind (`web`/`settings`/`addon`/`home`), active, pending | +| `webContents(id)` | the tab's webContents, after loading a dormant tab and thawing a frozen one; throws for non-web tabs | +| `open(url, {background})` | new tab through main's own `createTab` (so BCNR names resolve as typed) | +| `navigate(id, url)` | main's `navigateTab` | +| `close(id)`, `activate(id)` | as the tab strip does | +| `setStatus({connected, clients, busy, tabIds})` | drives the toolbar indicator and the per-tab marker | +| `onStop(cb)` | the indicator's stop button | + +### Indicator + +- A chip in the toolbar, next to Downloads: **Claude** with a dot that pulses + while a tool call runs. Visible whenever a client is connected (an SSE + stream is open or a request arrived in the last 2 minutes). Clicking it + opens the connector panel; its ✕ stops the connector at once (closes the + port, ends sessions, detaches every debugger). +- Each tab the client is controlling gets an outline marker in the tab strip. + +### Tools + +| tool | how | +| --- | --- | +| `list_tabs` | `tabControl.list()`; tabs on sites not yet approved show only their host | +| `open_tab(url, background?)` | approval for the target origin first | +| `navigate(tab_id, url \| action)` | url, or `back`/`forward`/`reload` | +| `close_tab(tab_id)`, `activate_tab(tab_id)` | | +| `screenshot(tab_id, full_page?)` | `Page.captureScreenshot`, scaled to CSS pixels so coordinates match `click(x, y)`; a hidden tab is brought to the front first (hidden views render no frames) | +| `read_page_text(tab_id, max_chars?)` | `innerText` read in an isolated world | +| `snapshot(tab_id, interactive_only?)` | `Accessibility.getFullAXTree`, compacted to `role "name" [ref=eN]` lines; refs map to `backendDOMNodeId` | +| `click(tab_id, ref \| x,y)` | `DOM.scrollIntoViewIfNeeded` + `DOM.getContentQuads`, then `Input.dispatchMouseEvent` press/release | +| `type(tab_id, text, ref?)` | `DOM.focus`, then `Input.insertText` per line and a real Enter (`Input.dispatchKeyEvent`) between lines; `mode: "keys"` sends every character as keyDown/keyUp | +| `press_key(tab_id, key, modifiers?)` | Enter, Tab, Backspace, arrows, ctrl+a … | +| `paste_html(tab_id, html, ref?)`, `paste_text(tab_id, text, ref?)` | write the real clipboard (`clipboard.write({html, text})`), `webContents.paste()`, then put the user's clipboard back | +| `upload_file(tab_id, ref, path)` | `DOM.setFileInputFiles` on an `` | +| `wait_for(tab_id, selector \| text, timeout_ms?)` | polls in an isolated world | +| `evaluate(tab_id, expression)` | `Runtime.evaluate` in the page; listed only while **Allow evaluate** is on (off by default) | + +Page reads run in an isolated world (`Page.createIsolatedWorld`), so page +scripts can neither see them nor tamper with the helpers. + +### Safety + +- **Per-site allowlist.** The first time a client acts on an origin (opens it, + navigates to it, reads or drives a tab on it), Theseus asks: *Allow Claude + to control medium.com?* — **Allow** (remembered), **This session only**, or + **Don't allow**. A refusal is remembered for 10 minutes, so a client cannot + spam the prompt. The panel lists approved sites with a remove button. +- **Never types into password fields.** Before `type`, `press_key`, + `paste_*` and `evaluate`-free input, the focused element (followed into + shadow roots and same-origin frames) is checked in an isolated world: + `input[type=password]`, `autocomplete=current-password|new-password| + one-time-code`, or a cross-origin frame that cannot be inspected → refused. + `click` on a ref that is a password field is refused too, so a client + cannot focus one by ref and then type. +- **Upload folders.** `upload_file` resolves the real path (symlinks and + junctions followed) and refuses anything outside the folders the user added + in the panel (none by default), anything that is not a regular file, and + files over 100 MB. The ref must be an ``. +- **evaluate** is a separate switch, off by default, with a warning in the + panel: it runs arbitrary script as the page. +- **Action log.** Every call is logged with an ISO timestamp, the tool, the + tab's origin, a short summary of the arguments (typed and pasted text is + logged by length and first 60 characters) and the outcome. The panel shows + the newest entries live; the log is kept in + `extensions-data/claude-connector/actions.log` (JSON lines, rotated at 1 MB). + +### What it does not defend against + +- A program already running as the user. The token sits in the add-on store + (`extensions-data/claude-connector.json`) next to the profile's cookies, + which such a program can read anyway. The connector keeps the door shut to + web pages and to other machines, and keeps the user in charge of which sites + a connected client may touch. +- A site that changes a field's type after the focus check. The check runs + right before each input; what the page does in between is up to the page. + +## Files + +- `bundled-addons/claude-connector/` — `addon.json`, `index.js` (activate, + settings, panel messages), `lib/mcp-server.cjs` (HTTP + JSON-RPC + + sessions), `lib/tab-driver.cjs` (CDP per tab), `lib/tools.cjs` (tool + definitions and handlers), `lib/policy.cjs` (allowlist, upload paths, + password guard script), `lib/action-log.cjs`, `panel.html`, + `stdio-shim.cjs`, `README.md`. +- `addons-host.js` — `tab-control` capability and `api.tabControl`. +- `main.js` — the `tabControl` hook, indicator state, `defaultEnabled:false` + handling, `claude-connector` kept as a reserved first-party id. +- `preload.js`, `chrome.html` — the indicator chip and the tab marker. +- `tests/claude-connector.test.mjs` — launches Theseus on a scratch profile + (`THESEUS_USER_DATA`, `THESEUS_NO_UPDATE_CHECK`), with the connector + pre-enabled in that profile, and drives a local test page over MCP: type + into a rich editor, paste HTML with headings and links, upload a PNG, + refuse a password field and an upload outside the folder. +- `tests/claude-connector-unit.test.mjs` — protocol, auth and policy checks + that run under plain Node. +- README section with the `claude mcp add` command. + +## Steps + +1. Host: `tab-control` capability in addons-host.js; hook, indicator state, + default-off handling in main.js; chip in chrome.html/preload.js. +2. Add-on: server, driver, tools, policy, log, panel, shim. +3. Tests: unit (plain Node), then the Electron end-to-end test. +4. Docs: README section, add-on README. + +## Not in this round + +- Cross-origin iframes (OOPIFs) are not driven; typing into one is refused + because its focused element cannot be checked. +- No per-tool approval prompts beyond the per-site one. +- No remote (non-loopback) clients, by design. diff --git a/dev/index.html b/dev/index.html new file mode 100644 index 0000000..4c6711c --- /dev/null +++ b/dev/index.html @@ -0,0 +1,363 @@ + + + + + +Developers — Theseus.X + + + + + + + + + + + + + + + +
+
+ +
+

Theseus for developers

+

+ Everything you need to extend Theseus Navigator, or borrow its superpower — on-device + Bitcoin Cash Domain Name resolution — for whatever you are building. Theseus ships + Ariadne's Thread as a bundled plug-in; the same resolver can live + inside your app too. +

+
+ +
+ +
+

Resolve BNS names from your own code

+

The authoritative developer page is on Ariadne.X — the resolver is the Ariadne project. It + covers the BNS1 protocol in one page, the record types, a 60-line Node.js resolver you can + run as-is, and the pointers to every production implementation.

+
+
+

→ Resolve a BNS name from your own code

+

Protocol, record types, minimum resolver, reference implementations, public endpoints.

+ ariadne.x/dev +
+
+

→ Ariadne's Thread

+

What it is, how it works, system-wide mode, trust model.

+ ariadne.x/thread +
+
+

→ Protocol spec

+

The authoritative BNS1 specification on the forge.

+ PROTOCOL.md ↗ +
+
+
+ +
+

Add BNS to your own app

+

If you ship any kind of app with a browser surface — Electron, native, game launcher, mail + client — your users can resolve .bch, .x, .p2p as soon + as Ariadne's Thread is on the machine. Three paths:

+ +

Easiest — chain-install the Windows service

+

Bundle the Thread installer and run AriadneResolver-Setup.exe /S after your own + install finishes. Theseus does this today. The resolver registers an NRPT rule that only + affects BNS TLDs, so everything else on the machine is untouched.

+ +

JavaScript / Electron — import the resolver

+
import { resolveName } from
+  "https://code.silentmode.st/silentmode/ariadne/raw/branch/master/Argus/src/lib/resolver-web.js";
+
+const { records } = await resolveName("sirius.x");
+// records.s3 → "bns/sirius.x/"
+ +

Any language — HTTP API against a local Thread

+

If the Thread is installed, it exposes a local HTTP API on 127.0.0.1:4626:

+
GET /api/name/<name>
+GET /api/dns/<name>
+GET /health
+ +

Full embedding guide, platform coverage table, and the NSIS/Inno chain-install snippets + live on ariadne.x/dev/#embed.

+
+ +
+

Build an extension

+

Extensions are everyone's add-ons: published by any BCNR name holder, signed by that + name's wallet, verified on your users' devices before a byte runs. They are not plug-ins — + plug-ins are first-party and ship inside Theseus (see Plug-ins).

+ +

Publish an extension

+
    +
  1. Register a .x name on sirius.x.
  2. +
  3. Build your extension tarball — same manifest as Chrome extensions plus a Silent Mode + addon.json.
  4. +
  5. Upload it with PUT /api/ext, signed with the wallet that owns the name.
  6. +
  7. Theseus verifies the signature against the on-chain owner before installing. Updates work + the same way; your users' Theseus checks for a newer updates.json on your + own Sia bucket.
  8. +
+

Walk-through: theseus.x/extensions/publish.

+
+ +
+

Connect Claude to Theseus

+

The built-in Claude connector is a local MCP server: Claude Code or Claude Desktop on the + same computer can open and read your tabs, click, type into rich editors, paste formatted text and + upload files, in the profile where you are already signed in. It is off until you turn it on, and + it reaches nothing beyond this computer.

+ +

Set it up

+
    +
  1. Turn it on in Settings › Extensions › Claude connector. Its panel (the connector icon in + the toolbar) shows the address, the token, the sites you have approved, the upload folders and a + live log.
  2. +
  3. Copy the command from the panel and run it once. --scope user makes it available in + every project:
  4. +
+
claude mcp add --scope user --transport http theseus http://127.0.0.1:47800/mcp --header "Authorization: Bearer <token from the panel>"
+

claude mcp list should then show theseus as connected. For Claude Desktop, + the panel's Claude Desktop tab has a ready claude_desktop_config.json entry. It runs + a small stdio relay through the Theseus executable itself, so there is nothing else to install.

+ +

What Claude can do

+

list_tabs, open_tab, navigate, activate_tab, + close_tab, screenshot, read_page_text, snapshot + (the accessibility tree, with a ref per element), click, type, + press_key, paste_html, paste_text, upload_file, + wait_for, and evaluate while you allow it. Typing is real key input and + pastes go through the system clipboard and the browser's own paste, so editors such as Medium's + accept them; your clipboard is put back afterwards.

+ +
What keeps you in charge. The connector listens on 127.0.0.1 only, behind a + random token; requests from web pages are refused. There is no browser-wide debugging port: each + tab is driven on its own. Theseus asks the first time Claude acts on a site. Password and one-time-code + fields are always refused, files can be uploaded only from folders you add, and running page scripts + (evaluate) is a separate switch, off by default. Every action is logged with its time. + The toolbar chip's ✕ stops the connector at once; Revoke token cuts off every client you set + up.
+

Design notes: + DESIGN-claude-connector.md + (hosted here · + forge mirror ↗). + The connector shipped with Theseus 0.3.107.

+
+ +
+

Plug-ins (first-party)

+

Plug-ins are the few add-ons Silent Mode builds, signs with the operator key, and ships as + part of Theseus — Aegis wallet, Ariadne's Thread, Shield blocker. Users enable or disable + them from Settings › Plug-ins.

+

If you have an idea for one, open an issue on + silentmode/theseus first — the + plug-in surface is intentionally small and we triage proposals before accepting code.

+
+ +
+

Where everything lives

+ +
+ +
+ + + + + diff --git a/dl/index.html b/dl/index.html new file mode 100644 index 0000000..8ecece7 --- /dev/null +++ b/dl/index.html @@ -0,0 +1,46 @@ + + + + + +theseus.x/dl — downloads + + + + + +
+

Downloads

+

These links always point at the current build. Verify the SHA-256 on theseus.x before running.

+
Theseus Navigator for Windowsinstaller · v0.3.48
Installer
+
Theseus Navigator, portablesingle .exe, no install
Portable
+
Ariadne for AndroidAPK · v0.18
APK
+
Ariadne's Thread for Windowssystem-wide resolver, installed by Theseus's setup by default
Installer
+

Machine-readable: releases-manifest.json · all releases: silentmode.st/releases

+
+ + + diff --git a/extensions/index.html b/extensions/index.html new file mode 100644 index 0000000..13406a5 --- /dev/null +++ b/extensions/index.html @@ -0,0 +1,512 @@ + + + + + +theseus.x — Extensions + + + + + + + + + + + + + + + + +
+

Theseus Extensions

+

Add-ons for Theseus Navigator — the ones that ship in the box and the ones anyone can + publish. Every package is signed (by the Silent Mode operator, or by the publisher's own BCDN name) + and verified on your device before install. No cert authority, no app-store gatekeeper. + First-party plug-ins (Aegis, Ariadne's Thread) have their own page.

+ +
+ +
+ +
+

Bundled extensions

+

Ship with every Theseus build and update over the operator-signed channel. Versions and hashes + are read from each add-on's signed updates.json when this page loads.

+ +
+

📸 Screenshot current 0.6.5

+
Capture the current tab — visible viewport, entire scrollable page, or a rectangle + you draw. The capture opens in a full-tab editor: crop, annotate, line tool, resizable text, + redact, save.
+
Settings › Extensions ·
+
+ Install in Theseus + Download tarball +
Updates: signed updates.json · SHA-256 verified before every install
+
+
+
Signed update manifest — SHA-256 verified before install
+ + +
sha25687ae730d329ff9f552bbf513efba19bd14bb016bb90e24f7b62868434dadda60
+
+
+ +
+

📝 Notepad bundled 0.1.0

+
Right-sidebar notes. Text lives on this machine only and autosaves as you type.
+
Settings › Extensions · no update channel yet — ships with each Theseus release.
+
+ Ships with Theseus — nothing to install; appears under Settings › Extensions on first launch. +
+
+ +
+

🌐 Translate bundled 0.1.0

+
Select text on any page and right-click Translate — the sidebar panel shows the translation, + with LibreTranslate or Google as the backend (your choice in the panel). Nothing is sent until you ask.
+
Settings › Extensions · no update channel yet — ships with each Theseus release.
+
+ Ships with Theseus — nothing to install; appears under Settings › Extensions on first launch. +
+
+ +
+

PDF Editor current 0.1.0

+
Open a PDF in a full tab — read, highlight, mark up, reorder or rotate or drop pages, + fill form fields, save a new copy. Annotations are written as real PDF content, the original + is never touched. Opens from the toolbar's PDF Editor menu or the right-click Open PDF in editor + on any link.
+
Settings › Extensions · signed update channel
+
+ Install in Theseus + Download tarball +
Updates: signed updates.json · SHA-256 verified before every install
+
+
+
Signed update manifest — SHA-256 verified before install
+ + +
sha256—
+
+
+ +
+

Claude connector bundled 0.1.0

+
Lets Claude Code or Claude Desktop on your computer work in your signed-in tabs through a + local MCP connection: read pages, click, type into rich editors, paste formatted text, upload files. + Off until you turn it on. Theseus asks before Claude touches each new site, never lets it type into + a password field, uploads only from folders you choose, and logs every action. A Claude chip + in the toolbar shows while a client is connected; its ✕ stops everything.
+
Settings › Extensions · off by default · ships with the next Theseus release (after 0.3.106)
+
+ Ships with Theseus — nothing to install. Setup: theseus.x/dev/#claude. +
+
+ +
No bundled extension matches that search.
+
+ +
+

Community extensions

+

Published by BCDN name holders, signed with the wallet that holds the name. Open this page in Theseus + and click Install in Theseus — or use Settings › Extensions › Community. Either way Theseus fetches the + package itself and re-checks every signature against the name's current owner in its own chain index — the relay + cannot substitute code under a trusted name.

+
Loading the catalog…
+
No community extension matches that search.
+
Trust, stated plainly. The signature proves who published an extension, not that it + is safe. A community extension runs in its own sandboxed process: it can read only its own folder, cannot start + other programs, and cannot see your vault, your wallet or the browser's memory. It reaches Theseus only through + the capabilities its manifest declares, and it can use the network. Its panel and anything it injects + into pages still do what those capabilities allow, so read the capability list before installing. Older Theseus + builds ran extensions inside the browser itself — keep Theseus up to date.
+
+ +
+

Build and publish your own

+

An extension is a folder with an addon.json and a main script. Package it, sign it with + the wallet that holds one of your BCDN names, and it is listed above within a minute. Nothing to apply for.

+
+
    +
  1. Write it. The manifest and a CommonJS entry that exports activate(api): +
    my-ext/
    +  addon.json      {"id":"my-ext","name":"My Extension","version":"1.0.0",
    +                   "description":"…","author":"you.bch","icon":"✨",
    +                   "main":"index.js","capabilities":["sidebar-panel"]}
    +  index.js        module.exports = { activate(api) {
    +                    api.registerSidebarPanel({ id:"main", title:"My Extension", icon:"✨", page:"panel.html" });
    +                  } };
    +  panel.html      plain HTML, runs in the sidebar; window.silentmode.storage for local state
    + The bundled Notepad + is the smallest complete example; addons-host.js + is the full API. id is [a-z0-9._-], 2–64 chars, and belongs to the first name that publishes it.
  2. +
  3. Mind the sandbox. Your main script runs in its own process, not inside the browser: +
      +
    • It can read its own folder and write to api.dataDir, nothing else on disk. No child processes, workers or native modules.
    • +
    • Calls into Theseus return promises, api.tabs.active() included. api.storage.get stays synchronous.
    • +
    • api.require and api.import are not available: bundle the modules you need in the package.
    • +
    • Request filters and site routes are for built-in add-ons. The vault is reachable only as api.vault.derive under your own id and api.vault.requestUnlock, where Theseus draws the prompt.
    • +
    • api.features.sandboxed is true when this applies.
    • +
  4. +
  5. Test it locally. Settings › Extensions › Open extensions folder, drop the folder in, Reload.
  6. +
  7. Package it. tar -czf my-ext-1.0.0.tar.gz -C my-ext . (max 8 MB; addon.json at the root or in a single top folder).
  8. +
  9. Publish it. Open Publish an extension → + sign in with the recovery phrase of the wallet that holds your name, pick the package, and sign. The gateway + verifies the signature against the name's on-chain owner, stores the package on Sia, and updates the catalog. + New versions: same page, higher version number, same name.
  10. +
+
+

Prefer the command line? The upload is one signed PUT: + PUT https://silentmode.st/api/ext/<your-name>/<id>/<version> with the tarball as body and headers + x-bns-ts, x-bns-sig (over sha256("BNS-EXT1\n<name>\n<id>\n<version>\n<sha256>\n<ts>")) + and x-bns-entry-sig (over sha256("silentmode.extension-v1|<id>|<version>|<sha256>|<name>")), + both 65-byte BCH message signatures by the name's owner key.

+
+ + + +
+

How updates work

+

Same mechanism a Firefox XPI update uses, cut down to what a name-native browser needs.

+
+
    +
  1. Every 30 seconds after boot Theseus fetches updates.json for each installed add-on that declares an updateURL in its manifest.
  2. +
  3. Each entry carries an Ed25519 signature over silentmode.addon-update-v1|<id>|<version>|<sha256>. Theseus verifies against a small set of operator public keys baked into the build. Unsigned or badly-signed entries are dropped silently.
  4. +
  5. If a signed version beats what's installed, Theseus downloads the tarball, re-checks its sha256, and stages the new copy under <userData>/addons-updates-staged/<id>-<version>/.
  6. +
  7. On the next Theseus launch, promoteStagedUpdates moves the staged copy into addons/<id>/. Any prior copy is renamed to addons-backups/<id>-<oldver>-<timestamp>/ so hand-edits survive.
  8. +
  9. Settings › Extensions has a Check for updates button and shows what is staged; first-party plug-ins (Aegis) can also stage and restart from their own panel.
  10. +
+
+
+ +
+

Operator key

+

The Ed25519 public key baked into Theseus 0.3.19+. + Rotation is by adding a new pubkey and shipping a Theseus release that carries both, then a follow-up release removing the old — during the overlap, entries can be signed by either.

+
732b1263a236b0030383a2376597cfa43c3624b3ca2912a46134f8f2a06e6012
+

Generated 2026-09-07. Held offline by the Silent Mode operator.

+
+ +
+

Roadmap

+

The endpoint is generic — any bundled add-on can advertise an updateURL. + Concrete next-steps:

+
    +
  • On-BCNR pubkey publication so rotation is auditable end-to-end.
  • +
  • Multi-sig (N-of-M operator keys) so a single compromised private key can't push code.
  • +
  • Extend the channel to Notepad (bundled, no channel yet).
  • +
+
+ + + +
+ + + + diff --git a/extensions/js/bns-register.js b/extensions/js/bns-register.js new file mode 100644 index 0000000..46da9b1 --- /dev/null +++ b/extensions/js/bns-register.js @@ -0,0 +1,7 @@ +import{generateBip39Mnemonic as qe,deriveSeedFromBip39Mnemonic as ne,deriveHdPrivateNodeFromSeed as Ke,deriveHdPath as We,secp256k1 as oe,hash160 as ze,encodeCashAddress as no,lockingBytecodeToCashAddress as Ge,cashAddressToLockingBytecode as oo,binToHex as je,hexToBin as so,utf8ToBin as ie,binToBase64 as Nt,base64ToBin as Dt}from"@bitauth/libauth";var mt="bchtest";var $t="m/44'/145'/0'",Je=5,kt="bns.wallet.v1";function ae(e){let t=ze(e);return Uint8Array.from([118,169,20,...t,136,172])}function se(e,t,n){let o=Ge({bytecode:ae(e),prefix:t,tokenSupport:n});if(typeof o=="string")throw new Error(`address encoding failed: ${o}`);return o.address}var Rt=class e{constructor(t,n,o=mt,s=$t){this.mnemonic=t,this.prefix=o,this.accountPath=s,this._root=Ke(n),this._keys=new Map,this._derive()}static async create(t=mt,n=$t){let o=qe();return new e(o,await ne(o),t,n)}static async fromMnemonic(t,n=mt,o=$t){let s=String(t).trim().toLowerCase().replace(/\s+/g," "),r=await ne(s);if(typeof r=="string")throw new Error(`invalid recovery phrase: ${r}`);return new e(s,r,n,o)}_derive(){this.receive=[],this.change=[];for(let[t,n]of[[0,this.receive],[1,this.change]])for(let o=0;ot.address)}keyFor(t){let n=this._keys.get(t);return n?{privateKey:n.privateKey,publicKey:n.publicKey}:null}signMessage(t,n){if(!(t instanceof Uint8Array)||t.length!==32)throw new Error("signMessage expects a 32-byte Uint8Array digest");let o=n||this.address,s=[...this.receive,...this.change].find(h=>h.address===o);if(!s)throw new Error(`no key for address ${o}`);let r=oe.signMessageHashRecoverableCompact(s.privateKey,t);if(typeof r=="string")throw new Error(`signMessage failed: ${r}`);let{signature:a}=r,i=r.recoveryId??r.recovery;if(typeof i!="number")throw new Error("signMessage: no recovery id in signature result");let l=31+i,c=new Uint8Array(65);return c[0]=l,c.set(a,1),c}async encrypt(t){let n=crypto.getRandomValues(new Uint8Array(16)),o=crypto.getRandomValues(new Uint8Array(12)),s=await re(t,n),r=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:o},s,ie(this.mnemonic)));return JSON.stringify({v:1,kdf:"PBKDF2-SHA256",iterations:Ft,prefix:this.prefix,salt:Nt(n),iv:Nt(o),ct:Nt(r)})}static async decrypt(t,n){let o=typeof t=="string"?JSON.parse(t):t,s=await re(n,Dt(o.salt),o.iterations??Ft),r;try{r=await crypto.subtle.decrypt({name:"AES-GCM",iv:Dt(o.iv)},s,Dt(o.ct))}catch{throw new Error("wrong password")}return e.fromMnemonic(new TextDecoder().decode(r),o.prefix??mt)}async save(t,n=globalThis.localStorage){n.setItem(kt,await this.encrypt(t))}static async load(t,n=globalThis.localStorage){let o=n.getItem(kt);return o?e.decrypt(o,t):null}static exists(t=globalThis.localStorage){return!!t?.getItem(kt)}static forget(t=globalThis.localStorage){t.removeItem(kt)}},Ft=25e4;async function re(e,t,n=Ft){let o=await crypto.subtle.importKey("raw",ie(String(e)),"PBKDF2",!1,["deriveKey"]);return crypto.subtle.deriveKey({name:"PBKDF2",salt:t,iterations:n,hash:"SHA-256"},o,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}var bt=["wss://silentmode.st/electrum","wss://chipnet.bch.ninja:50004","wss://chipnet.imaginary.cash:50004","wss://tbch4.loping.net:62002"],Lt=class{constructor(t,n){this.url=t,this.WS=n||globalThis.WebSocket,this.id=0,this.pending=new Map,this.buf=""}connect(){return new Promise((t,n)=>{let o=new this.WS(this.url);this.ws=o;let s=async()=>{try{await this.call("server.version",["bns-web","1.4.2"]),t(this)}catch(i){n(i)}},r=i=>n(new Error("electrum ws error: "+(i?.message||this.url))),a=i=>this._onData(typeof i.data=="string"?i.data:i.data.toString());o.addEventListener?(o.addEventListener("open",s),o.addEventListener("error",r),o.addEventListener("message",a)):(o.onopen=s,o.onerror=r,o.onmessage=a)})}_handle(t){let n=this.pending.get(t.id);n&&(this.pending.delete(t.id),t.error?n.reject(new Error(JSON.stringify(t.error))):n.resolve(t.result))}_onData(t){this.buf+=t;let n;for(;(n=this.buf.indexOf(` +`))>=0;){let s=this.buf.slice(0,n).trim();if(this.buf=this.buf.slice(n+1),s)try{this._handle(JSON.parse(s))}catch{}}let o=this.buf.trim();if(o)try{let s=JSON.parse(o);this._handle(s),this.buf=""}catch{}}call(t,n=[]){let o=++this.id;return new Promise((s,r)=>{this.pending.set(o,{resolve:s,reject:r}),this.ws.send(JSON.stringify({id:o,method:t,params:n})+` +`),setTimeout(()=>{this.pending.has(o)&&(this.pending.delete(o),r(new Error(`electrum timeout: ${t}`)))},2e4)})}close(){try{this.ws.close()}catch{}}};async function ce(e=bt,t){let n=[];for(let o of e)try{return await new Lt(o,t).connect()}catch(s){n.push(`${o}: ${s?.message??s}`)}throw Pe(e.length,n)}function Pe(e,t){let n=new Error(`no electrum server reachable (tried ${e}). These use non-standard ports, which some networks and VPNs block: + ${t.join(` + `)}`);return n.code="NO_ELECTRUM",n.failures=t,n}import{cashAddressToLockingBytecode as Ye,lockingBytecodeToCashAddress as Xe,sha256 as Ve,hash160 as co,binToHex as Qe,hexToBin as lo}from"@bitauth/libauth";function le(e){return Qe(Ve.hash(e).slice().reverse())}function _(e){let t=Ye(e);if(typeof t=="string")throw new Error(`bad address "${e}": ${t}`);return t.bytecode}function Ze(e){return le(_(e))}function M(e){let t=_(e),n=String(e).includes(":")?e.split(":")[0]:"bchtest",o=Xe({bytecode:t,prefix:n,tokenSupport:!0});if(typeof o=="string")throw new Error(`cannot token-encode "${e}": ${o}`);return o.address}async function ue(e,t){let n=_(t),o=le(n);return(await e.call("blockchain.scripthash.listunspent",[o,"include_tokens"])).map(r=>({txid:r.tx_hash,vout:r.tx_pos,satoshis:BigInt(r.value),height:r.height,address:t,lockingBytecode:n,token:r.token_data?{category:r.token_data.category,amount:BigInt(r.token_data.amount??0),nft:r.token_data.nft?{capability:r.token_data.nft.capability,commitment:r.token_data.nft.commitment}:void 0}:void 0}))}async function z(e,t){let n=[];for(let o of t)n.push(...await ue(e,o));return n}async function V(e,t){return e.call("blockchain.transaction.broadcast",[t])}import{binToHex as vt,hexToBin as j,utf8ToBin as st,binToUtf8 as de,encodeTransaction as tn,decodeTransaction as po}from"@bitauth/libauth";var en="BNS1",Tt="bchtest:qp8njaa9a3ahg7259s5qajnce526n0rttsq8qwx4jl",Mt="bchtest:qzc4c76vtw9nd49g6x8nmvc0jz62uvk4dc7hmpmerh",I=600n,F=1000n,U=546n,Ct="bch",Ht=200,nn=/^[a-z0-9-]{1,32}$/,he=/^[a-z0-9-]{1,16}$/;function ht(e){let t=String(e).toLowerCase().trim().replace(/^\.+/,"").replace(/\.+$/,"");if(!he.test(t))throw new Error(`invalid TLD "${e}" (allowed: [a-z0-9-]{1,16})`);return t}var St=class extends Error{constructor(t){super(t),this.name="NoGenesisInputError",this.code="NO_GENESIS_INPUT"}},J=class extends Error{constructor(t,{needSats:n,haveSats:o}={}){super(t),this.name="InsufficientFundsError",this.code="INSUFFICIENT_FUNDS",this.needSats=n,this.haveSats=o}};function P(e){let t=String(e).toLowerCase().trim().replace(/\.$/,"").split(".").filter(Boolean),n,o;if(t.length===0)throw new Error(`invalid name "${e}"`);if(t.length===1?(n=t[0],o=Ct):(o=t[t.length-1],n=t[t.length-2]),!nn.test(n)||!he.test(o))throw new Error(`invalid name "${e}" (allowed: