codehost 0.1.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,30 @@
1
+ name: Deploy
2
+
3
+ # Auto-build + deploy the Cloudflare Pages site and signaling Worker on push to
4
+ # main (the "git integration / auto build" for codehost.dev). Needs repo
5
+ # secrets CLOUDFLARE_API_TOKEN (Workers Scripts:Edit + Pages:Edit) and
6
+ # CLOUDFLARE_ACCOUNT_ID.
7
+ on:
8
+ push:
9
+ branches: [main]
10
+
11
+ concurrency:
12
+ group: deploy
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ deploy:
17
+ runs-on: ubuntu-latest
18
+ env:
19
+ CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
20
+ CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - uses: oven-sh/setup-bun@v2
24
+ - run: bun install
25
+ - run: bun run build
26
+ - name: Deploy Pages (app + service worker)
27
+ run: bunx wrangler pages deploy dist/public --project-name codehost --branch main --commit-dirty=true
28
+ - name: Deploy signaling Worker
29
+ run: bunx wrangler deploy
30
+ working-directory: worker
@@ -0,0 +1,39 @@
1
+ name: Release
2
+
3
+ # Publishes to npm via semantic-release using npm trusted publishing (OIDC) —
4
+ # no NPM_TOKEN. Requires: a trusted publisher configured on npm for this repo +
5
+ # this workflow file (release.yaml), id-token: write, semantic-release v25 /
6
+ # @semantic-release/npm v13, and npm >= 11.5.1.
7
+ on:
8
+ push:
9
+ branches: [main]
10
+
11
+ permissions:
12
+ contents: write # semantic-release commits the version bump + tag
13
+ issues: write
14
+ pull-requests: write
15
+ id-token: write # OIDC token for npm trusted publishing
16
+
17
+ concurrency:
18
+ group: release
19
+ cancel-in-progress: false
20
+
21
+ jobs:
22
+ release:
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ with:
27
+ fetch-depth: 0
28
+ persist-credentials: false
29
+ # Provides node + npm; do NOT set registry-url (it writes an .npmrc that
30
+ # breaks semantic-release's auth). Upgrade npm for OIDC support.
31
+ - uses: actions/setup-node@v4
32
+ with:
33
+ node-version: 24
34
+ - run: npm install -g npm@latest
35
+ - uses: oven-sh/setup-bun@v2
36
+ - run: bun install
37
+ - run: bunx semantic-release
38
+ env:
39
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,17 @@
1
+ {
2
+ "branches": ["main"],
3
+ "plugins": [
4
+ "@semantic-release/commit-analyzer",
5
+ "@semantic-release/release-notes-generator",
6
+ "@semantic-release/changelog",
7
+ "@semantic-release/npm",
8
+ [
9
+ "@semantic-release/git",
10
+ {
11
+ "assets": ["package.json", "CHANGELOG.md"],
12
+ "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
13
+ }
14
+ ],
15
+ "@semantic-release/github"
16
+ ]
17
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ ## [0.3.1](https://github.com/snomiao/codehost/compare/v0.3.0...v0.3.1) (2026-06-07)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add repository metadata so npm provenance publish succeeds ([e794bc2](https://github.com/snomiao/codehost/commit/e794bc2cef66276a53ca9fca9bece1a056bef703))
7
+
8
+ # [0.3.0](https://github.com/snomiao/codehost/compare/v0.2.0...v0.3.0) (2026-06-07)
9
+
10
+
11
+ ### Features
12
+
13
+ * open token URL after setup/serve and auto-connect a single server ([b6183a2](https://github.com/snomiao/codehost/commit/b6183a2dc35e3e2ce1b4dffa8d4228fab377f4e3))
14
+
15
+ # [0.2.0](https://github.com/snomiao/codehost/compare/v0.1.1...v0.2.0) (2026-06-05)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * oxmgr works under bun/bunx and on Windows (no Node, no global install) ([b339b31](https://github.com/snomiao/codehost/commit/b339b319c7147eee4e99c5abe977ace3146e41d6))
21
+ * tunnel VS Code's resource URLs via the real host, not 127.0.0.1 ([c9d22e2](https://github.com/snomiao/codehost/commit/c9d22e27946464252cd753424f0e267eb664fb93))
22
+
23
+
24
+ ### Features
25
+
26
+ * `-d` enables login auto-start via oxmgr's service integration ([623e022](https://github.com/snomiao/codehost/commit/623e02291cf0d1bc4c71c0344b333e93e72783db))
27
+ * `codehost expose <port>` — tunnel any local HTTP/WS server over WebRTC ([1ec57f4](https://github.com/snomiao/codehost/commit/1ec57f4f8d221b699c8f713b2eb6f0e2187665b7))
28
+ * deep-link to a live workspace via codehost.dev/gh/<owner>/<repo>/tree/<branch> ([1567ba7](https://github.com/snomiao/codehost/commit/1567ba7e64b821d5dea989235db63c5f22d9b4c9))
29
+ * proxy VS Code's CORS-less CDN through the signaling Worker ([0a362ee](https://github.com/snomiao/codehost/commit/0a362ee0d0c3247963da72bcc3b12365711c6d4a))
package/README.md CHANGED
@@ -20,15 +20,32 @@ directly; a tiny Cloudflare Worker only brokers the handshake.
20
20
 
21
21
  ## Quickstart
22
22
 
23
- On the machine you want to edit (needs the `code` CLI and [Bun](https://bun.sh)):
23
+ On the machine you want to edit, run the one-line installer it installs
24
+ [Bun](https://bun.sh) if needed, the `codehost` CLI, and then `codehost setup`
25
+ (picks a token, installs VS Code, and starts a server):
24
26
 
25
27
  ```bash
26
- bunx codehost serve -d -t <token>
28
+ # macOS / Linux
29
+ curl -fsSL https://codehost.dev/install.sh | sh
30
+
31
+ # Windows (PowerShell)
32
+ powershell -c "irm codehost.dev/install.ps1 | iex"
27
33
  ```
28
34
 
29
- Then open **https://codehost.dev**, enter the same `<token>`, and your server
30
- appears in the list. Click **Connect** — VS Code loads in the page, served
31
- entirely over the peer-to-peer data channel.
35
+ Already have Bun? `bun add -g codehost && codehost setup` does the same. Or, for
36
+ a specific directory/token in one shot:
37
+
38
+ ```bash
39
+ bunx codehost setup ~/my-project -t <token>
40
+ ```
41
+
42
+ Then open **https://codehost.dev**, enter the token printed by `setup`, and your
43
+ server appears in the list. Click **Connect** — VS Code loads in the page,
44
+ served entirely over the peer-to-peer data channel.
45
+
46
+ > Use `setup` (not `bunx codehost serve`) for the first run: `setup` installs
47
+ > everything and self-heals the native WebRTC module that a bare `bunx` can't
48
+ > fetch on its own.
32
49
 
33
50
  The `<token>` is a shared secret: anyone with it can see and connect to the
34
51
  servers in that room, so treat it like a password. It must be **at least 12
@@ -39,11 +56,25 @@ weaker tokens (e.g. `Str0ng-Token-99`).
39
56
  ## CLI
40
57
 
41
58
  ```bash
59
+ codehost setup [dir] [-t <token>] # token + VS Code + daemon, one shot
42
60
  codehost serve [dir] -t <token> [options] # serve a directory (default: cwd)
43
61
  codehost list # list daemonized servers (oxmgr)
44
62
  codehost stop <name> # stop a daemonized server
63
+ codehost update # fetch the latest VS Code CLI now
45
64
  ```
46
65
 
66
+ **`codehost setup`** is the easy path: it reuses (or generates and saves to
67
+ `~/.codehost/config.json`) a strong room token, ensures a working VS Code,
68
+ starts the server under oxmgr, and prints the token + URL. Pass `-t` to set a
69
+ token explicitly, or `--new-token` to rotate the saved one.
70
+
71
+ **VS Code is auto-installed.** On first `serve`, codehost uses a `code` already
72
+ on your `PATH` if present; otherwise it downloads Microsoft's standalone VS Code
73
+ CLI for your OS/arch (verifying its sha256), caches it under `~/.codehost/vscode/`,
74
+ and re-checks the stable channel at most once per day. Force a refresh with
75
+ `codehost update`, or point at a specific binary with the `CODEHOST_CODE_BIN`
76
+ environment variable.
77
+
47
78
  `serve` options:
48
79
 
49
80
  | flag | default | meaning |
package/TODO.md ADDED
@@ -0,0 +1,93 @@
1
+ # codehost — TODO / future features
2
+
3
+ Parked ideas, roughly in priority order. The in-flight deep-link feature
4
+ (`/gh/<owner>/<repo>/tree/<branch>` + `serve`/`dev` split) is tracked in the active
5
+ plan, not here.
6
+
7
+ ## Account login / device auth (Tailscale-style)
8
+
9
+ Replace/augment bearer tokens with account identity.
10
+
11
+ - CLI: `codehost serve --login=you@gmail.com` runs a device-authorization flow — prints a
12
+ short code + URL; you approve in `codehost.dev` while signed in (Google via **Firebase
13
+ Auth**) as that account. The daemon then holds a credential proving the same identity.
14
+ - Web: sign in with Google (Firebase) on codehost.dev.
15
+ - Signaling Worker: on room join, verify a Firebase ID token / signed JWT for the account
16
+ instead of (or alongside) the raw token. Personal room is keyed by account, auto-joined
17
+ by both the daemon and the web client — **no token copy-paste**.
18
+ - Benefits over tokens: revocable per device, per-account isolation, no shared secret to
19
+ leak. Like Tailscale authenticating devices.
20
+ - Keep **token rooms** too (anonymous quick-share); **login rooms** are the "my own
21
+ devices" path. The two can coexist.
22
+ - Open questions: Firebase project + Google OAuth client; how the CLI obtains a credential
23
+ (device-code OAuth vs. a Firebase custom-token minted by the Worker after browser
24
+ approval); revocation UI; mapping account -> room id.
25
+
26
+ ## Port / service forwarding — `[port|service].f.codehost.dev`
27
+
28
+ Expose a dev server running on the daemon (the tunnel already proxies HTTP to
29
+ `127.0.0.1:<x>`).
30
+
31
+ - Needs wildcard DNS `*.f.codehost.dev`, a tiny bootstrap page + its own per-subdomain
32
+ Service Worker, and a `subdomain -> (room, peer, port)` mapping with token/identity
33
+ scoping.
34
+ - **Opt-in** registration from a nav/settings panel — do **not** auto-expose every bound
35
+ port (security footgun).
36
+
37
+ ## Containerized dev environments — `codehost docker up [path]`
38
+
39
+ Run the workspace inside a container instead of on the host (devcontainer / Codespaces
40
+ style).
41
+
42
+ - `bunx codehost docker up [path]` mounts `path` into a container, sets up VS Code
43
+ `serve-web` + the repo's runtime inside (reuse the self-healing `code`/native installers;
44
+ honor the repo's `.devcontainer/devcontainer.json` when present), and runs the codehost
45
+ daemon in-container.
46
+ - Access at `codehost.dev/dev/<token>` (or `/gh/...`) via a generated or passed-in token.
47
+ - Lifecycle: `docker up` / `down` / `ps`. Composes with port forwarding above for the
48
+ container's services.
49
+ - Wins: isolation for untrusted repos, reproducible runtimes, parallel throwaway envs, no
50
+ host pollution.
51
+ - Open questions: base image (preinstalled `code`+bun vs. self-heal on first run), volume
52
+ mount perf, how the container daemon gets the token/identity, resource limits.
53
+
54
+ ## agent-yes web terminal UI (over `codehost expose`)
55
+
56
+ Make `codehost.dev/vs/<peerId>/` actually usable for an exposed agent-yes, not just the raw API.
57
+
58
+ - Add an HTML/JS terminal UI served by agent-yes's `ts/serve.ts` at `GET /`: xterm in the
59
+ browser, output via `EventSource('./api/tail/<kw>')` (SSE), input via `POST ./api/send`.
60
+ - **Use relative paths** (`./api/...`) so it works under the tunnel's `/vs/<peerId>/` prefix
61
+ (the prefix is stripped for the server, but the page's own URLs must stay relative).
62
+ - Reference: **snomiao/wtx** (cloned to lib/wtx) — a Bun PTY WebSocket server with replay
63
+ buffer + xterm client. Note wtx uses **WebSocket**; agent-yes uses **SSE + POST** — reuse
64
+ wtx's xterm/client setup but keep agent-yes's transport (or align them).
65
+ - Belongs in the agent-yes repo (it's served by agent-yes), enabled once `codehost expose
66
+ 7432` is running.
67
+
68
+ ## Real-time collaboration / presence (multiplayer cursors)
69
+
70
+ Multiple people open the same workspace (different Chrome profiles / accounts) and see each
71
+ other's cursors + selections live.
72
+
73
+ - `serve-web` is single-session: today multiple viewers get independent workbenches with no
74
+ shared awareness. Needs a layer on top.
75
+ - Realistic path: a **presence/awareness protocol** — each viewer broadcasts identity +
76
+ cursor/selection over the room substrate we already have (signaling / data channel),
77
+ rendered as remote-cursor decorations via an injected VS Code extension.
78
+ - Full concurrent co-editing (CRDT/OT, à la Yjs) is a much bigger lift; MS **Live Share** is
79
+ the off-the-shelf alternative but brings its own backend/auth.
80
+ - Depends on identity (see Account login above) to label who's who.
81
+
82
+ ## Deep-link feature follow-ups (after v1)
83
+
84
+ - Root daemon **enumerates / existence-checks** the repos under its root (vs. v1's
85
+ optimistic `?folder=`): nicer discovery list + accurate matching.
86
+ - **Clone-on-demand:** a root daemon `git clone`s `gh/owner/repo` into the root if absent,
87
+ then opens it (codespaces-like).
88
+ - **Live cross-room search:** fan out across all joined rooms for a repo with no history
89
+ (multiple concurrent `SignalingClient`s), instead of v1's history-driven single room.
90
+ - **Providers beyond GitHub** (`/gl/...`, self-hosted) via the `provider` field already in
91
+ `parseDeepLink`.
92
+ - Reflect the active repo back into the URL while browsing; chooser UI when several
93
+ machines/rooms serve the same repo.
@@ -0,0 +1,117 @@
1
+ # VS Code CDN proxy (CORS fix)
2
+
3
+ Status: implemented. Worker side verified; in-browser leg pending a live check.
4
+
5
+ ## Context
6
+
7
+ When VS Code runs inside the codehost iframe (origin `https://codehost.dev`, served
8
+ over the WebRTC tunnel), its workbench fetches configuration from Microsoft's
9
+ product CDN — e.g. `_fetchChatControlData` does:
10
+
11
+ ```
12
+ fetch('https://main.vscode-cdn.net/extensions/chat.json')
13
+ ```
14
+
15
+ That CDN sends **no `Access-Control-Allow-Origin` header**, so the browser blocks the
16
+ cross-origin read:
17
+
18
+ ```
19
+ Access to fetch at 'https://main.vscode-cdn.net/extensions/chat.json' from origin
20
+ 'https://codehost.dev' has been blocked by CORS policy: No 'Access-Control-Allow-Origin'
21
+ header is present on the requested resource.
22
+ ```
23
+
24
+ Our Service Worker only handled same-origin requests (it early-returned on
25
+ cross-origin), so these went straight to the CDN and failed. Chat / built-in-extension
26
+ control data never loaded and the console filled with CORS errors.
27
+
28
+ A Service Worker **alone cannot fix this**: it runs in the browser, bound by the same
29
+ CORS rules, and cannot turn a no-CORS cross-origin body into a readable one. The fix
30
+ needs a server-side proxy that re-serves the bytes with permissive CORS, plus a small
31
+ SW change to route the request there.
32
+
33
+ ## Decision
34
+
35
+ Reuse the already-deployed **signaling Worker** (`worker/index.ts`, served at
36
+ `signal.<page-host>`) as a thin, allow-listed CDN proxy. The Service Worker rewrites
37
+ blocked CDN requests to it.
38
+
39
+ ```
40
+ VS Code iframe (codehost.dev) signaling Worker (signal.codehost.dev)
41
+ fetch main.vscode-cdn.net/extensions/chat.json
42
+ │ (cross-origin, CORS-blocked)
43
+
44
+ Service Worker (sw.ts)
45
+ host ends with .vscode-cdn.net ? ──rewrite──► GET /cdn/main.vscode-cdn.net/extensions/chat.json
46
+ cache.match() first, else fetch + cache.put() │ allow-list check (.vscode-cdn.net)
47
+ ▲ │ fetch upstream (server-side, not CORS-bound)
48
+ └──────────── readable response ◄────────────────┘ + Access-Control-Allow-Origin: *
49
+ + preserve content-type, edge-cache
50
+ ```
51
+
52
+ **Self-host constraint (important):** the SW targets the **derived** signaling host
53
+ (`signal.<current page host>`), never a hardcoded `signal.codehost.dev`. A self-hoster
54
+ who serves the page + Worker on their own domain is automatically proxied by their own
55
+ Worker at `signal.<their-domain>/cdn/...`, with no code changes. See
56
+ `cdnProxyBase(hostname, protocol)` in `src/web/config.ts`.
57
+
58
+ ## Alternatives considered
59
+
60
+ - **Cloudflare Pages Function on `codehost.dev`** — same-origin and clean, but adds
61
+ Pages Functions to the currently pure-static build/deploy for no gain over reusing the
62
+ Worker we already run.
63
+ - **Daemon proxy over the WebRTC tunnel** — most self-host-robust (the daemon is always
64
+ the user's own machine, works even on networks where the browser can't reach the edge)
65
+ and zero hosted cost. Rejected as the default because it is slower (MS → daemon →
66
+ WebRTC → browser), cannot share a cache across users, adds CDN bytes to the WebRTC
67
+ datachannel, and allow-list changes must ship a new CLI to every user. The Worker wins
68
+ on latency, shared edge caching, and one-deploy allow-list updates. Cost is ~$0:
69
+ Cloudflare bills no egress, and these assets are tiny, public, and cacheable.
70
+
71
+ If a fully air-gapped / browser-can't-reach-the-edge deployment ever matters, the daemon
72
+ proxy is the fallback to revisit.
73
+
74
+ ## Security
75
+
76
+ - The Worker is the **authoritative allow-list**: only hosts ending in `.vscode-cdn.net`
77
+ are proxied (`CDN_HOST_SUFFIX` in `worker/index.ts`). Anything else → `403`. The
78
+ leading dot prevents look-alikes like `notvscode-cdn.net`.
79
+ - **GET/HEAD only** (others → `405`). It forwards a path under the chosen host and never
80
+ follows attacker-controlled hostnames, so it is not an open proxy.
81
+ - The SW's suffix check (`isProxiableCdnHost` in `src/web/config.ts`) is only an
82
+ optimization deciding what to rewrite; if it ever drifts from the Worker's list, the
83
+ worst case is a `403` or an unproxied request — the Worker stays the real gate.
84
+
85
+ ## Extending the allow-list
86
+
87
+ If the workbench starts pulling from another host (e.g. `update.code.visualstudio.com`),
88
+ widen the gate in two places and redeploy (one Worker + one Pages deploy — no CLI
89
+ change):
90
+
91
+ - `worker/index.ts` — `CDN_HOST_SUFFIX` (or make it a small list of suffixes).
92
+ - `src/web/config.ts` — `VSCODE_CDN_SUFFIX` / `isProxiableCdnHost`.
93
+
94
+ The extension **marketplace** (install/search) is a separate, larger concern (auth,
95
+ large payloads) and is intentionally out of scope here.
96
+
97
+ ## Files
98
+
99
+ - `worker/index.ts` — `/cdn/<host>/<path>` route + `handleCdnProxy` (allow-list, CORS,
100
+ `caches.default` edge cache, `content-type` passthrough, `cache-control`).
101
+ - `src/web/sw.ts` — cross-origin branch → `proxyCdn` (rewrite to the derived `/cdn`
102
+ base, Cache API per-browser caching).
103
+ - `src/web/config.ts` — `cdnProxyBase`, `isProxiableCdnHost`, `VSCODE_CDN_SUFFIX`
104
+ (shared host derivation, reused by `getSignalUrl`).
105
+
106
+ ## Verification
107
+
108
+ - **Worker, direct (done):**
109
+ - `curl -i https://signal.codehost.dev/cdn/main.vscode-cdn.net/extensions/chat.json`
110
+ → `200`, `content-type: application/json`, `access-control-allow-origin: *`,
111
+ `cache-control: public, max-age=3600`, real JSON body.
112
+ - `…/cdn/evil.example.com/x` → `403`; `POST …` → `405`.
113
+ - **In-browser (pending live check):** open `codehost.dev` (SW active), then from the
114
+ page `fetch('https://main.vscode-cdn.net/extensions/chat.json')` should resolve `200`
115
+ (served via `signal.<host>/cdn/...`) instead of throwing a CORS error; a second call is
116
+ served from the SW cache. Then load VS Code in the iframe and confirm the `chat.json`
117
+ CORS error is gone from the console.
package/package.json CHANGED
@@ -1,7 +1,13 @@
1
1
  {
2
2
  "name": "codehost",
3
- "version": "0.1.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/snomiao/codehost.git"
8
+ },
9
+ "homepage": "https://codehost.dev",
10
+ "bugs": "https://github.com/snomiao/codehost/issues",
5
11
  "bin": {
6
12
  "codehost": "./src/cli/index.ts"
7
13
  },
@@ -27,11 +33,15 @@
27
33
  "bun-pty": "^0.4.8",
28
34
  "hono": "^4.12.16",
29
35
  "node-datachannel": "^0.32.3",
36
+ "oxmgr": "^0.4.0",
30
37
  "react": "^19.1.1",
31
38
  "react-dom": "^19.1.1",
32
39
  "yargs": "^17.7.2"
33
40
  },
34
41
  "devDependencies": {
42
+ "@semantic-release/changelog": "^6.0.3",
43
+ "@semantic-release/git": "^10.0.1",
44
+ "semantic-release": "^25.0.0",
35
45
  "@types/bun": "^1.3.0",
36
46
  "@types/react": "^19.1.9",
37
47
  "@types/react-dom": "^19.1.7",
@@ -0,0 +1,5 @@
1
+ # SPA fallback: deep links like /gh/<owner>/<repo>/tree/<branch> and /dev/<path>
2
+ # have no static file, so serve the app and let it route client-side. Cloudflare
3
+ # Pages serves existing files first (/assets/*, /sw.js, /install.*), so only
4
+ # unmatched paths hit this rule. /vs/* is handled by the Service Worker at runtime.
5
+ /* /index.html 200
@@ -0,0 +1,43 @@
1
+ # codehost installer — https://codehost.dev
2
+ #
3
+ # powershell -c "irm codehost.dev/install.ps1 | iex"
4
+ #
5
+ # Ensures Bun is installed, installs the `codehost` CLI globally (which fetches
6
+ # the native WebRTC binary via Bun's lifecycle scripts), then runs `codehost
7
+ # setup` to pick a token, install VS Code, and start a server daemon.
8
+ #
9
+ # Env override: $env:CODEHOST_NO_SETUP = "1" -> install only, skip setup.
10
+
11
+ $ErrorActionPreference = "Stop"
12
+
13
+ function Info($m) { Write-Host "[codehost] $m" -ForegroundColor Cyan }
14
+ function Fail($m) { Write-Host "[codehost] $m" -ForegroundColor Red; exit 1 }
15
+
16
+ # Bun installs its global bin under %USERPROFILE%\.bun\bin by default.
17
+ $bunBin = Join-Path $env:USERPROFILE ".bun\bin"
18
+ if (Test-Path $bunBin) { $env:Path = "$bunBin;$env:Path" }
19
+
20
+ if (-not (Get-Command bun -ErrorAction SilentlyContinue)) {
21
+ Info "Bun not found - installing from bun.sh..."
22
+ Invoke-RestMethod https://bun.sh/install.ps1 | Invoke-Expression
23
+ if (Test-Path $bunBin) { $env:Path = "$bunBin;$env:Path" }
24
+ }
25
+
26
+ if (-not (Get-Command bun -ErrorAction SilentlyContinue)) {
27
+ Fail "Bun install did not land on PATH. Open a new terminal and re-run."
28
+ }
29
+
30
+ Info "installing the codehost CLI (bun add -g codehost)..."
31
+ bun add -g codehost
32
+
33
+ if (-not (Get-Command codehost -ErrorAction SilentlyContinue)) {
34
+ Fail "codehost installed but not on PATH. Add $bunBin to PATH and run: codehost setup"
35
+ }
36
+
37
+ if ($env:CODEHOST_NO_SETUP -eq "1") {
38
+ Info "installed. Run ``codehost setup`` in the directory you want to serve."
39
+ exit 0
40
+ }
41
+
42
+ Info "running ``codehost setup``..."
43
+ codehost setup
@@ -0,0 +1,55 @@
1
+ #!/bin/sh
2
+ # codehost installer — https://codehost.dev
3
+ #
4
+ # curl -fsSL https://codehost.dev/install.sh | sh
5
+ #
6
+ # Ensures Bun is installed, installs the `codehost` CLI globally (which fetches
7
+ # the native WebRTC binary via Bun's lifecycle scripts), then runs `codehost
8
+ # setup` to pick a token, install VS Code, and start a server daemon.
9
+ #
10
+ # Env overrides:
11
+ # CODEHOST_NO_SETUP=1 install only; don't run `codehost setup`
12
+ set -eu
13
+
14
+ info() { printf '\033[1;36m[codehost]\033[0m %s\n' "$1"; }
15
+ err() { printf '\033[1;31m[codehost]\033[0m %s\n' "$1" >&2; }
16
+
17
+ # Bun installs its global bin here by default; make sure it's on PATH for both
18
+ # the bun we may install and the `codehost` we're about to install.
19
+ BUN_INSTALL="${BUN_INSTALL:-$HOME/.bun}"
20
+ export BUN_INSTALL
21
+ export PATH="$BUN_INSTALL/bin:$PATH"
22
+
23
+ if ! command -v bun >/dev/null 2>&1; then
24
+ info "Bun not found — installing from bun.sh…"
25
+ if command -v curl >/dev/null 2>&1; then
26
+ curl -fsSL https://bun.sh/install | bash
27
+ elif command -v wget >/dev/null 2>&1; then
28
+ wget -qO- https://bun.sh/install | bash
29
+ else
30
+ err "need curl or wget to install Bun. Install one and re-run."
31
+ exit 1
32
+ fi
33
+ export PATH="$BUN_INSTALL/bin:$PATH"
34
+ fi
35
+
36
+ if ! command -v bun >/dev/null 2>&1; then
37
+ err "Bun install did not land on PATH. Open a new shell or add $BUN_INSTALL/bin to PATH, then re-run."
38
+ exit 1
39
+ fi
40
+
41
+ info "installing the codehost CLI (bun add -g codehost)…"
42
+ bun add -g codehost
43
+
44
+ if ! command -v codehost >/dev/null 2>&1; then
45
+ err "codehost installed but not on PATH. Add $BUN_INSTALL/bin to your PATH and run: codehost setup"
46
+ exit 1
47
+ fi
48
+
49
+ if [ "${CODEHOST_NO_SETUP:-}" = "1" ]; then
50
+ info "installed. Run \`codehost setup\` in the directory you want to serve."
51
+ exit 0
52
+ fi
53
+
54
+ info "running \`codehost setup\`…"
55
+ exec codehost setup
@@ -0,0 +1,107 @@
1
+ import { hostname } from "node:os";
2
+ import { resolve } from "node:path";
3
+ import type { CommandModule } from "yargs";
4
+ import type { PeerMeta } from "../../shared/signaling";
5
+ import { TOKEN_REQUIREMENTS, validateToken } from "../../shared/token";
6
+ import { launchServeDaemon } from "../daemonize";
7
+ import { announceConnect } from "../open-url";
8
+ import { runServer } from "../run-server";
9
+ import { launchVscode } from "../vscode";
10
+ import { repoIdentity } from "../git";
11
+ import { DEFAULT_SIGNAL_URL } from "./serve";
12
+
13
+ interface DevArgs {
14
+ dir: string;
15
+ token: string;
16
+ name?: string;
17
+ signal: string;
18
+ daemon: boolean;
19
+ port?: number;
20
+ }
21
+
22
+ export const devCommand: CommandModule<{}, DevArgs> = {
23
+ command: "dev [dir]",
24
+ describe:
25
+ "Serve a single folder over WebRTC; open it at codehost.dev/dev/<path> (or /gh/<owner>/<repo> when it's a GitHub repo)",
26
+ builder: (y) =>
27
+ y
28
+ .positional("dir", {
29
+ describe: "Directory to serve (defaults to cwd)",
30
+ type: "string",
31
+ default: ".",
32
+ })
33
+ .option("token", {
34
+ alias: "t",
35
+ describe: "Room token shared with the codehost.dev page",
36
+ type: "string",
37
+ demandOption: true,
38
+ })
39
+ .option("name", {
40
+ describe: "Display name for this server (defaults to hostname)",
41
+ type: "string",
42
+ })
43
+ .option("signal", {
44
+ describe: "Signaling server URL",
45
+ type: "string",
46
+ default: DEFAULT_SIGNAL_URL,
47
+ })
48
+ .option("daemon", {
49
+ alias: "d",
50
+ describe: "Run in the background under oxmgr (auto-starts on login)",
51
+ type: "boolean",
52
+ default: false,
53
+ })
54
+ .option("port", {
55
+ describe: "Fixed port for the local VS Code server (default: ephemeral)",
56
+ type: "number",
57
+ }) as any,
58
+ handler: async (argv) => {
59
+ argv.token = argv.token.trim();
60
+ const check = validateToken(argv.token);
61
+ if (!check.ok) {
62
+ console.error(`[codehost] ${check.reason}`);
63
+ console.error(`[codehost] room token requires: ${TOKEN_REQUIREMENTS}`);
64
+ process.exit(1);
65
+ }
66
+
67
+ const dir = resolve(process.cwd(), argv.dir);
68
+ const host = hostname();
69
+
70
+ if (argv.daemon) {
71
+ const { ok } = await launchServeDaemon({
72
+ command: "dev",
73
+ dir,
74
+ token: argv.token,
75
+ signal: argv.signal,
76
+ name: argv.name,
77
+ port: argv.port,
78
+ host,
79
+ });
80
+ if (ok) announceConnect(argv.token);
81
+ process.exit(ok ? 0 : 1);
82
+ }
83
+
84
+ // A single folder: git-identified so GitHub deep links resolve to it.
85
+ const id = repoIdentity(dir);
86
+ const meta: PeerMeta = {
87
+ name: argv.name ?? host,
88
+ cwd: dir,
89
+ host,
90
+ kind: "repo",
91
+ repo: id.repo,
92
+ branch: id.branch,
93
+ };
94
+
95
+ announceConnect(argv.token);
96
+ await runServer({
97
+ token: argv.token,
98
+ signal: argv.signal,
99
+ meta,
100
+ label: `serving ${dir}`,
101
+ launch: async (basePath) => {
102
+ const v = await launchVscode({ dir, basePath, port: argv.port });
103
+ return { port: v.port, stop: v.stop };
104
+ },
105
+ });
106
+ },
107
+ };