codehost 0.22.1 → 0.23.0
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.
- package/CHANGELOG.md +7 -0
- package/CLAUDE.md +43 -0
- package/package.json +1 -1
- package/src/cli/commands/dev.ts +2 -1
- package/src/cli/commands/expose.ts +2 -1
- package/src/cli/commands/serve.ts +2 -1
- package/src/cli/config.ts +12 -1
- package/src/shared/signaling.ts +7 -0
- package/src/web/discovery.tsx +47 -11
- package/.claude/scheduled_tasks.lock +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [0.23.0](https://github.com/snomiao/codehost/compare/v0.22.1...v0.23.0) (2026-06-15)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **web:** show the serving user@host in the connected header, link the repo URL out ([a10c243](https://github.com/snomiao/codehost/commit/a10c2435d0b6b15572708696854ccf65c74a268e))
|
|
7
|
+
|
|
1
8
|
## [0.22.1](https://github.com/snomiao/codehost/compare/v0.22.0...v0.22.1) (2026-06-15)
|
|
2
9
|
|
|
3
10
|
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# codehost — repo-scoped agent notes
|
|
2
|
+
|
|
3
|
+
WebRTC-tunneled VS Code: a CLI daemon (`codehost serve`/`dev`) serves a machine; the
|
|
4
|
+
codehost.dev page (Cloudflare Pages) connects over a WebRTC data channel; a Worker + Durable
|
|
5
|
+
Object (signal.codehost.dev) only relays signaling. See README.md for the architecture.
|
|
6
|
+
|
|
7
|
+
## Build / typecheck / test / deploy
|
|
8
|
+
|
|
9
|
+
- **Typecheck:** `bun run typecheck` — three tsconfigs (root, `src/web/tsconfig.sw.json`,
|
|
10
|
+
`worker/tsconfig.json`). All three must pass.
|
|
11
|
+
- **Build:** `bun run build` (vite app + service worker) and `bun run build:lib` (the standalone
|
|
12
|
+
`room-client` bundle for embedding).
|
|
13
|
+
- **Tests:** `bun test` globs into the **gitignored `tmp/` scratch checkout** (a second project
|
|
14
|
+
missing test deps) and reports false failures. Run project tests only:
|
|
15
|
+
`bun test $(git ls-files '*.test.ts' '*.test.tsx')`.
|
|
16
|
+
- **Deploy:** `bun run deploy:signal` (the Worker/DO — only needed when the signaling protocol
|
|
17
|
+
changes) and `bun run deploy:pages` (the codehost.dev site). Wrangler reads
|
|
18
|
+
`CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` from `.env.local` (gitignored). The token is
|
|
19
|
+
**account-owned**, so verify it via `/accounts/<id>/tokens/verify`, not `/user/tokens/verify`;
|
|
20
|
+
it has no Zone/DNS scope.
|
|
21
|
+
- A push to `main` triggers a semantic-release version bump + npm publish — keep `main` green.
|
|
22
|
+
|
|
23
|
+
## The signaling protocol is shared AND has live peers in the field
|
|
24
|
+
|
|
25
|
+
`src/shared/signaling.ts` is the wire contract for the page, the daemon, AND the Worker. Deployed
|
|
26
|
+
daemons run many npm versions, so **only make additive/optional changes** to the protocol.
|
|
27
|
+
|
|
28
|
+
The connecting role is `"client"`, but `"viewer"` remains a valid wire value: receivers accept
|
|
29
|
+
either via `isClientRole`, and new code still EMITS `CLIENT_WIRE_ROLE` (currently `"viewer"`)
|
|
30
|
+
during an "accept both, emit old" transition — so old and new daemons/pages interoperate. Don't
|
|
31
|
+
rename or repurpose wire values, or flip the emitted role, without keeping that compatibility.
|
|
32
|
+
|
|
33
|
+
## Verifying codehost.dev visually (rech)
|
|
34
|
+
|
|
35
|
+
To screenshot/verify the live site, drive a real Chrome with `rech` (the `rechrome` package — see
|
|
36
|
+
its own CLAUDE.md for setup and the session/scroll caveats). codehost.dev quirks:
|
|
37
|
+
|
|
38
|
+
- The page scrolls an **inner `<main>` (overflow:auto)**, not the document — so
|
|
39
|
+
`screenshot --full-page` only captures the viewport. To capture below the fold, `resize` the
|
|
40
|
+
window tall enough that everything lays out without inner-scroll, then screenshot and crop.
|
|
41
|
+
- The mobile layout kicks in under a **560px** width breakpoint — `resize` to a phone width to
|
|
42
|
+
check it.
|
|
43
|
+
- After a Pages deploy, open with a `?cb=<ts>` cache-buster to dodge a stale cached bundle.
|
package/package.json
CHANGED
package/src/cli/commands/dev.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { CommandModule } from "yargs";
|
|
|
4
4
|
import type { PeerMeta } from "../../shared/signaling";
|
|
5
5
|
import type { ApprovePolicy } from "../approver";
|
|
6
6
|
import { TOKEN_REQUIREMENTS, validateToken } from "../../shared/token";
|
|
7
|
-
import { ensureHostId } from "../config";
|
|
7
|
+
import { currentUser, ensureHostId } from "../config";
|
|
8
8
|
import { launchServeDaemon } from "../daemonize";
|
|
9
9
|
import { announceConnect } from "../open-url";
|
|
10
10
|
import { runServer } from "../run-server";
|
|
@@ -137,6 +137,7 @@ export const devCommand: CommandModule<{}, DevArgs> = {
|
|
|
137
137
|
// the real OS path for the local VS Code working dir.
|
|
138
138
|
cwd: toPosixPath(dir),
|
|
139
139
|
host,
|
|
140
|
+
user: currentUser(),
|
|
140
141
|
hostId: ensureHostId(),
|
|
141
142
|
kind: "repo",
|
|
142
143
|
repo: id.repo,
|
|
@@ -2,7 +2,7 @@ import { hostname } from "node:os";
|
|
|
2
2
|
import type { CommandModule } from "yargs";
|
|
3
3
|
import type { PeerMeta } from "../../shared/signaling";
|
|
4
4
|
import { TOKEN_REQUIREMENTS, validateToken } from "../../shared/token";
|
|
5
|
-
import { ensureHostId } from "../config";
|
|
5
|
+
import { currentUser, ensureHostId } from "../config";
|
|
6
6
|
import { launchServeDaemon } from "../daemonize";
|
|
7
7
|
import { runServer } from "../run-server";
|
|
8
8
|
import { DEFAULT_SIGNAL_URL } from "./serve";
|
|
@@ -79,6 +79,7 @@ export const exposeCommand: CommandModule<{}, ExposeArgs> = {
|
|
|
79
79
|
name: argv.name ?? `localhost:${argv.port}`,
|
|
80
80
|
cwd: `localhost:${argv.port}`,
|
|
81
81
|
host,
|
|
82
|
+
user: currentUser(),
|
|
82
83
|
hostId: ensureHostId(),
|
|
83
84
|
};
|
|
84
85
|
|
|
@@ -6,7 +6,7 @@ import type { PeerMeta } from "../../shared/signaling";
|
|
|
6
6
|
import type { ApprovePolicy } from "../approver";
|
|
7
7
|
import { DEFAULT_LAYOUT, GITHUB_HOST, toPosixPath } from "../../shared/repo";
|
|
8
8
|
import { TOKEN_REQUIREMENTS, validateToken } from "../../shared/token";
|
|
9
|
-
import { defaultRoot, ensureHostId } from "../config";
|
|
9
|
+
import { currentUser, defaultRoot, ensureHostId } from "../config";
|
|
10
10
|
import { launchServeDaemon } from "../daemonize";
|
|
11
11
|
import { announceConnect } from "../open-url";
|
|
12
12
|
import { agentYesPlugin } from "../plugins/agent-yes";
|
|
@@ -200,6 +200,7 @@ export const serveCommand: CommandModule<{}, ServeArgs> = {
|
|
|
200
200
|
// real OS path `dir` is still what we spawn VS Code in.
|
|
201
201
|
cwd: toPosixPath(dir),
|
|
202
202
|
host,
|
|
203
|
+
user: currentUser(),
|
|
203
204
|
hostId: ensureHostId(),
|
|
204
205
|
kind: "root",
|
|
205
206
|
layout,
|
package/src/cli/config.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
2
|
+
import { homedir, userInfo } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
|
|
5
5
|
// Persistent CLI config under ~/.codehost (same root as the managed VS Code
|
|
@@ -39,6 +39,17 @@ export function defaultRoot(file: string = CONFIG_FILE): string {
|
|
|
39
39
|
return readConfig(file).root || join(homedir(), "ws");
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/** This machine's OS login name, advertised so the web UI can show a
|
|
43
|
+
* `user@host` label. Falls back to $USER/$USERNAME, then "unknown" —
|
|
44
|
+
* os.userInfo() throws when the uid has no passwd entry (some containers). */
|
|
45
|
+
export function currentUser(): string {
|
|
46
|
+
try {
|
|
47
|
+
return userInfo().username;
|
|
48
|
+
} catch {
|
|
49
|
+
return process.env.USER || process.env.USERNAME || "unknown";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
/** This machine's persistent hostId, minting + saving it on first call. */
|
|
43
54
|
export function ensureHostId(file: string = CONFIG_FILE): string {
|
|
44
55
|
const config = readConfig(file);
|
package/src/shared/signaling.ts
CHANGED
|
@@ -68,6 +68,13 @@ export interface PeerMeta {
|
|
|
68
68
|
cwd?: string;
|
|
69
69
|
/** Server only: hostname of the machine running the daemon. */
|
|
70
70
|
host?: string;
|
|
71
|
+
/**
|
|
72
|
+
* OS login name of whoever launched the daemon — the web UI shows it as the
|
|
73
|
+
* `user@host` label in front of the workspace URL, so you can tell which
|
|
74
|
+
* machine/account is actually serving a `codehost.dev/gh/...` link. Absent on
|
|
75
|
+
* older daemons (and in environments with no passwd entry) — fall back to `host`.
|
|
76
|
+
*/
|
|
77
|
+
user?: string;
|
|
71
78
|
/**
|
|
72
79
|
* Stable machine identity (UUID persisted in ~/.codehost/config.json). All
|
|
73
80
|
* daemons on one machine share it, unlike the per-process peerId, so clients
|
package/src/web/discovery.tsx
CHANGED
|
@@ -102,6 +102,19 @@ function shareLabel(path: string | null): string | null {
|
|
|
102
102
|
return path;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/** External URL for a repo-shaped share path, so the connected-view label can
|
|
106
|
+
* link out to the real host: /gh/owner/repo/tree/x -> https://github.com/owner/repo/tree/x,
|
|
107
|
+
* /git/<host>/… -> https://<host>/… . Null for non-repo paths (folder mounts),
|
|
108
|
+
* which have no public URL. */
|
|
109
|
+
function shareHref(path: string | null): string | null {
|
|
110
|
+
if (!path) return null;
|
|
111
|
+
const gh = path.match(/^\/gh\/(.+)$/);
|
|
112
|
+
if (gh) return `https://github.com/${gh[1]}`;
|
|
113
|
+
const git = path.match(/^\/git\/(.+)$/);
|
|
114
|
+
if (git) return `https://${git[1]}`;
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
105
118
|
/**
|
|
106
119
|
* Find which of the user's saved rooms hosts a server matching a token-less deep
|
|
107
120
|
* link. Opens a short-lived viewer connection to each candidate room in
|
|
@@ -960,16 +973,37 @@ export function Discovery() {
|
|
|
960
973
|
<div style={styles.page}>
|
|
961
974
|
<header style={styles.header}>
|
|
962
975
|
<span style={styles.brand}>codehost</span>
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
style={styles.
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
976
|
+
{activeServer?.meta?.host ? (
|
|
977
|
+
// Which machine/account is actually serving this codehost.dev link.
|
|
978
|
+
<span style={styles.hostTag} title="machine serving this workspace">
|
|
979
|
+
[{activeServer.meta.user ? `${activeServer.meta.user}@` : ""}
|
|
980
|
+
{activeServer.meta.host}]
|
|
981
|
+
</span>
|
|
982
|
+
) : (
|
|
983
|
+
<span style={styles.dim}>·</span>
|
|
984
|
+
)}
|
|
985
|
+
{shareHref(sharePathRef.current) ? (
|
|
986
|
+
// Repo-shaped link: clickable out to the real host (GitHub etc.).
|
|
987
|
+
<a
|
|
988
|
+
style={styles.cwdLink}
|
|
989
|
+
href={shareHref(sharePathRef.current) ?? undefined}
|
|
990
|
+
target="_blank"
|
|
991
|
+
rel="noopener noreferrer"
|
|
992
|
+
title={shareHref(sharePathRef.current) ?? undefined}
|
|
993
|
+
>
|
|
994
|
+
{shareLabel(sharePathRef.current)}
|
|
995
|
+
</a>
|
|
996
|
+
) : (
|
|
997
|
+
<span
|
|
998
|
+
style={styles.cwd}
|
|
999
|
+
title={`${activeServer?.meta?.name ?? ""} ${activeServer?.meta?.cwd ?? ""}`.trim()}
|
|
1000
|
+
>
|
|
1001
|
+
{shareLabel(sharePathRef.current) ??
|
|
1002
|
+
activeServer?.meta?.cwd ??
|
|
1003
|
+
activeServer?.meta?.name ??
|
|
1004
|
+
activePeerId?.slice(0, 8)}
|
|
1005
|
+
</span>
|
|
1006
|
+
)}
|
|
973
1007
|
{connPath && (
|
|
974
1008
|
<span
|
|
975
1009
|
style={styles.dim}
|
|
@@ -1300,6 +1334,7 @@ const styles: Record<string, React.CSSProperties> = {
|
|
|
1300
1334
|
page: { display: "flex", flexDirection: "column", height: "100%", background: "#1f1f1f", color: "#ccc", fontFamily: "system-ui, sans-serif" },
|
|
1301
1335
|
header: { display: "flex", alignItems: "center", gap: 8, padding: "8px 14px", background: "#2d2d2d", borderBottom: "1px solid #3d3d3d", fontSize: 13 },
|
|
1302
1336
|
brand: { fontFamily: "monospace", fontWeight: 700, color: "#fff" },
|
|
1337
|
+
hostTag: { fontFamily: "monospace", fontSize: 12, color: "#dcdcaa" },
|
|
1303
1338
|
dim: { color: "#888", fontSize: 12 },
|
|
1304
1339
|
status: { fontSize: 12 },
|
|
1305
1340
|
// Wide cap + per-host card GRID below: a 4K monitor gets several columns of
|
|
@@ -1373,6 +1408,7 @@ const styles: Record<string, React.CSSProperties> = {
|
|
|
1373
1408
|
cardName: { fontSize: 14, fontWeight: 600, color: "#fff" },
|
|
1374
1409
|
cardSub: { display: "flex", gap: 12, fontSize: 12, color: "#888", marginTop: 2 },
|
|
1375
1410
|
cwd: { fontFamily: "monospace" },
|
|
1411
|
+
cwdLink: { fontFamily: "monospace", color: "#75beff", textDecoration: "none" },
|
|
1376
1412
|
echo: { marginTop: 6, fontSize: 12, color: "#4ec9b0", fontFamily: "monospace" },
|
|
1377
1413
|
echoBad: { marginTop: 6, fontSize: 12, color: "#f48771", fontFamily: "monospace" },
|
|
1378
1414
|
rosterSection: { marginTop: 28 },
|
|
@@ -1384,7 +1420,7 @@ const styles: Record<string, React.CSSProperties> = {
|
|
|
1384
1420
|
cmdRowNarrow: { flexDirection: "column", alignItems: "stretch", gap: 6, marginTop: 12 },
|
|
1385
1421
|
cmdLabel: { fontSize: 11, color: "#888", width: 88, flexShrink: 0 },
|
|
1386
1422
|
cmdLabelNarrow: { width: "auto" },
|
|
1387
|
-
cmdCode: { flex: 1, minWidth: 0, background: "#1b1b1b", border: "1px solid #3d3d3d", borderRadius: 6, padding: "8px 10px", fontFamily: "monospace", fontSize: 12.5, color: "#dcdcaa",
|
|
1423
|
+
cmdCode: { flex: 1, minWidth: 0, background: "#1b1b1b", border: "1px solid #3d3d3d", borderRadius: 6, padding: "8px 10px", fontFamily: "monospace", fontSize: 12.5, color: "#dcdcaa", whiteSpace: "pre-wrap", overflowWrap: "anywhere" },
|
|
1388
1424
|
cmdCopy: { flexShrink: 0, background: "#0e639c", border: "none", color: "#fff", padding: "8px 12px", borderRadius: 6, cursor: "pointer", fontSize: 12 },
|
|
1389
1425
|
cmdCopyNarrow: { width: "100%", padding: "10px 12px" },
|
|
1390
1426
|
rosterHint: { margin: "10px 0 0", fontSize: 12, color: "#888" },
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"sessionId":"f8e4e571-944e-43d7-bbfa-267a5251e41c","pid":87968,"procStart":"Mon Jun 8 09:46:02 2026","acquiredAt":1780974562147}
|