codedeck 0.5.3 → 0.6.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/README.md CHANGED
@@ -93,6 +93,7 @@ The daemon owns the sessions. The CLI only follows events — closing the termin
93
93
  | `npx codedeck send <id> "<msg>"` | Continue a session (new turn) |
94
94
  | `npx codedeck stop <id>` | Graceful interrupt → SIGTERM → SIGKILL |
95
95
  | `npx codedeck diff <id> [--stat] [--json]` | Git diff against base commit |
96
+ | `npx codedeck review [--port <n>] [--no-open]` | Open a local review of the current git changes |
96
97
 
97
98
  ## Open
98
99
 
@@ -0,0 +1,122 @@
1
+ import http from "node:http";
2
+ import { spawn } from "node:child_process";
3
+ import { InvalidArgumentError } from "commander";
4
+ import { REVIEW_PAGE } from "../../web/review-page.js";
5
+ import { getLocalReview } from "../../git/review.js";
6
+ export const DEFAULT_REVIEW_PORT = 3100;
7
+ export function parseReviewPort(raw) {
8
+ if (raw === undefined)
9
+ return DEFAULT_REVIEW_PORT;
10
+ if (!/^\d+$/.test(raw)) {
11
+ throw new InvalidArgumentError("--port must be a positive integer");
12
+ }
13
+ const port = Number(raw);
14
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
15
+ throw new InvalidArgumentError("--port must be a positive integer");
16
+ }
17
+ return port;
18
+ }
19
+ export function openBrowser(url) {
20
+ const opener = process.platform === "darwin"
21
+ ? "open"
22
+ : process.platform === "win32"
23
+ ? "cmd"
24
+ : "xdg-open";
25
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
26
+ try {
27
+ const child = spawn(opener, args, { detached: true, stdio: "ignore" });
28
+ child.unref();
29
+ return true;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ function sendJson(res, code, value) {
36
+ res.writeHead(code, { "content-type": "application/json; charset=utf-8" });
37
+ res.end(JSON.stringify(value));
38
+ }
39
+ export function createReviewHandler(reviewDeps) {
40
+ return (req, res) => {
41
+ void (async () => {
42
+ try {
43
+ const url = new URL(req.url || "/", "http://127.0.0.1");
44
+ if (req.method === "GET" && (url.pathname === "/" || url.pathname === "/review")) {
45
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
46
+ res.end(REVIEW_PAGE);
47
+ return;
48
+ }
49
+ if (req.method === "GET" && url.pathname === "/api/review") {
50
+ const ref = url.searchParams.get("ref") || "HEAD";
51
+ const file = url.searchParams.get("file") || undefined;
52
+ const root = reviewDeps?.root ?? process.cwd();
53
+ try {
54
+ const load = reviewDeps?.loadReview ?? ((r, q, f) => getLocalReview(r, { ref: q, file: f }));
55
+ sendJson(res, 200, await load(root, ref, file));
56
+ }
57
+ catch (error) {
58
+ const message = error instanceof Error ? error.message : String(error);
59
+ sendJson(res, message.includes("not a git repository") ? 404 : 500, { error: message });
60
+ }
61
+ return;
62
+ }
63
+ sendJson(res, 404, { error: "not found" });
64
+ }
65
+ catch (error) {
66
+ try {
67
+ sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
68
+ }
69
+ catch {
70
+ try {
71
+ res.end();
72
+ }
73
+ catch {
74
+ // The socket is already unusable.
75
+ }
76
+ }
77
+ }
78
+ })();
79
+ };
80
+ }
81
+ export function registerReviewCommand(program) {
82
+ program
83
+ .command("review")
84
+ .description("Open a local review of the current git changes")
85
+ .option("--port <n>", "port to listen on (default: 3100)", String(DEFAULT_REVIEW_PORT))
86
+ .option("--no-open", "serve the review without opening a browser")
87
+ .action(async (opts) => {
88
+ let port;
89
+ try {
90
+ port = parseReviewPort(opts.port);
91
+ }
92
+ catch (error) {
93
+ console.error(error instanceof Error ? error.message : String(error));
94
+ process.exitCode = 1;
95
+ return;
96
+ }
97
+ const server = http.createServer(createReviewHandler());
98
+ try {
99
+ await new Promise((resolve, reject) => {
100
+ server.once("error", reject);
101
+ server.listen(port, "127.0.0.1", () => resolve());
102
+ });
103
+ }
104
+ catch (error) {
105
+ console.error(`Failed to listen on 127.0.0.1:${port}: ${error instanceof Error ? error.message : String(error)}`);
106
+ process.exitCode = 1;
107
+ return;
108
+ }
109
+ const url = `http://127.0.0.1:${port}/`;
110
+ console.log(`CodeDeck review on ${url}`);
111
+ if (opts.open !== false && !openBrowser(url)) {
112
+ console.log(`Could not open a browser, visit ${url} manually.`);
113
+ }
114
+ const shutdown = () => {
115
+ server.close(() => process.exit(0));
116
+ setTimeout(() => process.exit(0), 1000).unref?.();
117
+ };
118
+ process.on("SIGINT", shutdown);
119
+ process.on("SIGTERM", shutdown);
120
+ });
121
+ }
122
+ //# sourceMappingURL=review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.js","sourceRoot":"","sources":["../../../src/cli/commands/review.ts"],"names":[],"mappings":"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"}
package/dist/cli/index.js CHANGED
@@ -20,7 +20,7 @@ import { registerOpenCommand } from "./commands/open.js";
20
20
  import { registerProfileCommand } from "./commands/profile.js";
21
21
  import { registerSetupCommand } from "./commands/setup.js";
22
22
  import { registerUsageCommand } from "./commands/usage.js";
23
- import { registerWebCommand } from "./commands/web.js";
23
+ import { registerReviewCommand } from "./commands/review.js";
24
24
  import { getCliInvocation, getCliName } from "./cli-name.js";
25
25
  function getVersion() {
26
26
  try {
@@ -68,7 +68,7 @@ Recommended flow:
68
68
  $ ${cli} run "task" --bg --json # starts in background
69
69
  $ ${cli} wait <id> # waits without ps/show loop
70
70
  $ ${cli} logs <id> --follow # inspect progress
71
- $ ${cli} web --open # realtime runs canvas in the browser
71
+ $ ${cli} review # review current git changes in the browser
72
72
 
73
73
  Run '${cli} <command> --help' for command-specific options.
74
74
  Docs: https://github.com/4ndreello/run-agent
@@ -90,7 +90,7 @@ registerOpenCommand(program);
90
90
  registerProfileCommand(program);
91
91
  registerSetupCommand(program);
92
92
  registerUsageCommand(program);
93
- registerWebCommand(program);
93
+ registerReviewCommand(program);
94
94
  // Make `codedeck help` behave like `codedeck --help`
95
95
  program.command("help", { hidden: true }).action(() => program.outputHelp());
96
96
  program.parseAsync(process.argv).catch((err) => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE7D,SAAS,UAAU;IACjB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC;QAC9F,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QACvD,OAAO,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,mEAAmE;AACnE,uEAAuE;AACvE,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;AAC7B,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;AAE/B,OAAO;KACJ,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,uHAAuH,CAAC;KACpI,OAAO,CAAC,UAAU,EAAE,CAAC;KACrB,UAAU,CAAC,YAAY,EAAE,0BAA0B,CAAC;KACpD,kBAAkB,CAAC,0BAA0B,CAAC;KAC9C,wBAAwB,CAAC,IAAI,CAAC;KAC9B,WAAW,CAAC,OAAO,EAAE;;MAElB,GAAG;MACH,GAAG;MACH,GAAG;MACH,GAAG;MACH,GAAG;MACH,GAAG;MACH,GAAG;MACH,GAAG;MACH,GAAG;MACH,GAAG;MACH,GAAG;MACH,GAAG;MACH,GAAG;MACH,GAAG;MACH,GAAG;MACH,GAAG;;;MAGH,GAAG;MACH,GAAG;MACH,GAAG;MACH,GAAG;MACH,GAAG;;OAEF,GAAG;;CAET,CAAC,CAAC;AAEH,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAC5B,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAC3B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,sBAAsB,CAAC,OAAO,CAAC,CAAC;AAChC,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAE5B,qDAAqD;AACrD,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;AAE7E,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IAC7C,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,28 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
1
4
  import { detectBinary, runCommandWithTimeout } from "../helpers.js";
2
5
  import { createRuntimeHooks, SessionDriver } from "../session-driver.js";
3
6
  import { parseOpencodeLine } from "./parser.js";
7
+ function opencodeV2BinaryPath(homeDir = os.homedir()) {
8
+ return path.join(homeDir, ".opencode", "bin", "opencode");
9
+ }
10
+ function resolveOpencodeCommand(homeDir = os.homedir()) {
11
+ const localBin = opencodeV2BinaryPath(homeDir);
12
+ return fs.existsSync(localBin) ? localBin : "opencode";
13
+ }
4
14
  export class OpencodeDriver extends SessionDriver {
5
15
  id = "opencode";
16
+ detectedPath;
6
17
  hooks = createRuntimeHooks({
7
18
  parse: parseOpencodeLine,
8
19
  nativeKeys: ["sessionID", "sessionId", "session_id"],
9
20
  harness: "Opencode",
10
21
  });
11
22
  resumeError = "No native session id for Opencode resume";
23
+ getCommand() {
24
+ return this.detectedPath ?? resolveOpencodeCommand();
25
+ }
12
26
  buildArgs(options) {
13
27
  const args = ["run", "--format", "json"];
14
28
  if (options.model)
@@ -33,9 +47,17 @@ export class OpencodeDriver extends SessionDriver {
33
47
  };
34
48
  }
35
49
  async detect() {
36
- const res = await detectBinary("opencode");
37
- if (!res.installed)
50
+ let res = await detectBinary("opencode");
51
+ if (!res.installed) {
52
+ const localBin = opencodeV2BinaryPath();
53
+ if (fs.existsSync(localBin))
54
+ res = await detectBinary(localBin);
55
+ }
56
+ if (!res.installed || !res.path) {
57
+ this.detectedPath = undefined;
38
58
  return { installed: false, error: "opencode binary not found" };
59
+ }
60
+ this.detectedPath = res.path;
39
61
  return { installed: true, path: res.path, version: res.version, details: "opencode run --format json" };
40
62
  }
41
63
  async listModels(options) {
@@ -1 +1 @@
1
- {"version":3,"file":"driver.js","sourceRoot":"","sources":["../../../src/drivers/opencode/driver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"driver.js","sourceRoot":"","sources":["../../../src/drivers/opencode/driver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export type PaneToggleUi = {
2
+ open: () => Promise<unknown>;
3
+ close: () => Promise<unknown>;
4
+ };
5
+
6
+ export async function togglePane(isOpen: boolean, ui: PaneToggleUi): Promise<boolean> {
7
+ if (isOpen) {
8
+ await ui.close();
9
+ return false;
10
+ }
11
+ await ui.open();
12
+ return true;
13
+ }
@@ -7,6 +7,7 @@ import type { Register } from "claude-code";
7
7
  import { formatPane, paneButtonLabel, selectPane } from "../mods/agents/pane.js";
8
8
  import { parseRows } from "../mods/agents/parse.js";
9
9
  import type { PaneSnapshot } from "../mods/agents/types.js";
10
+ import { togglePane } from "./pane-toggle.js";
10
11
 
11
12
  // Stable pane id: 1 to 64 letters, digits, "_" or "-". open carries it into
12
13
  // e.requestId on the render event, which is how this module tells its own pane
@@ -42,6 +43,12 @@ type Engine$ = {
42
43
  };
43
44
  };
44
45
 
46
+ const toggleAgentsPane = async ($: Engine$, isOpen: boolean): Promise<boolean> =>
47
+ togglePane(isOpen, {
48
+ open: () => $.ui.open({ id: PANE_ID, side: "right" }),
49
+ close: () => $.ui.close({ id: PANE_ID }),
50
+ });
51
+
45
52
  // Passing $ into a helper is allowed, verified. What the engine refuses is
46
53
  // pulling a namespace off it: `const P = $.process` fails to load the module.
47
54
  // So refresh takes $ as a parameter and the state register owns travels beside
@@ -143,13 +150,7 @@ export const register: Register = (on) => {
143
150
  // is the one call here not yet verified in a PTY session, paneOpen stays
144
151
  // true and the next /band retries the close, instead of the flag and the
145
152
  // pane desyncing for the rest of the session.
146
- if (paneOpen) {
147
- await $.ui.close({ id: PANE_ID });
148
- paneOpen = false;
149
- } else {
150
- await $.ui.open({ id: PANE_ID, side: "right" });
151
- paneOpen = true;
152
- }
153
+ paneOpen = await toggleAgentsPane($, paneOpen);
153
154
  await $.ui.invalidate("ui.render");
154
155
  return { text: paneOpen ? "agents pane open" : "agents pane closed" };
155
156
  });
@@ -175,14 +176,9 @@ export const register: Register = (on) => {
175
176
  // A $ captured from a past render does work, verified, but it outlives the
176
177
  // event it came from and nothing promises how long.
177
178
  if ((e as { element?: string }).element === BUTTON_KEY) {
178
- await $.ui.open({ id: PANE_ID, side: "right" });
179
- // Opening an already open pane is a no-op, verified live, so the button
180
- // never has to know the truth. Re-syncing the flag is the point: the
181
- // pane's own close box closes it without firing a single hook, and after
182
- // that one /band would toggle the wrong way.
183
- paneOpen = true;
179
+ paneOpen = await toggleAgentsPane($, paneOpen);
184
180
  await $.ui.invalidate("ui.render");
185
- void refresh($, state);
181
+ if (paneOpen) void refresh($, state);
186
182
  }
187
183
  return await next(e);
188
184
  });
@@ -195,10 +191,9 @@ export const register: Register = (on) => {
195
191
  // wrong shape".
196
192
  on("ui.render", async ($, e, next) => {
197
193
  if (e.surface !== "terminal") return await next(e);
198
- // The way back in. The pane can be closed from its own close box, which
199
- // fires no hook, so without a visible affordance the only recovery is
200
- // knowing that /band exists. One button above the prompt, only when there
201
- // is a run behind it.
194
+ // The pane can be closed from its own close box, which fires no hook.
195
+ // Keep one button above the prompt as the visible toggle for the state
196
+ // transitions this module owns.
202
197
  if (e.component === "AbovePrompt") {
203
198
  if (!state.hasRun) return await next(e);
204
199
  const { Box, Button } = await $.ui.resolve(e, "Box", "Button");
@@ -1,6 +1,6 @@
1
1
  // Canvas layer of the orchestrator agents pane.
2
2
  //
3
- // The `codedeck web` canvas, drawn as ASCII boxes for a docked terminal pane.
3
+ // The run canvas, drawn as ASCII boxes for a docked terminal pane.
4
4
  // Two pure functions behind one frozen interface: selectPane narrows the
5
5
  // daemon's full session list to one run, formatPane turns that snapshot into
6
6
  // the lines of the drawing and fits them to the height the pane was given.
@@ -60,11 +60,14 @@ const STATUS_WORD = vocab({
60
60
  stopped: "Paused",
61
61
  failed: "Failed",
62
62
  });
63
+ // Brand marks from the JetBrains Nerd Font and Omarchy icon font. The pane is
64
+ // a terminal surface, so these keep the identity of each harness without
65
+ // spending the width of its full name.
63
66
  const HARNESS_GLYPH = vocab({
64
- claude: "▲",
65
- opencode: "■",
66
- codex: "◆",
67
- omp: "⬟",
67
+ claude: "\uEC82",
68
+ opencode: "\uE902",
69
+ codex: "\uEC81",
70
+ omp: "\uE903",
68
71
  });
69
72
  const HARNESS_LABEL = vocab({
70
73
  claude: "Claude",
@@ -184,6 +187,29 @@ function statusWord(status: unknown): string {
184
187
  return STATUS_WORD[text(status)] ?? cell(status);
185
188
  }
186
189
 
190
+ function footerLegend(columns: number): [string, string] {
191
+ const fullStatus = " ● working ◉ waiting ○ finished";
192
+ const fullHarness =
193
+ " " +
194
+ glyph("claude") +
195
+ " Claude " +
196
+ glyph("opencode") +
197
+ " OpenCode " +
198
+ glyph("codex") +
199
+ " Codex " +
200
+ glyph("omp") +
201
+ " OMP";
202
+ if (fullStatus.length <= columns - 2 && fullHarness.length <= columns - 2) {
203
+ return [fullStatus, fullHarness];
204
+ }
205
+
206
+ const compactStatus = " ● wk ◉ wait ○ done";
207
+ const iconHarness =
208
+ " " + glyph("claude") + " " + glyph("opencode") + " " + glyph("codex") + " " + glyph("omp");
209
+ if (compactStatus.length + 4 <= columns - 2) return [compactStatus, iconHarness];
210
+ return [" ● ◉ ○", iconHarness];
211
+ }
212
+
187
213
  function age(iso: unknown, now: number): string {
188
214
  if (typeof iso !== "string") return "";
189
215
  const then = Date.parse(iso);
@@ -356,11 +382,12 @@ function draw(snapshot: PaneSnapshot, columns: number, limit: number | undefined
356
382
  frameSep(),
357
383
  frameRow(""),
358
384
  ];
385
+ const [statusLegend, harnessLegend] = footerLegend(W);
359
386
  const tail = [
360
387
  frameRow(""),
361
388
  frameSep(),
362
- frameRow(" ● working ◉ waiting ○ finished"),
363
- frameRow(" ▲ Claude ■ OpenCode ◆ Codex ⬟ OMP"),
389
+ frameRow(statusLegend),
390
+ frameRow(harnessLegend),
364
391
  frameBot(),
365
392
  ];
366
393
  const fixed = head.length + tail.length;
@@ -443,10 +470,14 @@ export function paneButtonLabel(snapshot: PaneSnapshot | undefined): string {
443
470
  if (!snapshot || typeof snapshot !== "object") return "agents";
444
471
  try {
445
472
  const rows = Array.isArray(snapshot.rows) ? snapshot.rows : [];
446
- const working = rows.filter((row) => LIVE.has(row?.status ?? "")).length;
447
- if (working > 0) return `${working} working`;
448
- const waiting = rows.filter((row) => WAIT.has(row?.status ?? "")).length;
449
- return waiting > 0 ? `${waiting} waiting for you` : "no active agents";
473
+ const workingRows = rows.filter((row) => LIVE.has(row?.status ?? ""));
474
+ if (workingRows.length > 0) {
475
+ return `${workingRows.length} working ${workingRows.map((row) => glyph(row?.agent)).join("")}`;
476
+ }
477
+ const waitingRows = rows.filter((row) => WAIT.has(row?.status ?? ""));
478
+ return waitingRows.length > 0
479
+ ? `${waitingRows.length} waiting for you ${waitingRows.map((row) => glyph(row?.agent)).join("")}`
480
+ : "no active agents";
450
481
  } catch {
451
482
  return "agents";
452
483
  }
@@ -1,4 +1,4 @@
1
- /** Local staged/unstaged review UI served by `codedeck web` at `/review`.
1
+ /** Local staged/unstaged review UI served by `codedeck review`.
2
2
  *
3
3
  * Self-contained page (no external requests besides `api/review`): file
4
4
  * tree on the left, unified diff on the right, per-line comment boxes
@@ -103,7 +103,6 @@ export const REVIEW_PAGE = `<!doctype html>
103
103
  <span class="spacer"></span>
104
104
  <input id="filter" type="search" placeholder="filtrar arquivos…" aria-label="Filtrar arquivos">
105
105
  <button id="copyAll" type="button" title="Copiar todos os comentários em markdown">Copiar todos (<span id="nComments">0</span>)</button>
106
- <a href="/" style="color:#98989f;font-size:12.5px">canvas</a>
107
106
  </header>
108
107
  <div id="layout">
109
108
  <nav id="files" aria-label="Arquivos"><h2>ARQUIVOS</h2><ul id="fileList"></ul></nav>
@@ -1 +1 @@
1
- {"version":3,"file":"review-page.js","sourceRoot":"","sources":["../../src/web/review-page.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA+oB3B,CAAC"}
1
+ {"version":3,"file":"review-page.js","sourceRoot":"","sources":["../../src/web/review-page.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA8oB3B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codedeck",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
4
4
  "description": "CodeDeck, a local runtime for coding agents with session management, process supervision, event normalization, and git isolation",
5
5
  "type": "module",
6
6
  "bin": {