ccdeck 3.36.3 → 3.36.5

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.
@@ -64,8 +64,8 @@
64
64
  stored === "light" || (stored !== "dark" && light) ? "light" : "dark");
65
65
  })();
66
66
  </script>
67
- <script type="module" crossorigin src="/assets/index-Dj4JHRcG.js"></script>
68
- <link rel="stylesheet" crossorigin href="/assets/index-1U-Iz_x2.css">
67
+ <script type="module" crossorigin src="/assets/index-B18vwZ9I.js"></script>
68
+ <link rel="stylesheet" crossorigin href="/assets/index-BkXTN4dZ.css">
69
69
  </head>
70
70
  <body>
71
71
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccdeck",
3
- "version": "3.36.3",
3
+ "version": "3.36.5",
4
4
  "description": "Local dashboard for Claude Code and Codex CLI sessions: which Claude Code session needs input, every tool call and Claude Code subagent live, token cost and quota, several Claude accounts. Run npx ccdeck.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -62,7 +62,9 @@
62
62
  "3.35.0",
63
63
  "3.35.1",
64
64
  "3.36.0",
65
- "3.36.1"
65
+ "3.36.1",
66
+ "3.36.4",
67
+ "3.36.5"
66
68
  ],
67
69
  "3.36.3": [
68
70
  {
@@ -80,8 +80,8 @@ export const DEFAULTS = Object.freeze({
80
80
  // the server can read it with no page open.
81
81
  autoUpdate: true,
82
82
  // USAGE REPORTS (#1853), ON unless somebody turns them off — the owner's
83
- // decision, 2026-09-30: nobody is asked, the README says what is sent, and
84
- // Appearance holds the switch. See reports.mjs for what is sent, and
83
+ // decision, 2026-09-30: nobody is asked and the README says what is sent; the
84
+ // page has no switch since 2026-10-01. See reports.mjs for what is sent, and
85
85
  // reportsVetoed for the launch-time veto that wins over this.
86
86
  reports: true,
87
87
  // What reports.mjs keeps between runs, and the page never sees
@@ -147,6 +147,19 @@ export function createOutputWatch(io = {}) {
147
147
  function clear() { seen.clear(); }
148
148
  function size() { return seen.size; }
149
149
 
150
+ /** How many distinct projects the live sessions sit in, read off the paths
151
+ * already held — no new state. Claude keeps one transcript folder per project
152
+ * under ~/.claude/projects, so distinct folders are distinct projects. Only
153
+ * the count is ever read; no path or project name leaves. */
154
+ function projectCount() {
155
+ const projects = new Set();
156
+ for (const { path } of seen.values()) {
157
+ const cut = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
158
+ projects.add(cut > 0 ? path.slice(0, cut) : path);
159
+ }
160
+ return projects.size;
161
+ }
162
+
150
163
  /**
151
164
  * One tick. Stats each named session's file, reads only what is new, and
152
165
  * answers with EVERY block that landed in it, oldest first — see blocksIn
@@ -270,5 +283,5 @@ export function createOutputWatch(io = {}) {
270
283
  }
271
284
  }
272
285
 
273
- return { note, forget, clear, size, poll };
286
+ return { note, forget, clear, size, projectCount, poll };
274
287
  }
@@ -1,5 +1,5 @@
1
- // The page's three ways into reports.mjs (#1853): the switch in Appearance, an
2
- // error the page caught, and the feedback dialog. The page never
1
+ // The page's ways into reports.mjs (#1853): an error the page caught and the
2
+ // feedback dialog. (The switch in Appearance went on 2026-10-01; its route stays.) The page never
3
3
  // talks to api.ccdeck.dev itself — this server does, which is also why the
4
4
  // install id never has to reach the page.
5
5
 
@@ -19,7 +19,8 @@ async function readJson(req, res, limit) {
19
19
  }
20
20
  }
21
21
 
22
- /** `POST /api/reports` `{ on }`: Appearance's "Send usage reports" switch. */
22
+ /** `POST /api/reports` `{ on }`: sets `prefs.reports`. No page control calls it since the owner
23
+ * removed Appearance's switch (2026-10-01); kept so a deck can still be switched off over the API. */
23
24
  export async function handleReportsWrite(req, res, { report = reporter } = {}) {
24
25
  const body = await readJson(req, res, 1_000);
25
26
  if (typeof body?.on !== "boolean") return send(res, 400, { ok: false, reason: "bad_request" });
@@ -40,8 +40,9 @@
40
40
  // so it stops entirely the moment reports are switched off or vetoed.
41
41
  //
42
42
  // NOBODY IS ASKED, AND NOTHING IS HIDDEN. The owner chose on-by-default
43
- // (2026-09-30): the README says what is sent, Appearance holds the switch, and
44
- // AGENTS_DECK_NO_REPORTS=1 keeps it off from the first start.
43
+ // (2026-09-30): the README says what is sent, and AGENTS_DECK_NO_REPORTS=1 keeps it
44
+ // off from the first start. The deck has no switch for it since the owner removed
45
+ // Appearance's on 2026-10-01.
45
46
  //
46
47
  // SWITCHING IT OFF DELETES WHAT WAS SENT. The id is forgotten here and the API
47
48
  // is asked to drop every event and error it holds for it; if that request
@@ -533,17 +534,19 @@ export function createReporter({
533
534
  // the CLI probe into a fast path like `--version` that imports it for nothing.
534
535
 
535
536
  /** The coarse counts the "active" report carries, read from the state the deck
536
- * already keeps — the LRU of tracked sessions and the per-session subagent
537
- * signatures. Never new tracking, and never anything but a number: no session
538
- * id, name or path. `projects` is left out for now — its only source is the
539
- * disk-backed, per-account rollup (account-projects.mjs), which has no cheap
540
- * synchronous count — so the field is simply not sent. */
537
+ * already keeps — the LRU of tracked sessions, the per-session subagent
538
+ * signatures, and the folders those sessions' transcripts sit in. Never new
539
+ * tracking, and never anything but a number: no session id, project name or
540
+ * path, only how many. `projects` is the distinct transcript folders among the
541
+ * live sessions, so it counts Claude projects; a Codex-only deck sends no
542
+ * transcript path and so reports no projects. */
541
543
  async function deckUsage() {
542
544
  const out = {};
543
545
  try {
544
- const { trackedSessionCount } = await import("./session-tracking.mjs");
546
+ const { trackedSessionCount, trackedProjectCount } = await import("./session-tracking.mjs");
545
547
  out.sessions = trackedSessionCount();
546
- } catch { /* count unavailable: omitted */ }
548
+ out.projects = trackedProjectCount();
549
+ } catch { /* counts unavailable: omitted */ }
547
550
  try {
548
551
  const { trackedSubagentCount } = await import("./session-enrichment.mjs");
549
552
  out.subagents = trackedSubagentCount();
@@ -156,6 +156,14 @@ function trackedSessionCount() {
156
156
  return sessionTouchedAt.size;
157
157
  }
158
158
 
159
+ /** How many distinct projects those live sessions sit in — the same live set,
160
+ * counted by the folder each session's transcript is in. Reused, not new state:
161
+ * the watch already holds every live session's transcript path. Never a project
162
+ * name or path — only the count. */
163
+ function trackedProjectCount() {
164
+ return outputWatch.projectCount();
165
+ }
166
+
159
167
  function touchSession(sid) {
160
168
  if (!sid || typeof sid !== "string") return;
161
169
  // Re-insert so the Map's own insertion order *is* the LRU order and eviction
@@ -248,4 +256,4 @@ async function handleForget(req, res) {
248
256
  // What index.mjs calls besides HARD_TRACKED_SESSIONS. Listed rather than
249
257
  // marked at each declaration, so the declarations read as they did where they
250
258
  // came from.
251
- export { handleForget, outputWatch, startOutputWatch, touchSession, trackedSessionCount };
259
+ export { handleForget, outputWatch, startOutputWatch, touchSession, trackedProjectCount, trackedSessionCount };