codedeck 0.6.0 → 0.7.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.
Files changed (140) hide show
  1. package/README.md +50 -35
  2. package/dist/cli/commands/claims.js +3 -2
  3. package/dist/cli/commands/claims.js.map +1 -1
  4. package/dist/cli/commands/doctor.js +8 -40
  5. package/dist/cli/commands/doctor.js.map +1 -1
  6. package/dist/cli/commands/logs.js +2 -2
  7. package/dist/cli/commands/logs.js.map +1 -1
  8. package/dist/cli/commands/open.js +102 -11
  9. package/dist/cli/commands/open.js.map +1 -1
  10. package/dist/cli/commands/ps.js +6 -6
  11. package/dist/cli/commands/ps.js.map +1 -1
  12. package/dist/cli/commands/review.js +36 -58
  13. package/dist/cli/commands/review.js.map +1 -1
  14. package/dist/cli/commands/run.js +27 -27
  15. package/dist/cli/commands/run.js.map +1 -1
  16. package/dist/cli/commands/setup.js +96 -314
  17. package/dist/cli/commands/setup.js.map +1 -1
  18. package/dist/cli/commands/show.js +2 -1
  19. package/dist/cli/commands/show.js.map +1 -1
  20. package/dist/cli/commands/ui.js +61 -0
  21. package/dist/cli/commands/ui.js.map +1 -0
  22. package/dist/cli/commands/usage-backfill.js +149 -0
  23. package/dist/cli/commands/usage-backfill.js.map +1 -0
  24. package/dist/cli/commands/usage.js +113 -45
  25. package/dist/cli/commands/usage.js.map +1 -1
  26. package/dist/cli/commands/wait.js +3 -2
  27. package/dist/cli/commands/wait.js.map +1 -1
  28. package/dist/cli/index.js +55 -39
  29. package/dist/cli/index.js.map +1 -1
  30. package/dist/cli/web-launch.js +126 -0
  31. package/dist/cli/web-launch.js.map +1 -0
  32. package/dist/config/config.js +0 -70
  33. package/dist/config/config.js.map +1 -1
  34. package/dist/config/paths.js +1 -0
  35. package/dist/config/paths.js.map +1 -1
  36. package/dist/config/setup.js +248 -0
  37. package/dist/config/setup.js.map +1 -0
  38. package/dist/config/web-host.js +31 -0
  39. package/dist/config/web-host.js.map +1 -0
  40. package/dist/config/web-port.js +24 -0
  41. package/dist/config/web-port.js.map +1 -0
  42. package/dist/core/claude-transcript.js +226 -0
  43. package/dist/core/claude-transcript.js.map +1 -0
  44. package/dist/core/errors.js +17 -0
  45. package/dist/core/errors.js.map +1 -1
  46. package/dist/core/models.js +4 -1
  47. package/dist/core/models.js.map +1 -1
  48. package/dist/core/pricing.js +87 -55
  49. package/dist/core/pricing.js.map +1 -1
  50. package/dist/core/roles.js +6 -5
  51. package/dist/core/roles.js.map +1 -1
  52. package/dist/core/run-skills.js +114 -0
  53. package/dist/core/run-skills.js.map +1 -0
  54. package/dist/core/run-usage.js +56 -10
  55. package/dist/core/run-usage.js.map +1 -1
  56. package/dist/core/session.js +6 -16
  57. package/dist/core/session.js.map +1 -1
  58. package/dist/core/usage-query.js +41 -0
  59. package/dist/core/usage-query.js.map +1 -0
  60. package/dist/core/usage-source.js +17 -0
  61. package/dist/core/usage-source.js.map +1 -0
  62. package/dist/daemon/build-id.js +30 -0
  63. package/dist/daemon/build-id.js.map +1 -0
  64. package/dist/daemon/daemon.js +1257 -700
  65. package/dist/daemon/daemon.js.map +1 -1
  66. package/dist/daemon/instance-lock.js +48 -0
  67. package/dist/daemon/instance-lock.js.map +1 -0
  68. package/dist/daemon/ipc.js +4 -3
  69. package/dist/daemon/ipc.js.map +1 -1
  70. package/dist/daemon/web-supervisor.js +251 -0
  71. package/dist/daemon/web-supervisor.js.map +1 -0
  72. package/dist/drivers/claude/driver.js +1 -0
  73. package/dist/drivers/claude/driver.js.map +1 -1
  74. package/dist/drivers/codex/driver.js +14 -3
  75. package/dist/drivers/codex/driver.js.map +1 -1
  76. package/dist/drivers/codex/host-skills.js +57 -0
  77. package/dist/drivers/codex/host-skills.js.map +1 -0
  78. package/dist/drivers/opencode/driver.js +6 -2
  79. package/dist/drivers/opencode/driver.js.map +1 -1
  80. package/dist/drivers/session-driver.js +7 -1
  81. package/dist/drivers/session-driver.js.map +1 -1
  82. package/dist/drivers/session-runtime.js +2 -0
  83. package/dist/drivers/session-runtime.js.map +1 -1
  84. package/dist/drivers/terminal.js +15 -0
  85. package/dist/drivers/terminal.js.map +1 -1
  86. package/dist/git/diff.js +29 -4
  87. package/dist/git/diff.js.map +1 -1
  88. package/dist/open/contract.js.map +1 -1
  89. package/dist/open/launchers/claude.js +78 -2
  90. package/dist/open/launchers/claude.js.map +1 -1
  91. package/dist/open/link-watcher.js +44 -0
  92. package/dist/open/link-watcher.js.map +1 -0
  93. package/dist/open/pty.js +330 -5
  94. package/dist/open/pty.js.map +1 -1
  95. package/dist/open/runtime.js +15 -12
  96. package/dist/open/runtime.js.map +1 -1
  97. package/dist/plugin/agents/general.md +33 -6
  98. package/dist/plugin/hooks/pane-ticker.ts +102 -0
  99. package/dist/plugin/hooks/register.tsx +39 -18
  100. package/dist/plugin/hooks/session-id.sh +1 -1
  101. package/dist/plugin/mods/agents/pane.ts +285 -156
  102. package/dist/plugin/mods/agents/types.ts +23 -3
  103. package/dist/plugin/statusline.sh +57 -32
  104. package/dist/store/database.js +49 -1
  105. package/dist/store/database.js.map +1 -1
  106. package/dist/store/native-links.js +80 -0
  107. package/dist/store/native-links.js.map +1 -0
  108. package/dist/store/sessions.js +166 -51
  109. package/dist/store/sessions.js.map +1 -1
  110. package/dist/store/usage-ledger.js +153 -0
  111. package/dist/store/usage-ledger.js.map +1 -0
  112. package/dist/utils/process.js +2 -16
  113. package/dist/utils/process.js.map +1 -1
  114. package/dist/web/brand.js +59 -0
  115. package/dist/web/brand.js.map +1 -0
  116. package/dist/web/child.js +67 -0
  117. package/dist/web/child.js.map +1 -0
  118. package/dist/web/home-page.js +48 -0
  119. package/dist/web/home-page.js.map +1 -0
  120. package/dist/web/review-page.js +12 -5
  121. package/dist/web/review-page.js.map +1 -1
  122. package/dist/web/security.js +170 -0
  123. package/dist/web/security.js.map +1 -0
  124. package/dist/web/server.js +198 -0
  125. package/dist/web/server.js.map +1 -0
  126. package/dist/web/setup-icons.js +37 -0
  127. package/dist/web/setup-icons.js.map +1 -0
  128. package/dist/web/setup-page.js +1795 -0
  129. package/dist/web/setup-page.js.map +1 -0
  130. package/dist/web/setup-routes.js +475 -0
  131. package/dist/web/setup-routes.js.map +1 -0
  132. package/dist/web/usage-page.js +609 -0
  133. package/dist/web/usage-page.js.map +1 -0
  134. package/dist/web/usage-routes.js +88 -0
  135. package/dist/web/usage-routes.js.map +1 -0
  136. package/dist/web/web-token.js +56 -0
  137. package/dist/web/web-token.js.map +1 -0
  138. package/package.json +1 -1
  139. package/dist/plugin/prompts/_partials/commit.md +0 -145
  140. package/dist/plugin/prompts/_partials/pr-writer.md +0 -186
