Documentation handbook · Install the prebuilt release
This guide is for changing Weft itself. To use Weft without a source checkout, follow the installation quickstart.
Work in this order: install dependencies, exercise the local harness and demo, then install your source build and try a real phone. Local simulation needs no Supabase project or phone; it does not prove cross-device connectivity.
- Prerequisites and dependencies
- Local validation
- Demo and simulator
- Install and pair a source build
- Deployments
- Custom Supabase
- Dev Tunnel lifecycle
- Node.js 20 or newer.
- For the hosted web app and demo: a current browser.
- Only for building the native Android shell: Android Studio + Android SDK.
- The session extension uses
@github/copilot-sdk, supplied by the CLI or Copilot app host at runtime. Device Station launches require an installed, signed-in CLI.
cd weft
npm ci --workspaces --include-workspace-rootAfter dependencies are installed, these checks exercise core flows without an external relay or physical phone:
# 1. Shared contracts: crypto, pairing handshake, transport, message round-trips
npm test -w @aasis21/weft-shared
# 2. Extension relay end-to-end against a simulated phone (LocalTransport):
# pairing → stream → approval round-trip → prompt → mode switch → session end
node extension/harness/harness.mjs --auto
# 3. Bundle the extension (esbuild; @github/copilot-sdk left external)
npm run build -w @aasis21/weft-extension # -> dormant extension.mjs + lazy activeRuntime.mjs
# 4. Build the mobile app (Vite production build)
npm run build -w @aasis21/weft-mobileFor the repository-wide checks, run npm test, npm run build, npm run lint,
and npm run check-version. Mobile type tests use
npm run test:types -w @aasis21/weft-mobile. For browser journeys, install the
Playwright runtimes once with npx playwright install chromium webkit, then run
npm run test:e2e -w @aasis21/weft-mobile.
cd mobile
npm run dev # open the printed localhost URLExplore's balanced Discover deck, Play, Unwind, and live Copilot dock require no external content provider. The dock only projects activity already available in the active Weft session and returns attention actions to chat. To exercise the Watch category, set an HTTPS URL for an isolated, auto-updating widget before starting or building the mobile app:
$env:VITE_EXPLORE_WIDGET_URL = 'https://your-widget-provider.example/embed/...'
npm run dev -w @aasis21/weft-mobileThe URL is build-time configuration, not a credential and not committed to source. Watch does not load it until the user explicitly chooses Load video feed. The provider runs in a sandboxed iframe with no Weft prompt, repository, filename, tool, agent-activity, channel, or session values appended or posted to it. Weft treats frame load only as navigation, not proof that cross-origin content rendered. Offline or invalid configuration produces an unavailable state; an unverified or blank embed keeps explicit reload, hide, and open-directly controls. The rest of Explore remains offline-capable.
In the app choose Try the demo. It stands up a fake laptop side in-process
(real ECDH keypairs + LocalTransport), completes the real
pair.hello/pair.challenge/pair.proof/pair.ack handshake, then streams scripted
assistant/tool events, an approval card, heartbeats, and reflects mode changes — all
application payloads travel over AES-256-GCM.
The source installer writes code into your personal Copilot extensions directory. Review it before running it in a managed environment.
1. Install the extension (builds + copies the dormant extension.mjs bootstrap, lazy
activeRuntime.mjs, and on-demand pairingCanvas.mjs into
~/.copilot/extensions/weft/, discovered by CLI and local app sessions — that directory holds
installed code only):
.\setup.ps1 # Windows PowerShell
./setup.sh # macOS/Linux
# remove later with ./uninstall.ps1 / ./uninstall.shTransport (Supabase vs. devtunnel) is configured via two small files in ~/.weft/:
weft.config.json (the transport pointer, written by weft set-transport) and
supabase.json (the Supabase URL + anon key, seeded by the installer — with the hosted
defaults, or with your own project's creds if you pass -SupabaseUrl/-SupabaseKey).
They sit alongside projects.json and the devtunnel registry (see weftHome() in
extension/src/projects.mjs), and there is no env var / .env for either (see
transportConfig.mjs / transportFactory.mjs). Because they're separate, weft set-transport supabase (which takes no keys) just flips the pointer back after you've experimented
with devtunnel — your creds are still on disk from the last install, no re-typing. And
re-running setup.ps1/setup.sh (or the site installer) never silently resets or shadows
whatever you've already configured — the installers only re-seed supabase.json if it's
absent or you explicitly passed -SupabaseUrl/-SupabaseKey (or -Force), and
setup.ps1/setup.sh print a reminder to run weft set-transport if no pointer is
configured yet.
2. Get the app on your phone — use the PWA by default:
- Hosted PWA (supported primary distribution): open https://useweft.netlify.app on your phone,
then choose Install app or Add to Home Screen in the browser. It can also run
directly in the browser. Android Chrome uses
BarcodeDetectorfor QR scanning; iOS Safari and Firefox use the in-page jsQR fallback. - Android development shell: Weft does not currently publish an APK. Debug, unversioned, and third-party APKs are not supported release artifacts.
- Local dev server:
cd mobile && npm run devserves the demo on your development computer. For a physical phone, use an HTTPS-served development deployment: an ordinary HTTP LAN address is not a secure context for camera and Web Crypto APIs. Manual paste is available on keyboard-and-mouse devices, not touch-only phones.
Developers who need to build the native shell from source can run npx cap sync android
and use Android Studio, but that build is not a distributed release APK.
3. Pair and drive it. Run weft start. On Windows you can close its terminal once
startup succeeds; Station continues in a manually started per-user background task.
Use weft status and weft stop to manage it. For terminal-owned operation, use
weft start --foreground; other platforms retain foreground mode.
Open https://useweft.netlify.app on the phone, choose Scan QR to pair, and scan the
code. The phone can then start or resume Copilot sessions in registered projects.
For a source-built phone UI, use your HTTPS development deployment instead.
Device Station must remain running for phone-driven project/session discovery, Start,
Open/Resume, and activation of dormant sessions. If the selected Copilot session is
already open, Station uses its user-local lifecycle endpoint to activate that exact
session runtime, including a local Copilot app session; it does not launch a second copilot --resume.
To pair only the current session, /weft shows a terminal QR in CLI or a canvas in
the local Copilot app. You can also ask Copilot to open Weft pairing. Station is not required.
After either path pairs, active prompts, events, approvals, streaming, and terminal data
flow directly between the phone and session over the encrypted relay, bypassing Station
and the local lifecycle endpoint.
Keep the same browser identity when testing reconnection. If its storage has been cleared, stop the station and follow phone replacement. Do not reset pairing as a substitute for diagnosing a relay problem.
After /clear, the replacement Copilot session starts dormant and must be activated
again through Device Station or a new /weft QR.
See advanced usage for per-session transport overrides.
After installing your source build, run weft ui. The default browser opens a
private, loopback-only dashboard; all assets are bundled with the CLI, with no
separate frontend server or repository checkout required. It exposes Station/relay
start, stop and restart, project registration/defaults, settings, local session
activation/disconnection, Station-mediated session launch, pairing and update checks.
Opening the dashboard starts only its local host. Viewing sessions does not activate
them. Closing the page leaves Station, relay and Copilot running; the unused UI host
expires after five minutes without requests once accepted operations finish.
weft ui status checks the host and weft ui stop stops it without touching workloads.
On Windows, normal installation adds Weft Control Center to Start.
weft ui shortcut adds a desktop shortcut. Shortcut targets use installed code,
not a fixed localhost port or pairing URL. Foreground-owned workloads must be managed
in their owning terminal; the UI does not silently take ownership or kill them.
Other platforms can use local configuration/session views but retain foreground
workload commands.
Dev Tunnel sign-in remains an explicit terminal step when required:
devtunnel user login -g. Browser start does not wait on hidden terminal prompts.
Configuration changes explain restart requirements. Stop Relay and trust replacement
require confirmation; unregistering a folder removes only its shortcut registration.
Phone connectivity is based on recent authenticated peer traffic, not a saved pairing record. When no recent evidence is available, the panel reports unknown; watch level and runtime activation are displayed separately. The Control Center does not show transcripts or provide an arbitrary command/file browser.
Installer-source overrides, clean-install and actual code updates remain terminal
operations. Update checks read the published release manifest, not session content.
Do not use weft update to test an unreleased source change; install the source build
explicitly through the documented source installer.
The web app is a static build of
mobile/dist; the docs site is the separatedocs/tree on GitHub Pages. The phone reads relay details from its pairing QR, so a custom relay does not require a separate web-app build. Deploy the app using the release scripts or workflows below so hosted installers and the release manifest stay consistent with the app.
ship.sh / ship.ps1 deploy to Netlify from a laptop. GitHub Actions uses ship.sh
with the target environment's site configuration. The test and production workflows
have different triggers and publication rules:
| Workflow | Environment | Netlify site | URL | Triggers |
|---|---|---|---|---|
Deploy Netlify Production |
production |
useweft |
https://useweft.netlify.app | manual only |
Deploy Netlify Test |
test |
useweft-test |
https://useweft-test.netlify.app | successful main CI + manual |
The test workflow runs after CI succeeds on main and checks out that CI run's commit.
A successful deployment publishes that validated commit; a pending or failed deployment
leaves the previous site in place. Production requires an explicit dispatch:
gh workflow run "Deploy Netlify Production" --repo aasis21/weftDeploying a feature branch. The test environment has no deployment-branch policy, so the test
workflow can be dispatched from any ref — pick the branch in the Actions UI, or:
gh workflow run "Deploy Netlify Test" --ref users/<you>/<feature> -f mode=previewmode=preview (the default for a manual run) is a Netlify draft deploy: it publishes to a unique
throwaway URL and leaves useweft-test.netlify.app alone, so several branches can be in flight at
once. mode=live overwrites the shared test site, as does the automatic deployment after
successful main CI. The resulting URL is printed in the run's job summary.
Each environment holds NETLIFY_AUTH_TOKEN and NETLIFY_SITE_ID. The installers are checked in
with the production origin as their default, so at deploy time the ship scripts resolve the target
site's real URL and rewrite the mobile/dist copies of install.sh / install.ps1 (never the
sources) — a test deploy therefore serves an installer that pulls its own bundles. (A preview
deploy retargets to the test site's canonical URL, not the throwaway draft URL, since the draft URL
is only known after the upload.)
weft update --check
weft updateThe check is read-only and also reports mandatory files that need repair at the current
version. The update uses the bundle set declared by the published release manifest,
verifies every downloaded file, replaces installed code and the Weft skill transactionally,
and preserves ~/.weft/ configuration, registered projects, and persistent pairing
material. Restart Station and restart or reload the Copilot session after updating. PWA updates arrive through
the browser; close and reopen the installed PWA if a newly published version is not visible.
Notification behavior depends on the browser and operating system. Exercise permission prompts and background behavior on a real device; do not treat the local demo as proof of notification delivery. The session thread remains the place to review pending approvals. Do not promise that a backgrounded or killed PWA will wake for a request. See the notification limitations.
- Create a Supabase project.
- Enable Realtime Authorization and add RLS policies on
realtime.messagesthat allow anonymous/authenticated broadcast only within theprivate:weft:*namespace. This is namespace-level gating, not per-user channel authorization. It is stored as code: applysupabase/migrations/to the project (via the Supabase MCP, the Supabase CLIsupabase db push, or by pasting the SQL into the dashboard SQL editor). Channels are opened withconfig.private = true, so joins are denied until this migration is applied (seesecurity.mdandsupabase/README.md). - Wire up the extension's transport (no env var, no
.env— seehosting.md). Supply your project's creds at install time, then select the transport:The mobile side needs no extra wiring — it reads the URL + anon key straight from whatever pairing QR the extension stamps (see# bash installer: seed your own project's creds curl -fsSL https://useweft.netlify.app/install.sh | \ WEFT_SUPABASE_URL="https://YOUR_PROJECT.supabase.co" \ WEFT_SUPABASE_ANON_KEY="YOUR_CLIENT_SAFE_ANON_KEY" bash # (or edit ~/.weft/supabase.json directly), then: weft set-transport supabase
hosting.md). - The caller constructs a
@supabase/supabase-jsclient, callsclient.realtime.setAuth(anonKey)(the anon key is the Realtime access token that RLS authorizes), and passes it tocreateSupabaseTransport({ client, channelId })(theshared/package stays dependency-free by injecting the client). The extension does this automatically onceweft set-transport supabasehas been run.
SupabaseTransportregisters a single catch-all broadcast listener beforesubscribe()and dispatches internally, so subscriptions added afterconnect()still receive events. No subscribe-ordering constraint remains for cross-device use.
The devtunnel transport is operator-run for /weft — the shared local relay +
Microsoft Dev Tunnel is your responsibility to bring up inside a Copilot session, in
exactly the same sense that the Supabase transport expects you to have already spun up a
Supabase project. /weft never spawns the tunnel for you; it just reads the shared
registry (~/.weft/devtunnel.json) and uses whatever's running.
weft start is the exception: the standalone Device Station will provision the relay
itself when one isn't already healthy (see
Self-provisioning with weft start below).
Explicit relay flow (after weft set-transport devtunnel):
# bring up the shared relay; Windows returns after background readiness
weft devtunnel start
# terminal 2 — pair as usual; /weft picks up the running relay
copilot # then run /weft inside the session
# or, standalone:
weft startOn Windows the shared relay is supervised by a manually started Scheduled Task.
Closing the launching terminal does not stop it. Repeated starts reuse the workload,
not competing provisioners. Use weft devtunnel status for health and
weft devtunnel stop to disable restart before stopping it.
Foreground mode (weft devtunnel start --foreground) and other platforms retain a
terminal-owned relay. Every activated session discovers it through
~/.weft/devtunnel.json; /weft never starts infrastructure.
If you run /weft with transport = devtunnel and no relay is running, pairing fails
fast with an actionable error pointing you at weft devtunnel start. This is deliberate
— it mirrors how the Supabase transport won't try to spin up Supabase for you either.
On Windows, weft start validates configuration/projects and completes any required
interactive sign-in, then starts the shared background relay and background Station.
Neither task has boot/sign-in triggers. A failed workload restarts under the same
signed-in user's limited permissions. Station stopping leaves the relay available.
The relay manager checks local service health and public forwarding every 30 seconds.
An unreachable network does not cause endpoint replacement; repeated public rejection
or a dead relay triggers recovery. Saved tunnel ID and local port are preferred.
An endpoint change is explicit: weft status reports endpoint-refresh-needed and
weft start retrieves the updated QR. Direct sessions similarly ask for /weft when
their shared endpoint changes. Ordinary reconnects retain trust and use a fresh secure
handshake automatically, without scanning.
In foreground mode, weft start on the devtunnel transport:
- reuses an already-healthy relay from
~/.weft/devtunnel.jsonif there is one (and then never touches its lifetime — the owning terminal still owns it), otherwise - checks the
devtunnelCLI, and if the account is signed out (including the case wheredevtunnel user showexits 0 but printsLogin token expired.) runsdevtunnel user login -gand re-verifies before continuing, - clears a stale registry entry (a record claiming a relay that is no longer running) and provisions a fresh relay, then
- watches its health every 30s for as long as the station runs, re-provisioning once
if it goes away — if the new relay comes back on a different URL the station moves
itself onto it and prints a fresh QR right in the terminal, so you re-scan once
instead of restarting
weft start(the channel and keys are unchanged), and - releases it on exit — but only if this station is the process that started it. A
relay you brought up with
weft devtunnel startis never torn down by a station shutting down.
With persistent pairing on, teardown keeps the cloud tunnel so the next start comes back
on the same URL and already-paired phones reconnect without re-scanning. Use
weft devtunnel start when you want the relay to outlive individual stations (e.g. so
/weft in Copilot sessions has something to attach to).
Transport (Supabase vs. devtunnel) is not an env var — it's configured via weft set-transport (pointer → ~/.weft/weft.config.json) and, for Supabase, the creds file
~/.weft/supabase.json seeded by the installer (-SupabaseUrl/-SupabaseKey or
WEFT_SUPABASE_URL/WEFT_SUPABASE_ANON_KEY to use your own project;
see hosting.md). The extension has a
small number of unrelated, legitimate tuning env vars:
| Env var | Used by | Meaning |
|---|---|---|
WEFT_CHANNEL_ID |
extension | force a channel id (tests); otherwise random 128-bit |
pairing.md— the ECDH pairing handshake.security.md— threat model & crypto.mode-switching.md— runtime interactive/plan/autopilot switching.