codehost 0.18.2 → 0.20.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 CHANGED
@@ -1,3 +1,17 @@
1
+ # [0.20.0](https://github.com/snomiao/codehost/compare/v0.19.0...v0.20.0) (2026-06-11)
2
+
3
+
4
+ ### Features
5
+
6
+ * **web:** edit a host's .codehost config from the site — advertised as a ⚙ workspace entry ([7c97b87](https://github.com/snomiao/codehost/commit/7c97b870715d6879cee39dab286cc7b8d45f08a6))
7
+
8
+ # [0.19.0](https://github.com/snomiao/codehost/compare/v0.18.2...v0.19.0) (2026-06-11)
9
+
10
+
11
+ ### Features
12
+
13
+ * **web:** GitHub-URL header/title for the open workspace; agent chips link into the agent-yes console ([83d62bc](https://github.com/snomiao/codehost/commit/83d62bc2ab3e70722f8a4f0f817541bea7cf69a2))
14
+
1
15
  ## [0.18.2](https://github.com/snomiao/codehost/compare/v0.18.1...v0.18.2) (2026-06-11)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codehost",
3
- "version": "0.18.2",
3
+ "version": "0.20.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
- import { mkdirSync } from "node:fs";
1
+ import { existsSync, mkdirSync } from "node:fs";
2
2
  import { homedir, hostname } from "node:os";
3
- import { resolve } from "node:path";
3
+ import { join, resolve } from "node:path";
4
4
  import type { CommandModule } from "yargs";
5
5
  import type { PeerMeta } from "../../shared/signaling";
6
6
  import { DEFAULT_LAYOUT, GITHUB_HOST, toPosixPath } from "../../shared/repo";
@@ -150,6 +150,12 @@ export const serveCommand: CommandModule<{}, ServeArgs> = {
150
150
  // Layout-enumerated checkouts plus directories other `codehost dev` runs
151
151
  // registered with this host daemon (git-identified best-effort).
152
152
  const workspaces = enumerateWorkspaces(dir, layout);
153
+ // The config dir itself is editable from the site (rendered as ⚙, opens
154
+ // in the editor) — advertised so its /host/<host>/<path> link resolves.
155
+ const configDir = join(dir, ".codehost");
156
+ if (existsSync(configDir)) {
157
+ workspaces.push({ path: toPosixPath(configDir), config: true });
158
+ }
153
159
  for (const w of readRegisteredWorkspaces()) {
154
160
  const path = toPosixPath(w.path);
155
161
  if (workspaces.some((x) => x.path === path)) continue;
@@ -30,6 +30,10 @@ export interface WorkspaceInfo {
30
30
  repo?: string;
31
31
  /** Branch from the layout path, e.g. "main". */
32
32
  branch?: string;
33
+ /** This entry is the daemon's `.codehost/` config dir (setup.sh etc.), not a
34
+ * repo checkout — clients render it as a settings affordance, openable in
35
+ * the editor like any workspace. */
36
+ config?: boolean;
33
37
  }
34
38
 
35
39
  /** Metadata a `codehost serve`/`dev` daemon advertises about itself. */
@@ -58,6 +58,18 @@ function folderQuery(folder?: string): string {
58
58
  return folder ? `?folder=${encodeURIComponent(folder)}` : "";
59
59
  }
60
60
 
61
+ /** Human label for a connected workspace: its GitHub-style URL when the share
62
+ * path is repo-shaped (/gh/owner/repo -> github.com/owner/repo, /git/<host>/…
63
+ * -> <host>/…), else the deep-link path as-is. */
64
+ function shareLabel(path: string | null): string | null {
65
+ if (!path) return null;
66
+ const gh = path.match(/^\/gh\/(.+)$/);
67
+ if (gh) return `github.com/${gh[1]}`;
68
+ const git = path.match(/^\/git\/(.+)$/);
69
+ if (git) return git[1];
70
+ return path;
71
+ }
72
+
61
73
  /**
62
74
  * Find which of the user's saved rooms hosts a server matching a token-less deep
63
75
  * link. Opens a short-lived viewer connection to each candidate room in
@@ -292,6 +304,13 @@ export function Discovery() {
292
304
  tryAutoConnect();
293
305
  }, [serversByRoom, tokens]);
294
306
 
307
+ // Mirror the open workspace into the tab title (GitHub-style URL), so tabs
308
+ // read as "github.com/owner/repo/tree/main — codehost", not all "Codehost".
309
+ useEffect(() => {
310
+ const label = connState === "connected" ? shareLabel(sharePathRef.current) : null;
311
+ document.title = label ? `${label} — codehost` : "Codehost";
312
+ }, [connState]);
313
+
295
314
  // Keep the connection in sync with the URL as servers come and go: reconnect
296
315
  // when the workspace named by the address bar (re)appears in a room — covers a
297
316
  // daemon restart or a dropped channel while the tab stays open.
@@ -691,8 +710,10 @@ export function Discovery() {
691
710
  // Group workspaces by machine: the stable hostId when the daemon advertises
692
711
  // one, else the hostname string (older daemons), else the peer stands alone.
693
712
  // Agents are machine-level (advertised by the host's root daemon) — collect
694
- // them per group, deduped by pid across peers.
695
- const hostGroups: { key: string; label: string; items: typeof filtered; agents: AgentInfo[] }[] = [];
713
+ // them per group, deduped by pid across peers, each remembering its room so
714
+ // a click can hand the agent-yes console the right token.
715
+ type RoomedAgent = AgentInfo & { room: string };
716
+ const hostGroups: { key: string; label: string; items: typeof filtered; agents: RoomedAgent[] }[] = [];
696
717
  for (const t of filtered) {
697
718
  const key = t.server.meta?.hostId ?? t.server.meta?.host ?? t.server.peerId;
698
719
  let group = hostGroups.find((g) => g.key === key);
@@ -702,7 +723,7 @@ export function Discovery() {
702
723
  }
703
724
  group.items.push(t);
704
725
  for (const a of t.server.meta?.agents ?? []) {
705
- if (!group.agents.some((x) => x.pid === a.pid)) group.agents.push(a);
726
+ if (!group.agents.some((x) => x.pid === a.pid)) group.agents.push({ ...a, room: t.room });
706
727
  }
707
728
  }
708
729
  const toggleTag = (t: string) =>
@@ -765,8 +786,15 @@ export function Discovery() {
765
786
  <header style={styles.header}>
766
787
  <span style={styles.brand}>codehost</span>
767
788
  <span style={styles.dim}>·</span>
768
- <span style={styles.dim}>{activeServer?.meta?.name ?? activePeerId?.slice(0, 8)}</span>
769
- {activeServer?.meta?.cwd && <span style={styles.cwd}>{activeServer.meta.cwd}</span>}
789
+ <span
790
+ style={styles.cwd}
791
+ title={`${activeServer?.meta?.name ?? ""} ${activeServer?.meta?.cwd ?? ""}`.trim()}
792
+ >
793
+ {shareLabel(sharePathRef.current) ??
794
+ activeServer?.meta?.cwd ??
795
+ activeServer?.meta?.name ??
796
+ activePeerId?.slice(0, 8)}
797
+ </span>
770
798
  <span style={{ flex: 1 }} />
771
799
  <button
772
800
  style={styles.shareBtn}
@@ -959,14 +987,20 @@ export function Discovery() {
959
987
  {g.agents.length > 0 && (
960
988
  <div style={styles.agentRow}>
961
989
  {g.agents.map((a) => (
962
- <span
990
+ <a
963
991
  key={a.pid}
964
992
  style={styles.agentChip}
965
- title={`${a.cwd}${a.title ? `\n${a.title}` : ""}`}
993
+ title={`${a.cwd}${a.title ? `\n${a.title}` : ""}\nopen in the agent-yes console`}
994
+ // Tail & send live in the agent-yes console — it joins
995
+ // this same room as a viewer (token rides the fragment,
996
+ // never sent to a server) and auto-selects the pid.
997
+ href={`https://agent-yes.com/?pid=${a.pid}#ch:${encodeURIComponent(a.room)}`}
998
+ target="_blank"
999
+ rel="noopener"
966
1000
  >
967
1001
  <span style={{ color: a.state === "active" ? "#4ec9b0" : "#777" }}>●</span> {a.tool}{" "}
968
1002
  {a.pid}
969
- </span>
1003
+ </a>
970
1004
  ))}
971
1005
  </div>
972
1006
  )}
@@ -991,11 +1025,13 @@ export function Discovery() {
991
1025
  key={w.path}
992
1026
  style={styles.wsLink}
993
1027
  onClick={() => openWorkspace(s, w)}
994
- title={w.path}
1028
+ title={w.config ? `edit this host's provisioning config\n${w.path}` : w.path}
995
1029
  >
996
- {w.repo
997
- ? `${w.repo.split("/").slice(1).join("/")}${w.branch ? ` @${w.branch}` : ""}`
998
- : w.path}
1030
+ {w.config
1031
+ ? ".codehost (setup.sh, config.yaml)"
1032
+ : w.repo
1033
+ ? `${w.repo.split("/").slice(1).join("/")}${w.branch ? ` @${w.branch}` : ""}`
1034
+ : w.path}
999
1035
  </button>
1000
1036
  ))}
1001
1037
  </div>
@@ -1081,7 +1117,7 @@ const styles: Record<string, React.CSSProperties> = {
1081
1117
  agentRow: { display: "flex", flexWrap: "wrap", gap: 6, margin: "0 0 8px" },
1082
1118
  agentChip: {
1083
1119
  fontFamily: "monospace", fontSize: 11.5, padding: "2px 8px", borderRadius: 999,
1084
- border: "1px solid #3d3d3d", color: "#9aa4af",
1120
+ border: "1px solid #3d3d3d", color: "#9aa4af", textDecoration: "none", cursor: "pointer",
1085
1121
  },
1086
1122
  card: { display: "flex", alignItems: "center", gap: 12, background: "#252525", border: "1px solid #3d3d3d", borderRadius: 8, padding: "12px 14px" },
1087
1123
  cardMain: { flex: 1, minWidth: 0 },