@@ -26,7 +26,7 @@ description: Do CodeDeck work directly in the current workspace, with evidence.
26
26
 
27
27
  - Once the task is clear in a `codedeck run` worker, rename your session with `codedeck rename "$CODEDECK_SESSION_ID" <short-task-slug>`.
28
28
 
29
- <!-- Source: ~/.claude/skills/commit/SKILL.md (vendored full text). Adaptations: (1) the create-branch skill reference is a one-line main/master rule below, the skill itself is not vendored; (2) any sentry-skills:commit reference points to the Commits section in this prompt; (3) Co-Authored-By names the harness that did the work, not Claude. -->
29
+ <!-- Generated from skills/commit/SKILL.md by scripts/copy-plugin.mjs. Edit the skill, not the agent file. -->
30
30
 
31
31
  ## Commits
32
32
 
@@ -172,7 +172,7 @@ Reason: Caused performance regression in production.
172
172
 
173
173
  - [Sentry Commit Messages](https://develop.sentry.dev/engineering-practices/commit-messages/)
174
174
 
175
- <!-- Source: ~/.claude/skills/pr-writer/SKILL.md (vendored full text). Adaptations: (1) the create-branch skill reference is a one-line main/master rule, the skill itself is not vendored; (2) the sentry-skills:commit reference below points to the Commits section in this prompt; (3) Co-Authored-By names the harness that did the work, not Claude. -->
175
+ <!-- Generated from skills/pr-writer/SKILL.md by scripts/copy-plugin.mjs. Edit the skill, not the agent file. -->
176
176
 
177
177
  ## Pull requests
178
178
 
@@ -251,6 +251,7 @@ Use this structure for PR descriptions (ignoring any repository PR templates):
251
251
  - Links to relevant issues or tickets
252
252
  - Context that isn't obvious from the code
253
253
  - Notes on specific areas that need careful review
254
+ - Visual evidence when it passes the gate in Visual Evidence below
254
255
 
255
256
  ### Step 4: Create the PR
256
257
 
@@ -321,6 +322,36 @@ Reference issues in the PR body:
321
322
  | `Refs GH-1234` | Links without closing |
322
323
  | `Refs LINEAR-ABC-123` | Links Linear issue |
323
324
 
325
+ ## Visual Evidence
326
+
327
+ Default to none. Add a visual only when a reviewer would otherwise have to check out the branch to see the change, or when the text needs a paragraph to describe what one picture shows. One visual per PR is the norm; never add one to fill space.
328
+
329
+ | Change | Evidence |
330
+ |--------|----------|
331
+ | UI layout or styling | Screenshot, before/after when it changes existing UI |
332
+ | Interaction or animation | GIF, 15s or less |
333
+ | CLI/TUI output | Fenced text block with the real output, not a screenshot |
334
+ | New flow across 3+ components, or a changed state machine | Mermaid diagram |
335
+ | Refactor, config, deps, internal bug fix, tests | None |
336
+
337
+ Rules:
338
+ - Capture from the running app (Playwright for web, `ffmpeg` to turn a recording into a GIF). Never mock, edit or stage a screenshot.
339
+ - A Mermaid diagram shows the mechanism the PR adds or changes, not the whole system. GitHub renders ` ```mermaid ` blocks natively.
340
+ - Put each visual under the sentence it supports, with a one-line caption.
341
+
342
+ Hosting images: `gh` cannot upload attachments, so push them to an orphan `pr-assets` branch and link them. Never commit them to the PR branch.
343
+
344
+ ```bash
345
+ B=$(git branch --show-current)
346
+ git fetch -q origin pr-assets:pr-assets 2>/dev/null || git branch pr-assets "$(git commit-tree "$(git mktree </dev/null)" -m 'chore: Start PR assets')"
347
+ d=$(mktemp -d); git worktree add -q "$d" pr-assets
348
+ mkdir -p "$d/$B"; cp shot.png "$d/$B/"
349
+ git -C "$d" add -A && git -C "$d" commit -qm "chore: Add PR assets for $B" && git -C "$d" push -q origin pr-assets
350
+ git worktree remove "$d"
351
+ ```
352
+
353
+ Link as `![caption](https://github.com/OWNER/REPO/blob/pr-assets/BRANCH/shot.png?raw=true)`. This form also renders on private repos. For a video that must stay a video, leave the file path in your report and say the user has to drag it into the PR on GitHub.
354
+
324
355
  ## Guidelines
325
356
 
326
357
  - **One PR per feature/fix** - Don't bundle unrelated changes
@@ -338,14 +369,10 @@ gh api -X PATCH repos/{owner}/{repo}/pulls/PR_NUMBER -f body="$(cat <<'EOF'
338
369
  Updated description here
339
370
  EOF
340
371
  )"
341
- ```
342
372
 
343
- ```bash
344
373
  # Update PR title
345
374
  gh api -X PATCH repos/{owner}/{repo}/pulls/PR_NUMBER -f title='new: Title here'
346
- ```
347
375
 
348
- ```bash
349
376
  # Update both
350
377
  gh api -X PATCH repos/{owner}/{repo}/pulls/PR_NUMBER \
351
378
  -f title='new: Title' \
@@ -0,0 +1,102 @@
1
+ import type { PaneSnapshot } from "../mods/agents/types.js";
2
+
3
+ const TICK_INTERVAL_MS = 1000;
4
+ const REFRESH_INTERVAL_MS = 5000;
5
+ const LIVE_STATUSES = new Set(["working", "starting", "needs_input"]);
6
+
7
+ export interface PaneTickerOptions {
8
+ now: () => number;
9
+ setInterval: (callback: () => void, milliseconds: number) => unknown;
10
+ clearInterval: (handle: unknown) => void;
11
+ invalidate: () => unknown;
12
+ refresh: () => unknown;
13
+ }
14
+
15
+ export interface PaneTicker {
16
+ update(paneOpen: boolean, snapshot: PaneSnapshot | undefined): void;
17
+ refreshed(): void;
18
+ }
19
+
20
+ /** Schedules pane redraws and refresh requests while any worker card is live. */
21
+ export function createPaneTicker(options: PaneTickerOptions): PaneTicker {
22
+ let running = false;
23
+ let handle: unknown;
24
+ let lastRefreshAt = 0;
25
+
26
+ const invoke = (action: () => unknown) => {
27
+ try {
28
+ void Promise.resolve(action()).catch(() => {});
29
+ } catch {
30
+ // Hook failures drop the drawing, so timer work must stay contained.
31
+ }
32
+ };
33
+
34
+ const tick = () => {
35
+ if (!running) return;
36
+ try {
37
+ invoke(options.invalidate);
38
+ const now = options.now();
39
+ if (!Number.isFinite(now) || now - lastRefreshAt < REFRESH_INTERVAL_MS) return;
40
+ lastRefreshAt = now;
41
+ invoke(options.refresh);
42
+ } catch {
43
+ // A failed timer callback must not escape into the hooks runtime.
44
+ }
45
+ };
46
+
47
+ const stop = () => {
48
+ if (!running) return;
49
+ running = false;
50
+ const currentHandle = handle;
51
+ handle = undefined;
52
+ try {
53
+ options.clearInterval(currentHandle);
54
+ } catch {
55
+ // Keep the module alive even if the timer host refuses a clear.
56
+ }
57
+ };
58
+
59
+ const start = () => {
60
+ if (running) return;
61
+ try {
62
+ const now = options.now();
63
+ if (!Number.isFinite(now)) return;
64
+ lastRefreshAt = now;
65
+ running = true;
66
+ handle = options.setInterval(tick, TICK_INTERVAL_MS);
67
+ } catch {
68
+ running = false;
69
+ handle = undefined;
70
+ }
71
+ };
72
+
73
+ const hasLiveRow = (snapshot: PaneSnapshot | undefined): boolean => {
74
+ try {
75
+ return (
76
+ snapshot !== undefined &&
77
+ Array.isArray(snapshot.rows) &&
78
+ snapshot.rows.some((row) => row != null && LIVE_STATUSES.has(row.status))
79
+ );
80
+ } catch {
81
+ return false;
82
+ }
83
+ };
84
+
85
+ return {
86
+ update(paneOpen, snapshot) {
87
+ if (paneOpen && hasLiveRow(snapshot)) {
88
+ start();
89
+ return;
90
+ }
91
+ stop();
92
+ },
93
+ refreshed() {
94
+ try {
95
+ const now = options.now();
96
+ if (Number.isFinite(now)) lastRefreshAt = now;
97
+ } catch {
98
+ // Keep the prior cadence if the injected clock fails.
99
+ }
100
+ },
101
+ };
102
+ }
@@ -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 { createPaneTicker, type PaneTicker } from "./pane-ticker.js";
10
11
  import { togglePane } from "./pane-toggle.js";
11
12
 
12
13
  // Stable pane id: 1 to 64 letters, digits, "_" or "-". open carries it into
@@ -51,14 +52,17 @@ const toggleAgentsPane = async ($: Engine$, isOpen: boolean): Promise<boolean> =
51
52
 
52
53
  // Passing $ into a helper is allowed, verified. What the engine refuses is
53
54
  // pulling a namespace off it: `const P = $.process` fails to load the module.
54
- // So refresh takes $ as a parameter and the state register owns travels beside
55
- // it in this object. paneOpen never leaves register, so it stays a bare let.
55
+ // refresh takes $ as a parameter; its state and paneOpen stay in this register.
56
56
  type PaneState = {
57
57
  // Last good snapshot; undefined until one refresh has fully succeeded, which
58
58
  // is how the pane draws nothing rather than a guess.
59
59
  snapshot: PaneSnapshot | undefined;
60
60
  inFlight: boolean;
61
61
  lastRefreshEndedAt: number;
62
+ ticker: PaneTicker | undefined;
63
+ paneOpen: boolean;
64
+ tickInvalidate: () => unknown;
65
+ tickRefresh: () => unknown;
62
66
  // True once session.start saw a CODEDECK_RUN_ID. Cached because ui.render
63
67
  // fires on every drawing pass and must not await an env read to decide
64
68
  // whether to draw one button.
@@ -67,6 +71,13 @@ type PaneState = {
67
71
 
68
72
  const TOOL_REFRESH_GAP_MS = 1500;
69
73
 
74
+ function syncTicker($: Engine$, state: PaneState, refreshCompleted = false): void {
75
+ state.tickInvalidate = () => $.ui.invalidate("ui.render");
76
+ state.tickRefresh = () => refresh($, state);
77
+ if (refreshCompleted) state.ticker?.refreshed();
78
+ state.ticker?.update(state.paneOpen, state.snapshot);
79
+ }
80
+
70
81
  const refresh = async ($: Engine$, state: PaneState): Promise<void> => {
71
82
  // Claim the slot before the first await. Guard and set must not straddle a
72
83
  // yield point: with the `$.env.get` read in between, two tool.call firings
@@ -103,6 +114,7 @@ const refresh = async ($: Engine$, state: PaneState): Promise<void> => {
103
114
  } finally {
104
115
  state.inFlight = false;
105
116
  state.lastRefreshEndedAt = Date.now();
117
+ syncTicker($, state, true);
106
118
  }
107
119
  };
108
120
 
@@ -112,10 +124,21 @@ export const register: Register = (on) => {
112
124
  inFlight: false,
113
125
  lastRefreshEndedAt: 0,
114
126
  hasRun: false,
127
+ ticker: undefined,
128
+ paneOpen: false,
129
+ tickInvalidate: () => undefined,
130
+ tickRefresh: () => undefined,
115
131
  };
116
- let paneOpen = false;
132
+ state.ticker = createPaneTicker({
133
+ now: () => Date.now(),
134
+ setInterval: (callback, milliseconds) => setInterval(callback, milliseconds),
135
+ clearInterval: (handle) => clearInterval(handle as ReturnType<typeof setInterval>),
136
+ invalidate: () => state.tickInvalidate(),
137
+ refresh: () => state.tickRefresh(),
138
+ });
117
139
 
118
140
  on("session.start", async ($, e, next) => {
141
+ syncTicker($, state);
119
142
  // Not "agents": the engine refuses it with `$.command.register: "/agents"
120
143
  // refused: it is the built-in /agents`. "band", "deck" and
121
144
  // "codedeck-agents" were each verified free. The description is load
@@ -125,20 +148,15 @@ export const register: Register = (on) => {
125
148
  description: "toggle the codedeck agents pane",
126
149
  immediate: true,
127
150
  });
128
- // Only open when there is actually a run behind the pane. A session that
129
- // loads this plugin by manual --plugin-dir or a global install has no
130
- // CODEDECK_RUN_ID, and the dock there is permanently empty while eating
131
- // most of the screen for the whole session. /band stays the escape hatch
132
- // for anyone who wants the pane anyway.
151
+ // Only show the button when there is actually a run behind the pane. A
152
+ // session that loads this plugin by manual --plugin-dir or a global
153
+ // install has no CODEDECK_RUN_ID, and its dock stays empty. /band remains
154
+ // available for anyone who wants to open it anyway.
133
155
  const runId = await $.env.get("CODEDECK_RUN_ID");
134
156
  state.hasRun = Boolean(runId);
135
157
  if (runId) {
136
- // No width is requested: the engine sizes the dock itself and reports
137
- // the usable content back as e.props.bodyColumns on every render.
138
- await $.ui.open({ id: PANE_ID, side: "right" });
139
- paneOpen = true;
140
- // Populate before the first turn, or the pane stays blank through the
141
- // whole opening exchange, which is exactly when the run is dispatched.
158
+ // Prime the button label without taking screen space when the session
159
+ // starts. The panel opens only on an explicit button press or /band.
142
160
  void refresh($, state);
143
161
  }
144
162
  return next(e);
@@ -150,9 +168,11 @@ export const register: Register = (on) => {
150
168
  // is the one call here not yet verified in a PTY session, paneOpen stays
151
169
  // true and the next /band retries the close, instead of the flag and the
152
170
  // pane desyncing for the rest of the session.
153
- paneOpen = await toggleAgentsPane($, paneOpen);
171
+ state.paneOpen = await toggleAgentsPane($, state.paneOpen);
172
+ syncTicker($, state);
154
173
  await $.ui.invalidate("ui.render");
155
- return { text: paneOpen ? "agents pane open" : "agents pane closed" };
174
+ if (state.paneOpen) void refresh($, state);
175
+ return { text: state.paneOpen ? "agents pane open" : "agents pane closed" };
156
176
  });
157
177
 
158
178
  on("turn.complete", async ($, e, next) => {
@@ -176,9 +196,10 @@ export const register: Register = (on) => {
176
196
  // A $ captured from a past render does work, verified, but it outlives the
177
197
  // event it came from and nothing promises how long.
178
198
  if ((e as { element?: string }).element === BUTTON_KEY) {
179
- paneOpen = await toggleAgentsPane($, paneOpen);
199
+ state.paneOpen = await toggleAgentsPane($, state.paneOpen);
200
+ syncTicker($, state);
180
201
  await $.ui.invalidate("ui.render");
181
- if (paneOpen) void refresh($, state);
202
+ if (state.paneOpen) void refresh($, state);
182
203
  }
183
204
  return await next(e);
184
205
  });
@@ -20,4 +20,4 @@ target=${CODEDECK_SESSION_FILE:-}
20
20
  id=$(grep -oE '"session_id":"[0-9a-fA-F-]+"' | head -1 | cut -d'"' -f4)
21
21
  [[ -n "$id" ]] || exit 0
22
22
 
23
- printf '%s' "$id" > "$target"
23
+ printf '%s\n' "$id" >> "$target"