ccdeck 3.41.1 → 3.41.3

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.
@@ -66,7 +66,7 @@
66
66
  document.documentElement.setAttribute("data-color-scheme", lightThemes.indexOf(theme) >= 0 ? "light" : "dark");
67
67
  })();
68
68
  </script>
69
- <script type="module" crossorigin src="/assets/index-2Z0Tr6cq.js"></script>
69
+ <script type="module" crossorigin src="/assets/index-C80Hp_2s.js"></script>
70
70
  <link rel="stylesheet" crossorigin href="/assets/index-BgSnAnP6.css">
71
71
  </head>
72
72
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccdeck",
3
- "version": "3.41.1",
3
+ "version": "3.41.3",
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": {
@@ -75,7 +75,8 @@
75
75
  "3.38.2",
76
76
  "3.38.6",
77
77
  "3.39.4",
78
- "3.40.1", "3.40.2"
78
+ "3.40.1",
79
+ "3.41.3"
79
80
  ],
80
81
  "3.41.1": [
81
82
  {
@@ -121,6 +122,12 @@
121
122
  "body": "Change individual telemetry flags and use a simpler Monitor view."
122
123
  }
123
124
  ],
125
+ "3.40.2": [
126
+ {
127
+ "title": "📻 Music is in Radio now, not in Settings",
128
+ "body": "Settings no longer has a Music section. Claude FM, Lofi Girl and the streams you added are under Workspace streams in Radio, on the right sidebar, and Radio’s player has the volume and mute. Your station, volume and streams are kept. Adding, renaming or removing a stream link is no longer offered."
129
+ }
130
+ ],
124
131
  "3.40.0": [
125
132
  {
126
133
  "title": "📻 Explore radio around the world",
@@ -128,7 +135,7 @@
128
135
  },
129
136
  {
130
137
  "title": "🦊 Choose your canvas companion",
131
- "body": "Choose Deck companion, Fox or Penguin in Settings › Appearance. Your companion shares the existing canvas movements and music controls, and your choice stays saved."
138
+ "body": "Choose Deck companion, Fox or Penguin in Settings › General. Your companion shares the existing canvas movements and music controls, and your choice stays saved."
132
139
  },
133
140
  {
134
141
  "title": "📊 See what each session used",
@@ -147,12 +154,16 @@
147
154
  {
148
155
  "title": "Clearer account usage and Telemetry Radar",
149
156
  "body": "Account usage shows reset times together with capacity. Telemetry Radar has a clearer monitoring workspace and keeps history for up to 24 hours. Radar remains Beta."
157
+ },
158
+ {
159
+ "title": "🔎 Telemetry Radar on Linux and Windows, with no Terminal step",
160
+ "body": "Radar now works on macOS, Linux and Windows. Press Start monitoring and ccdeck runs the capture itself, with no command to paste into Terminal, and keeps listening until you stop it. Capture needs a one-time setup for your user: Wireshark’s ChmodBPF helper on macOS, tcpdump with capture permission on Linux, or Wireshark with Npcap on Windows. Radar says what is missing, and once permission is granted it starts listening by itself."
150
161
  }
151
162
  ],
152
163
  "3.39.1": [
153
164
  {
154
165
  "title": "🎨 More themes, with clearer contrast",
155
- "body": "Choose Omarchy, Matrix, Black Contrast, White Contrast or Catppuccin Mocha in Settings › Appearance. Fresh installations start with Rider Black; your saved theme choice is preserved."
166
+ "body": "Choose Omarchy, Matrix, Black Contrast, White Contrast or Catppuccin Mocha in Settings › General. Fresh installations start with Rider Black; your saved theme choice is preserved."
156
167
  },
157
168
  {
158
169
  "title": "A little more room on the canvas",
@@ -162,7 +173,7 @@
162
173
  "3.39.0": [
163
174
  {
164
175
  "title": "🔎 Inspect Claude Code telemetry on macOS",
165
- "body": "Telemetry Radar shows Claude Code's configured telemetry and can inspect traffic at destinations you choose. When settings are absent, it can show observed connections separately; a connection alone does not prove telemetry or reveal message contents. Capture starts only after you run its generated command in Terminal, which uses macOS tcpdump with administrator permission. Captured telemetry can contain prompt or response content, and other apps sending to the same destination may appear too. Messages stay in ccdeck's memory for up to five minutes; no capture file is written."
176
+ "body": "Telemetry Radar shows Claude Code's configured telemetry and can inspect traffic at destinations you choose. When settings are absent, it can show observed connections separately; a connection alone does not prove telemetry or reveal message contents. Captured telemetry can contain prompt or response content, and other apps sending to the same destination may appear too. Captured messages stay in ccdeck's memory, and no capture file is written."
166
177
  },
167
178
  {
168
179
  "title": "🧭 Open panels from the edges",
@@ -174,13 +185,13 @@
174
185
  },
175
186
  {
176
187
  "title": "🎨 Two new dark themes",
177
- "body": "Choose Rider Black or VS Code Black in Settings › Appearance. Both add a darker canvas with flatter panels and distinct focus accents; your existing Light or Dark choice stays as it was."
188
+ "body": "Choose Rider Black or VS Code Black in Settings › General. Both add a darker canvas with flatter panels and distinct focus accents; your existing Light or Dark choice stays as it was."
178
189
  }
179
190
  ],
180
191
  "3.38.5": [
181
192
  {
182
193
  "title": "🔧 Settings are in one place now",
183
- "body": "The theme, the sounds, notifications, Claude FM and the minimap character are all in Settings, behind the gear at the right of the topbar, or Cmd+, (Ctrl+, on Windows and Linux). The speaker beside it keeps the quick things one click away: sounds on or off, and how loud each tone is. Nothing you had set has changed, it has only moved, and T still switches the theme. The auto-switch threshold also takes a custom value now: pick Custom… and type a whole percent from 50 to 99."
194
+ "body": "The theme, the sounds, notifications and the minimap character are all in Settings, behind the gear at the right of the topbar, or Cmd+, (Ctrl+, on Windows and Linux). V opens it at Sounds, with each tone's volume and sound, and M turns sounds off or on from anywhere. Nothing you had set has changed, it has only moved, and T still switches the theme. The auto-switch threshold also takes a custom value now: pick Custom… and type a whole percent from 50 to 99."
184
195
  },
185
196
  {
186
197
  "title": "Windows: no more temperature stuck at 28 °C",
@@ -190,7 +201,7 @@
190
201
  "3.38.4": [
191
202
  {
192
203
  "title": "🔔 A notification when Auto-switch changes your account",
193
- "body": "When Auto-switch moves Claude Code to another account, ccdeck now tells you once, with where it went and why, for example \"Switched to work · personal@example.com reached 90%\". You can turn it off in the sound settings, where two more wait, off until you turn them on: one when a quota window of the Claude or Codex account you are on reaches 90% and again at 100%, and one when a window that had reached 90% is available again. Each is said once per window and a restart does not repeat it. With either quota notification on, the deck reads quota about every two minutes even while no page is open."
204
+ "body": "When Auto-switch moves Claude Code to another account, ccdeck now tells you once, with where it went and why, for example \"Switched to work · personal@example.com reached 90%\". You can turn it off in Settings › Notifications, where two more wait, off until you turn them on: one when a quota window of the Claude or Codex account you are on reaches 90% and again at 100%, and one when a window that had reached 90% is available again. Each is said once per window and a restart does not repeat it. With either quota notification on, the deck reads quota about every two minutes even while no page is open."
194
205
  }
195
206
  ],
196
207
  "3.38.3": [
@@ -228,7 +239,7 @@
228
239
  },
229
240
  {
230
241
  "title": "💬 Send feedback from inside the deck",
231
- "body": "Appearance settings has a \"Send feedback…\" button: pick what kind it is, write a title and a few lines, and add a way to reach you if you want an answer. It goes to the people who make ccdeck, who may open a public issue from it, never with your contact in it. If it cannot be sent, the form stays filled in and says why."
242
+ "body": "Feedback, beside Settings at the right of the topbar (under More on a phone), opens a short form: pick what kind it is, write a few lines, and add a way to reach you under Add details if you want an answer. It goes to the people who make ccdeck, who may open a public issue from it, never with your contact in it. If it cannot be sent, the form stays filled in and says why."
232
243
  }
233
244
  ],
234
245
  "3.33.0": [
@@ -382,7 +393,7 @@
382
393
  },
383
394
  {
384
395
  "title": "📅 Projects opens on Today",
385
- "body": "The Projects report per account starts with today's work, and an empty window offers the next wider one. The topbar also shows this month's usage, and the desktop app can restart into an update from its own window."
396
+ "body": "The Projects report per account starts with today's work, and an empty window offers the next wider one. The desktop app can also restart into an update from its own window."
386
397
  }
387
398
  ],
388
399
  "3.27.0": [
@@ -440,7 +451,7 @@
440
451
  },
441
452
  {
442
453
  "title": "🎨 See each theme before you pick it",
443
- "body": "The Appearance menu shows a small preview of the deck in Light and in Dark. Claude FM also keeps its volume when a notification chime plays, instead of dipping under it."
454
+ "body": "Settings › General shows a small preview of the deck in each theme. Claude FM also keeps its volume when a notification chime plays, instead of dipping under it."
444
455
  }
445
456
  ],
446
457
  "3.24.1": [
@@ -598,11 +609,11 @@
598
609
  },
599
610
  {
600
611
  "title": "⎋ Esc leaves the machine panel where it is",
601
- "body": "The × on **This machine** read `Close (Esc)`, so Escape put the panel away. Escape belongs to the canvas — it deselects and hands the keyboard back — and pressing it while you are reading your instruments should not close them.\n\nThe panel now closes the two ways it opens: its ×, and the meter in the topbar. The × says `Close`, and names no key it does not answer."
612
+ "body": "The × on **This machine** read `Close (Esc)`, so Escape put the panel away. Escape belongs to the canvas — it deselects and hands the keyboard back — and pressing it while you are reading your instruments should not close them.\n\nThe panel now closes the ways it opens: its ×, its button on the window's right edge, and S. The × says `Close`, and names no key it does not answer."
602
613
  },
603
614
  {
604
615
  "title": "🖥 The machine moved out of the topbar and into a button",
605
- "body": "The small CPU sparkline and memory bar in the topbar are gone. A chip glyph in the icon row beside `$` and the accounts silhouette opens the same **This machine** panel — cores, memory, swap, load, temperature, busiest processes — and the bar stops moving in the corner of your eye.\n\nIt also stops asking. The deck read your machine every three seconds for the life of every tab to feed that meter; it now reads only while the panel is open, and the history behind the readings is kept by the deck itself, so a panel opened an hour from now still has the hour behind it."
616
+ "body": "The small CPU sparkline and memory bar in the topbar are gone. A chip glyph, on the window's right edge now, opens the same **This machine** panel — cores, memory, swap, load, temperature, busiest processes — and the bar stops moving in the corner of your eye.\n\nIt also stops asking. The deck read your machine every three seconds for the life of every tab to feed that meter; it now reads only while the panel is open, and the history behind the readings is kept by the deck itself, so a panel opened an hour from now still has the hour behind it."
606
617
  },
607
618
  {
608
619
  "title": "🔎 The panel's eight process rows became one way in",
@@ -660,7 +671,7 @@
660
671
  "3.9.0": [
661
672
  {
662
673
  "title": "🎛️ Five switches that were not where the thing they control is",
663
- "body": "**The session list has a button again.** It lost one a while back and only the `L` key was left — and `L` is written down in one place, a shortcuts sheet behind a small `?` in the corner of the canvas. If you use a mouse there was no way to open the list at all. The `☰` is back at the left of the top bar.\n\n**The Notifications switch now tells you whether the browser will actually show one.** It said `on` while this browser had never been asked for permission, which is not the same thing. It now says so on that row and offers the ask right there. Before, the only place to grant it was a button that appears solely while a session is stuck waiting — so on a machine where that rarely happens you could not switch notifications on at all.\n\n**Started with `--workspace` or `--scope` and seeing nothing?** The empty screen used to tell everyone to \"run `claude` or `codex` in any folder\", which is the one thing that cannot work for you. It now names the folder the deck is actually watching.\n\n**Browser Watch says when a setting did not save.** Flipping the switch and having it fail showed a red line reading \"Could not read\" — about reading history, not about your press — and ten seconds later the panel wiped it and looked perfectly healthy with the setting unchanged. A failed save now says so, in its own words, and stays until you dismiss it. The \"nobody browsing for\" menu also follows the stored value again: change it in a second tab and this one never caught up.\n\n**The deck no longer restarts itself when nothing on screen offers to stop it.** Thirty seconds after the last agent went quiet, with a new version on disk, the server would exit, come back and reload the page. The `auto when idle` switch that controls that lives inside the update banner — so if you had closed the banner with its `×`, it happened with no visible setting anywhere. Closing the banner now also stands down the restart; clicking the version chip brings back both together."
674
+ "body": "**The session list has a button again.** It lost one a while back and only the `L` key was left — and `L` is written down in one place, a shortcuts sheet behind a small `?` in the corner of the canvas. If you use a mouse there was no way to open the list at all. Its button is on the window's left edge now.\n\n**The Notifications switch now tells you whether the browser will actually show one.** It said `on` while this browser had never been asked for permission, which is not the same thing. It now says so on that row and offers the ask right there. Before, the only place to grant it was a button that appears solely while a session is stuck waiting — so on a machine where that rarely happens you could not switch notifications on at all.\n\n**Started with `--workspace` or `--scope` and seeing nothing?** The empty screen used to tell everyone to \"run `claude` or `codex` in any folder\", which is the one thing that cannot work for you. It now names the folder the deck is actually watching.\n\n**Browser Watch says when a setting did not save.** Flipping the switch and having it fail showed a red line reading \"Could not read\" — about reading history, not about your press — and ten seconds later the panel wiped it and looked perfectly healthy with the setting unchanged. A failed save now says so, in its own words, and stays until you dismiss it. The \"nobody browsing for\" menu also follows the stored value again: change it in a second tab and this one never caught up.\n\n**The deck no longer restarts itself when nothing on screen offers to stop it.** Thirty seconds after the last agent went quiet, with a new version on disk, the server would exit, come back and reload the page. The `auto when idle` switch that controls that lives inside the update banner — so if you had closed the banner with its `×`, it happened with no visible setting anywhere. Closing the banner now also stands down the restart; clicking the version chip brings back both together."
664
675
  }
665
676
  ],
666
677
  "3.8.9": [
@@ -714,25 +725,25 @@
714
725
  "3.8.1": [
715
726
  {
716
727
  "title": "🔒 Security: update if the deck runs on a Mac",
717
- "body": "A local process able to reach the deck's port — no token, no browser, no tab — could get a string of its choosing compiled as AppleScript when the deck raised a desktop notification with no page open. On macOS that is code running as you.\n\nThe strings were already passed as arguments rather than pasted into the script, which is normally the whole defence. It is not enough for `osascript`: its option parser reads any argument starting with a dash as an option, so a name beginning with `-e` was taken as more script instead of as text. Every place the deck hands `osascript` or `notify-send` something from outside now carries an explicit end-of-options marker, so nothing from outside can be read as an instruction again.\n\nNothing to change on your side beyond updating. If you would rather keep the deck off the desktop entirely, the Notifications switch in the sound menu does that, and `AGENTS_DECK_NO_NOTIFY=1` does it at launch — but the fix is the thing to have."
728
+ "body": "A local process able to reach the deck's port — no token, no browser, no tab — could get a string of its choosing compiled as AppleScript when the deck raised a desktop notification with no page open. On macOS that is code running as you.\n\nThe strings were already passed as arguments rather than pasted into the script, which is normally the whole defence. It is not enough for `osascript`: its option parser reads any argument starting with a dash as an option, so a name beginning with `-e` was taken as more script instead of as text. Every place the deck hands `osascript` or `notify-send` something from outside now carries an explicit end-of-options marker, so nothing from outside can be read as an instruction again.\n\nNothing to change on your side beyond updating. If you would rather keep the deck off the desktop entirely, the Notifications switch in Settings › Notifications does that, and `AGENTS_DECK_NO_NOTIFY=1` does it at launch — but the fix is the thing to have."
718
729
  }
719
730
  ],
720
731
  "3.8.0": [
721
732
  {
722
733
  "title": "🔕 Notifications now have an off switch",
723
- "body": "3.7.0 gave you a \"notify me\" button and no way back. Turning them off meant digging into your browser's site settings — which no page can do for you — and that still left the other notifier, the one the deck raises when no page is open at all. Silencing that one meant quitting the deck and starting it again with `AGENTS_DECK_NO_NOTIFY=1` in front. The person who wanted quiet was the one doing the work.\n\nThere is a **Notifications** switch in the sound menu now, beside the one for sound — that menu is already \"how loudly does this deck interrupt me\". One switch governs both notifiers, it takes effect immediately without a restart, and it is remembered across restarts.\n\nIt is kept by the deck rather than by your browser, because the second notifier runs when no page exists and a setting living in a page could never reach it. `AGENTS_DECK_NO_NOTIFY=1` still wins over the switch — a machine told at launch to stay off the desktop stays off it — and when that is the case the menu says so instead of showing an \"on\" that will not happen."
734
+ "body": "3.7.0 gave you a \"notify me\" button and no way back. Turning them off meant digging into your browser's site settings — which no page can do for you — and that still left the other notifier, the one the deck raises when no page is open at all. Silencing that one meant quitting the deck and starting it again with `AGENTS_DECK_NO_NOTIFY=1` in front. The person who wanted quiet was the one doing the work.\n\nThere is a **Notifications** switch in Settings › Notifications now. One switch governs both notifiers, it takes effect immediately without a restart, and it is remembered across restarts.\n\nIt is kept by the deck rather than by your browser, because the second notifier runs when no page exists and a setting living in a page could never reach it. `AGENTS_DECK_NO_NOTIFY=1` still wins over the switch — a machine told at launch to stay off the desktop stays off it — and when that is the case Settings says so instead of showing an \"on\" that will not happen."
724
735
  }
725
736
  ],
726
737
  "3.7.1": [
727
738
  {
728
739
  "title": "🔔 The notifications now fire on the thing that actually stops your agent",
729
- "body": "3.7.0 could tell you a session was waiting — but only when Claude Code asked permission to run a tool. If you work with permissions bypassed, that never happens, and the whole feature was silent for you. Measured on one real log: 1683 events, every one of them in bypassPermissions, and a single permission prompt in the entire history.\n\nWhat fires constantly on that setup is the other kind: the agent asking you a question and stopping for the answer. The deck was throwing those away — no amber chip, no row in the sidebar, no notification, and no \"notify me\" button, because the button only appears while something is actually blocked.\n\nNow they count. The card carries the question itself rather than a generic \"Your turn\" — \"paycore needs your input: merge both branches to main, or just one?\" — and so does the notification, on the desktop, whether the tab is behind something or not open at all.\n\nAn idle prompt still stays quiet: a turn that ended is not a session that is stuck, and a channel that cries wolf is one you switch off."
740
+ "body": "3.7.0 could tell you a session was waiting — but only when Claude Code asked permission to run a tool. If you work with permissions bypassed, that never happens, and the whole feature was silent for you. Measured on one real log: 1683 events, every one of them in bypassPermissions, and a single permission prompt in the entire history.\n\nWhat fires constantly on that setup is the other kind: the agent asking you a question and stopping for the answer. The deck was throwing those away — no amber chip, no row in the sidebar, no notification, and no \"notify me\" button, because the button only appeared while something was actually blocked.\n\nNow they count. The card carries the question itself rather than a generic \"Your turn\" — \"paycore needs your input: merge both branches to main, or just one?\" — and so does the notification, on the desktop, whether the tab is behind something or not open at all.\n\nAn idle prompt still stays quiet: a turn that ended is not a session that is stuck, and a channel that cries wolf is one you switch off."
730
741
  }
731
742
  ],
732
743
  "3.7.0": [
733
744
  {
734
745
  "title": "🔔 The deck can reach you when you are not looking at it",
735
- "body": "The deck's whole point is telling you which agent is waiting on you, and until now it could only say so on screen — the amber chip, the tab title, the favicon. Every one of those is drawn inside a page, so the one moment it matters, when you have walked away and four agents have gone quiet, the deck was silent.\n\nNow it reaches the desktop, two ways that never overlap. With the deck open behind something, the page raises the notification itself: a \"notify me\" button appears in the top bar the first time a session blocks, and the browser asks you — nothing is requested before there is something to notify you about. With no page open at all, the server raises it, because it can see that nobody is connected and therefore that no other surface is saying anything.\n\nOne notification per session, replaced rather than stacked if a second deck raises it too. Clicking it brings the tab forward. `AGENTS_DECK_NO_NOTIFY=1` turns the desktop side off entirely, and if you refuse the browser prompt the deck says so plainly instead of leaving you thinking you switched something on — after a refusal only your browser's site settings can undo it, which no page is allowed to touch."
746
+ "body": "The deck's whole point is telling you which agent is waiting on you, and until now it could only say so on screen — the amber chip, the tab title, the favicon. Every one of those is drawn inside a page, so the one moment it matters, when you have walked away and four agents have gone quiet, the deck was silent.\n\nNow it reaches the desktop, two ways that never overlap. With the deck open behind something, the page raises the notification itself: the browser asks for permission when you turn notifications on in Settings › Notifications — nothing is requested before you do. With no page open at all, the server raises it, because it can see that nobody is connected and therefore that no other surface is saying anything.\n\nOne notification per session, replaced rather than stacked if a second deck raises it too. Clicking it brings the tab forward. `AGENTS_DECK_NO_NOTIFY=1` turns the desktop side off entirely, and if you refuse the browser prompt the deck says so plainly instead of leaving you thinking you switched something on — after a refusal only your browser's site settings can undo it, which no page is allowed to touch."
736
747
  },
737
748
  {
738
749
  "title": "🧩 A blocked session now says what it is asking for",
@@ -762,7 +773,7 @@
762
773
  },
763
774
  {
764
775
  "title": "🧹 The board's own figure is out of the Usage panel",
765
- "body": "3.3.0 put the canvas's live spend on its own labelled line, under whichever period you had chosen. In front of a panel that answers today, this month and all time from the logs on disk, that second money figure answers a question nobody asked at that moment — and it invited a comparison it could never win, $7,385 on the board beneath $170 for today.\n\nIt is gone. The board's tokens and cost are still on the topbar, with the sentence that explains why they fall on their own, and the panel still shows them when ccusage cannot answer at all."
776
+ "body": "3.3.0 put the canvas's live spend on its own labelled line, under whichever period you had chosen. In front of a panel that answers today, this month and all time from the logs on disk, that second money figure answers a question nobody asked at that moment — and it invited a comparison it could never win, $7,385 on the board beneath $170 for today.\n\nIt is gone. The panel still shows the board's tokens and cost when ccusage cannot answer at all."
766
777
  }
767
778
  ],
768
779
  "3.3.0": [
@@ -776,7 +787,7 @@
776
787
  },
777
788
  {
778
789
  "title": "👁 Browser Watch reads nothing while it is switched off",
779
- "body": "The switch used to control only what was KEPT. The read happened anyway — every five minutes, on every deck, the panel's badge poll discovered each Chromium profile and copied its whole History database to a temp file to query it. A switch that says off while that continues is not a switch.\n\nWith the watch off, nothing touches a browser now. Opening the panel still reads live, because that is you looking, and a watch that is ON still records in the background — that is the feature. The trade is that the topbar badge no longer counts findings while the watch is off; open the panel to see them.\n\nThe README has a Browser Watch section now: what is read, when, where the copy goes, and that only visits after the deck started are ever considered."
790
+ "body": "The switch used to control only what was KEPT. The read happened anyway — every five minutes, on every deck, the panel's badge poll discovered each Chromium profile and copied its whole History database to a temp file to query it. A switch that says off while that continues is not a switch.\n\nWith the watch off, nothing touches a browser now. Opening the panel still reads live, because that is you looking, and a watch that is ON still records in the background — that is the feature. The trade is that the Browser Watch badge no longer counts findings while the watch is off; open the panel to see them.\n\nThe README has a Browser Watch section now: what is read, when, where the copy goes, and that only visits after the deck started are ever considered."
780
791
  },
781
792
  {
782
793
  "title": "🔒 The deck's own data is no longer readable by anything on the machine",
@@ -846,7 +857,7 @@
846
857
  "1.48.0": [
847
858
  {
848
859
  "title": "👁 Browser Watch — see what a program opened while you were away",
849
- "body": "There is a new eye in the top bar. It watches the browsers on this machine and tells you when a program — an extension, an agent, a command — opened pages while nobody was touching the browser.\n\nUsually that is your own agent doing what you asked, and the panel says so rather than crying intrusion. What it gives you is the evidence: which pages, in which browser, at what time, with every address written out in full.\n\nIt is off until you switch it on, and nothing leaves this machine. It reads a copy of the browser's own history and only pages newer than the moment the deck started — never anything from before, never cookies, never saved passwords. Open ⚙ inside the panel and \"What Browser Watch can access\" says exactly what it reads, keeps and sends.\n\nWhen it finds something the eye turns amber with a count. You can dismiss a finding once you have looked at it and it stays gone, while the log file on disk keeps the record. On a Mac it can also close the tab it found, or quit the browser."
860
+ "body": "Browser Watch is new, on the window's right edge (or press B). It watches the browsers on this machine and tells you when a program — an extension, an agent, a command — opened pages while nobody was touching the browser.\n\nUsually that is your own agent doing what you asked, and the panel says so rather than crying intrusion. What it gives you is the evidence: which pages, in which browser, at what time, with every address written out in full.\n\nIt is off until you switch it on, and nothing leaves this machine. It reads a copy of the browser's own history and only pages newer than the moment the deck started — never anything from before, never cookies, never saved passwords. Open ⚙ inside the panel and \"What Browser Watch can access\" says exactly what it reads, keeps and sends.\n\nWhen it finds something, its button shows a count. You can dismiss a finding once you have looked at it and it stays gone, while the log file on disk keeps the record. On a Mac it can also close the tab it found, or quit the browser."
850
861
  },
851
862
  {
852
863
  "title": "🔑 Move all your accounts to another machine in one paste",
@@ -866,7 +877,7 @@
866
877
  "1.46.0": [
867
878
  {
868
879
  "title": "🔊 Pick a volume and a sound for each of the two tones",
869
- "body": "Click the speaker in the top bar and a small menu opens. There are two tones — one when a turn finishes, one when Claude is waiting for you — and each has its own volume and its own sound, six to choose from. Press \"Hear it\" to try one straight away, without waiting for a real turn.\n\nThe two lists are separate on purpose, so you can never end up with both tones sounding the same. A finish tone always falls; an asking tone always rises. That is how you tell them apart without looking at the screen.\n\nM still turns the sound on and off from anywhere."
880
+ "body": "Open Settings › Sounds, or press V. There are two tones — one when a turn finishes, one when Claude is waiting for you — and each has its own volume and its own sound, six to choose from. Press \"Hear it\" to try one straight away, without waiting for a real turn.\n\nThe two lists are separate on purpose, so you can never end up with both tones sounding the same. A finish tone always falls; an asking tone always rises. That is how you tell them apart without looking at the screen.\n\nM still turns the sound on and off from anywhere."
870
881
  },
871
882
  {
872
883
  "title": "✨ This box is new",
@@ -1 +0,0 @@
1
- import{j as e,r as a,z as Ue,A as rs,B as We,u as os,d as as,C as ls}from"./index-2Z0Tr6cq.js";const cs={radar:e.jsxs(e.Fragment,{children:[e.jsx("path",{d:"M20 12a8 8 0 1 1-8-8v8l5-5"}),e.jsx("path",{d:"M12 8a4 4 0 1 0 4 4"}),e.jsx("circle",{cx:"12",cy:"12",r:"1"})]}),connection:e.jsxs(e.Fragment,{children:[e.jsx("path",{d:"M8 8h8v8H8zM12 3v5M12 16v5M3 12h5M16 12h5"}),e.jsx("circle",{cx:"12",cy:"3",r:"1"}),e.jsx("circle",{cx:"21",cy:"12",r:"1"})]}),logs:e.jsxs(e.Fragment,{children:[e.jsx("rect",{x:"5",y:"3",width:"14",height:"18",rx:"2"}),e.jsx("path",{d:"M9 8h6M9 12h6M9 16h3"})]}),metrics:e.jsx(e.Fragment,{children:e.jsx("path",{d:"M4 20h16M7 16v-5M12 16V4M17 16V8"})}),traces:e.jsxs(e.Fragment,{children:[e.jsx("path",{d:"M7 5h10M7 12h10M7 19h10M12 5v14"}),e.jsx("circle",{cx:"7",cy:"5",r:"2"}),e.jsx("circle",{cx:"17",cy:"12",r:"2"}),e.jsx("circle",{cx:"7",cy:"19",r:"2"})]}),info:e.jsxs(e.Fragment,{children:[e.jsx("circle",{cx:"12",cy:"12",r:"9"}),e.jsx("path",{d:"M12 11v5M12 7h.01"})]})};function W({kind:s,...t}){return e.jsx("svg",{width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",...t,children:cs[s]})}function he({className:s="",children:t,...r}){return e.jsxs("span",{className:"tr-select-wrap",children:[e.jsx("select",{...r,className:`tr-select${s?` ${s}`:""}`,children:t}),e.jsx("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:e.jsx("path",{d:"m6 9 6 6 6-6"})})]})}function Ge(s){return[...new Set(s.filter(t=>t.key.endsWith("ENDPOINT")).map(t=>t.value).filter(t=>/^https?:\/\//.test(t)))]}function qe(s){return(s==null?void 0:s.status)!=="observing"?[]:[...new Set(s.connections.filter(t=>t.active===!0).map(t=>t.destination))]}function He(s){try{const t=new URL(s);return/^\d+\.\d+\.\d+\.\d+$/.test(t.hostname)?`${t.hostname}:${t.port||(t.protocol==="https:"?"443":"80")}`:null}catch{return null}}function ds(s){const t=s.filter(r=>r.key==="CLAUDE_CODE_ENABLE_TELEMETRY").map(r=>r.value);return t.length?new Set(t).size>1?"Conflicting settings":t[0]==="Enabled"?"Enabled in settings":t[0]==="Disabled"?"Disabled in settings":"Unknown":"Unknown"}function hs(s){const t=s.replace(/^\uFEFF/,"").trim();if(!t)return[];let r;try{JSON.parse(t),r=[t]}catch{r=s.replace(/^\uFEFF/,"").split(/\r?\n/)}if(r.length>1e4)throw new Error("This file has more than 10,000 lines. Choose a smaller export.");return r.flatMap((c,x)=>{var p;if(!c.trim())return[];try{const j=JSON.parse(c),d=j&&typeof j=="object"?j:{},h=new Set,o=[j];for(;o.length;){const b=o.pop();if(!b||typeof b!="object")continue;if(Array.isArray(b)){for(const l of b)o.push(l);continue}const f=b;for(const l of["session.id","session_id","sessionId"])typeof f[l]=="string"&&h.add(f[l]);if(["session.id","session_id"].includes(String(f.key))){const l=f.value;typeof(l==null?void 0:l.stringValue)=="string"&&h.add(l.stringValue)}for(const l of Object.values(f))o.push(l)}return[{line:x+1,raw:c,sessionIds:[...h],label:String(d.eventName??((p=d.event)==null?void 0:p.name)??d.type??d.model??"JSON record").slice(0,160),sessionId:typeof(d.session_id??d.sessionId??d["session.id"])=="string"?d.session_id??d.sessionId??d["session.id"]:null,timestamp:typeof d.timestamp=="string"?d.timestamp:null,valid:!0}]}catch{return[{line:x+1,raw:c,label:"Invalid JSON",sessionId:null,timestamp:null,sessionIds:[],valid:!1}]}})}function us(){const[s,t]=a.useState(null),[r,c]=a.useState(!1),x=a.useRef(0);return a.useEffect(()=>{let j=!0,d,h=null;const o=()=>document.visibilityState!=="hidden",b=()=>{clearTimeout(d),h==null||h.abort(),h=null},f=async()=>{if(!j||!o()||h)return;const w=new AbortController,y=x.current;h=w;try{const L=await fetch("/api/system/traffic-radar",{signal:w.signal});if(!L.ok)throw new Error("radar_unavailable");const T=await L.json();if(!T.ok)throw new Error("radar_unavailable");j&&!w.signal.aborted&&y===x.current&&(t(T),c(!1))}catch{j&&!w.signal.aborted&&y===x.current&&c(!0)}finally{h===w&&(h=null),j&&!w.signal.aborted&&o()&&(d=setTimeout(f,5e3))}},l=()=>{b(),document.visibilityState!=="hidden"&&f()};return f(),document.addEventListener("visibilitychange",l),()=>{j=!1,b(),document.removeEventListener("visibilitychange",l)}},[]),{snapshot:s,failed:r,updateConfig:j=>{x.current++,t(d=>d&&{...d,config:j})}}}function ms(){const[s,t]=a.useState(null),[r,c]=a.useState(!1),[x,p]=a.useState(!1),[j,d]=a.useState(""),[h,o]=a.useState(""),[b,f]=a.useState([]),[l,w]=a.useState(null),[y,L]=a.useState(null),T=a.useRef(null),$=a.useRef(null),S=a.useRef(0);a.useEffect(()=>{let N=!0,m,i=null;const u=()=>document.visibilityState!=="hidden",g=()=>{clearTimeout(m),i==null||i.abort(),i=null},k=async()=>{if(!N||!u()||i)return;if(T.current){m=setTimeout(k,1e3);return}const M=S.current,_=new AbortController;i=_;try{const Y=await fetch("/api/system/traffic-radar/capture",{signal:_.signal});if(!Y.ok)throw new Error;const R=await Y.json();N&&!_.signal.aborted&&M===S.current&&(t(R),c(!1))}catch{N&&!_.signal.aborted&&M===S.current&&c(!0)}finally{i===_&&(i=null),N&&!_.signal.aborted&&u()&&(m=setTimeout(k,1e3))}},A=()=>{g(),document.visibilityState!=="hidden"&&k()};return k(),document.addEventListener("visibilitychange",A),()=>{var M;N=!1,g(),(M=$.current)==null||M.abort(),document.removeEventListener("visibilitychange",A)}},[]);const E=async(N,m)=>{if(!Ue(T.current))return;S.current++,T.current=N,L(N),p(!0),d("");const i=new AbortController;$.current=i;const u=setTimeout(()=>i.abort(),2e4);try{const g=await fetch(`/api/system/traffic-radar/capture/${N}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({destination:m}),signal:i.signal}),k=await g.json();if(!g.ok)throw new Error(k.error||"Capture action failed. Please retry.");N==="prepare"&&(o(k.command),f(k.commands??[{destination:String(m),command:k.command}]),w(k.sessionId)),N==="stop"&&(o(""),f([]));const A=await fetch("/api/system/traffic-radar/capture",{signal:i.signal});A.ok&&(t(await A.json()),c(!1))}catch(g){d(g instanceof Error?g.message:"Capture action failed. Please retry.")}finally{clearTimeout(u),$.current=null,T.current=null,L(null),p(!1)}};return{capture:s,failed:r,busy:x,pendingAction:y,error:j,commands:(s==null?void 0:s.sessionId)===l?b:[],command:(s==null?void 0:s.sessionId)===l?h:"",action:E}}const ye=50,xs=2e5;function ze({name:s}){return e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"tr-json-key",children:typeof s=="string"?JSON.stringify(s):s}),": "]})}function Ye({value:s,name:t,root:r=!1}){if(s!==null&&typeof s=="object")return e.jsx(js,{value:s,name:t,root:r});const c=s===null?"null":typeof s;return e.jsxs("div",{children:[t!==void 0&&e.jsx(ze,{name:t}),e.jsx("span",{className:`tr-json-${c}`,children:JSON.stringify(s)})]})}function js({value:s,name:t,root:r}){const[c,x]=a.useState(r),[p,j]=a.useState(ye),d=Array.isArray(s),h=a.useMemo(()=>Array.isArray(s)?null:Object.keys(s),[s]),o=d?s.length:h.length,b=Math.min(p,o);return e.jsxs("details",{open:c,onToggle:f=>{f.target===f.currentTarget&&x(f.currentTarget.open)},children:[e.jsxs("summary",{children:[t!==void 0&&e.jsx(ze,{name:t}),e.jsxs("span",{children:[d?"[":"{",o===0?d?"]":"}":"…"]}),o>0&&e.jsxs("span",{className:"tr-count",children:[o," ",d?o===1?"item":"items":o===1?"key":"keys"]})]}),c&&o>0&&e.jsxs("div",{children:[Array.from({length:b},(f,l)=>{const w=h?h[l]:l,y=Array.isArray(s)?s[l]:s[w];return e.jsx(Ye,{name:w,value:y},w)}),b<o&&e.jsxs("button",{type:"button",className:"btn",onClick:()=>j(f=>f+ye),children:["Show ",Math.min(ye,o-b)," more (",o-b," remaining)"]}),e.jsx("div",{children:d?"]":"}"})]})]})}function fs({value:s}){const t=a.useMemo(()=>{const r=JSON.stringify(s,null,2);if(r.length>xs)return r;const c=[],x=/"(?:\\.|[^"\\])*"(\s*:)?|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|\b(?:true|false|null)\b/g;let p=0;for(const j of r.matchAll(x)){const d=j.index;d>p&&c.push(r.slice(p,d));const h=j[0],o=j[1]||"",b=o?"key":h[0]==='"'?"string":h==="null"?"null":h==="true"||h==="false"?"boolean":"number";c.push(e.jsx("span",{className:`tr-json-${b}`,children:o?h.slice(0,-o.length):h},d)),o&&c.push(o),p=d+h.length}return p<r.length&&c.push(r.slice(p)),c},[s]);return e.jsx("pre",{className:"tr-value",children:t})}function ps({value:s,initialMode:t="tree"}){const[r,c]=a.useState(t);return e.jsxs("div",{children:[e.jsxs("div",{className:"tr-detail-tabs",role:"group","aria-label":"JSON view",children:[e.jsx("button",{type:"button",className:"btn","aria-pressed":r==="tree",onClick:()=>c("tree"),children:"Tree"}),e.jsx("button",{type:"button",className:"btn","aria-pressed":r==="raw",onClick:()=>c("raw"),children:"Raw JSON"})]}),r==="raw"?e.jsx(fs,{value:s}):e.jsx("div",{className:"tr-value tr-json-tree",children:e.jsx(Ye,{value:s,root:!0})})]})}function gs(s,t=!1){var b,f,l,w;const r=!!s&&(s.enabled??["awaiting","receiving","capturing"].includes(s.state)),c=((b=s==null?void 0:s.sources)==null?void 0:b.filter(y=>y.active).length)??0,x=!!((f=s==null?void 0:s.sources)!=null&&f.some(y=>/permission/i.test(y.error??""))),p=(s==null?void 0:s.events.length)??0,j=((l=s==null?void 0:s.observations)==null?void 0:l.length)??0,d=!!(s!=null&&s.bytes)||j>0||p>0;let h="Monitoring stopped",o="neutral";return t?(h="Monitor connection lost",o="error"):s?s.state==="error"?(h="Capture error",o="error"):r&&c>0&&c<(((w=s.sources)==null?void 0:w.length)??0)?(h="Listening to some destinations",o="attention"):r&&x?(h="Capture permission needed",o="attention"):r&&s.state==="interrupted"?(h="Reconnecting capture…",o="attention"):r&&(c||!s.managed&&["receiving","capturing"].includes(s.state))?(h="Listening for telemetry",o="ok"):r&&(h=s.managed?"Starting monitoring…":"Activation needed"):h="Reading monitor…",{enabled:r,active:c,permission:x,decoded:p,observed:j,traffic:d,title:h,tone:o}}const F=s=>s&&typeof s=="object"&&!Array.isArray(s)?s:{},z=s=>Array.isArray(s)?s:[],de=s=>new Date(s).toLocaleTimeString(),vs=s=>({unconfirmed:"Transfer observed · receipt unconfirmed",accepted:"Collector accepted",partial:"Collector reported partial success",rejected:"Collector rejected",unknown:"Response incomplete",reset:"Stream reset"})[s.outcome],Ne={encrypted:"Encrypted traffic observed; contents cannot be read.",joined_midstream:"Capture joined an existing connection. Waiting for a new HTTP/2 connection to decode it reliably.",capture_gap:"Incomplete packets or a gap were observed. Affected contents are not shown as decoded.",decode_failed:"Some payloads could not be decoded. They are not shown as verified events.",payload_limit:"A payload exceeded the inspection limit and was omitted.",stream_limit:"A stream exceeded the inspection limit.",connection_limit:"The connection inspection limit was reached.",unsupported_compression:"A compression format other than gzip was observed.",unsupported_link:"This network capture format is not supported.",unsupported_network:"Some packets used a network protocol this decoder does not support.",fragmented:"Fragmented IP packets cannot be decoded in this version.",unknown_export:"A request could not be identified as an OTLP export.",connection_closed:"An HTTP/2 connection closed.",idle_connection:"An idle connection was removed from the decoder."};function ue(s){const t=F(s);for(const r of["stringValue","intValue","doubleValue","boolValue","bytesValue"])if(t[r]!==void 0)return t[r];return t.arrayValue?z(F(t.arrayValue).values).map(ue):t.kvlistValue?Object.fromEntries(z(F(t.kvlistValue).values).map(r=>{const c=F(r);return[String(c.key),ue(c.value)]})):null}function bs(s){return/prompt/i.test(s)?"Prompt-related field included in this export.":/response|completion/i.test(s)?"Response-related field included in this export.":/tool/i.test(s)?"Tool-related field included in this export.":/session/i.test(s)?"Session identifier or session metadata.":/user|email|account/i.test(s)?"User or account metadata.":/token|cost|duration|latency/i.test(s)?"Usage or timing measurement.":"Attribute included in this export."}function se({value:s}){return e.jsx("pre",{className:"tr-value",children:typeof s=="string"?s:JSON.stringify(s,null,2)})}function we({value:s}){return e.jsx("dl",{className:"tr-attributes",children:z(s).map((t,r)=>{const c=F(t);return e.jsxs("div",{children:[e.jsxs("dt",{children:[e.jsx("code",{children:String(c.key??"Unnamed attribute")}),e.jsx("span",{children:bs(String(c.key))})]}),e.jsx("dd",{children:e.jsx(se,{value:ue(c.value)})})]},r)})})}function ys({record:s}){const t=["gauge","sum","histogram","exponentialHistogram","summary"].find(r=>s[r]);return t?e.jsxs("div",{children:[s.description&&e.jsx("p",{className:"tr-note",children:String(s.description)}),z(F(s[t]).dataPoints).map((r,c)=>{const x=F(r);return e.jsxs("section",{children:[e.jsxs("dl",{className:"tr-facts",children:[e.jsxs("div",{children:[e.jsx("dt",{children:"Measurement"}),e.jsx("dd",{children:t})]}),x.asInt!==void 0||x.asDouble!==void 0?e.jsxs("div",{children:[e.jsx("dt",{children:"Value"}),e.jsx("dd",{children:e.jsxs("code",{children:[String(x.asInt??x.asDouble)," ",String(s.unit??"")]})})]}):null,x.count!==void 0&&e.jsxs("div",{children:[e.jsx("dt",{children:"Count"}),e.jsx("dd",{children:e.jsx("code",{children:String(x.count)})})]}),x.sum!==void 0&&e.jsxs("div",{children:[e.jsx("dt",{children:"Sum"}),e.jsx("dd",{children:e.jsxs("code",{children:[String(x.sum)," ",String(s.unit??"")]})})]})]}),e.jsx(we,{value:x.attributes}),e.jsxs("details",{className:"tr-details",children:[e.jsx("summary",{children:"All measurement fields"}),e.jsx(se,{value:x})]})]},c)})]}):null}function Ns({record:s}){let t=null;try{t=`${Number(BigInt(String(s.endTimeUnixNano))-BigInt(String(s.startTimeUnixNano)))/1e6} ms`}catch{t=null}return e.jsxs("dl",{className:"tr-facts",children:[e.jsxs("div",{children:[e.jsx("dt",{children:"Duration"}),e.jsx("dd",{children:t??"Not included"})]}),s.kind&&e.jsxs("div",{children:[e.jsx("dt",{children:"Span kind"}),e.jsx("dd",{children:String(s.kind)})]}),s.status&&e.jsxs("div",{children:[e.jsx("dt",{children:"Span status"}),e.jsx("dd",{children:e.jsx(se,{value:s.status})})]})]})}function ws({payload:s,signal:t}){const r=t==="traces"?"Spans":t==="logs"?"Logs":"Metrics",c=t==="traces"?"spans":t==="logs"?"logRecords":"metrics";return e.jsx("div",{className:"tr-decoded",children:z(s["resource"+r]).map((x,p)=>{const j=F(x);return e.jsxs("section",{children:[e.jsxs("details",{className:"tr-details",children:[e.jsx("summary",{children:"Resource metadata"}),e.jsx(we,{value:F(j.resource).attributes})]}),z(j["scope"+r]).map((d,h)=>{const o=F(d);return e.jsxs("div",{children:[o.scope&&e.jsxs("p",{className:"tr-note",children:["Instrumentation: ",String(F(o.scope).name??"Unnamed")," ",String(F(o.scope).version??"")]}),z(o[c]).map((b,f)=>{const l=F(b);return e.jsxs("article",{className:"tr-record",children:[e.jsx("h4",{children:String(l.eventName??l.name??`${t==="logs"?"Log record":"Record"} ${f+1}`)}),l.body&&e.jsxs(e.Fragment,{children:[e.jsx("p",{className:"tr-note",children:"Event body"}),e.jsx(se,{value:ue(l.body)})]}),t==="metrics"&&e.jsx(ys,{record:l}),t==="traces"&&e.jsx(Ns,{record:l}),l.attributes&&e.jsx(we,{value:l.attributes}),e.jsxs("details",{className:"tr-details",children:[e.jsx("summary",{children:"All record fields"}),e.jsx(se,{value:l})]})]},f)})]},h)})]},p)})})}function Ss({capture:s,radar:t,failed:r,busy:c,pendingAction:x=null,error:p,command:j,commands:d=[],action:h,sessionFilter:o="all",sessions:b=[]}){var Ae,Oe,Le,Me,Re,Ie,_e,De,$e,Pe,Fe,Je,Be;const f=Ge(((Ae=t==null?void 0:t.config)==null?void 0:Ae.variables)??[]),l=[...new Set(f.map(He).filter(n=>!!n))],w=qe(t).filter(n=>!l.includes(n)&&He(`http://${n}`)),[y,L]=a.useState("all"),[T,$]=a.useState(""),[S,E]=a.useState(null),[N,m]=a.useState(null),[i,u]=a.useState("all"),[g,k]=a.useState("all"),[A,M]=a.useState(100),[_,Y]=a.useState(null),[R,me]=a.useState(null),[J,ne]=a.useState(null),[K,oe]=a.useState(!1),Se=a.useRef(0),[xe,Ce]=a.useState("summary"),[D,je]=a.useState(null),[ke,Ke]=a.useState(0),te=a.useRef(null),G=(s==null?void 0:s.events)??[],Z=!!(G.length||(Oe=s==null?void 0:s.observations)!=null&&Oe.length),Ze=new Map(G.map(n=>[n.id,n])),fe=R?R.map(n=>Ze.get(n.id)).filter(n=>!!n):G,V=fe.filter(n=>{var v,O;return(g==="all"||n.signal===g)&&(i==="all"||i===n.destination)&&(o==="all"||(o==="unidentified"?!((v=n.sessionIds)!=null&&v.length):(O=n.sessionIds)==null?void 0:O.includes(o)))}),H=((s==null?void 0:s.observations)??[]).filter(n=>(_===null||_.includes(n.id))&&g==="all"&&(o==="all"||o==="unidentified")&&(i==="all"||i===n.destination)),P=H.find(n=>n.id===S),C=S!==null?void 0:N===null?V[0]:V.find(n=>n.id===N),ie=[...V.map(n=>({kind:"export",event:n})),...H.map(n=>({kind:"observation",event:n}))].sort((n,v)=>v.event.at-n.event.at||v.event.id-n.event.id),Qe=new Set(R==null?void 0:R.map(n=>n.id)),Xe=new Set(_),pe=R===null?0:G.filter(n=>!Qe.has(n.id)).length+((s==null?void 0:s.observations)??[]).filter(n=>!Xe.has(n.id)).length,re=C==null?void 0:C.id;a.useEffect(()=>{m(null),E(null),M(100)},[o,g,i]),a.useEffect(()=>{N!==null||S!==null||(V.length?m(V[0].id):H.length&&E(H[0].id))},[(Le=V[0])==null?void 0:Le.id,(Me=H[0])==null?void 0:Me.id,N,S]),a.useEffect(()=>{if(je(null),ne(null),re===void 0)return;const n=new AbortController;let v=!0;const O=setTimeout(()=>n.abort(),5e3);return fetch(`/api/system/traffic-radar/capture/event?id=${re}`,{signal:n.signal}).then(async I=>{if(!I.ok)throw new Error("This message has expired. Select a newer message.");const q=await I.json();v&&!n.signal.aborted&&je({id:re,payload:q.payload})}).catch(I=>{v&&je({id:re,error:n.signal.aborted?"Loading timed out. Try again.":I instanceof Error?I.message:"Could not read this message."})}).finally(()=>clearTimeout(O)),()=>{v=!1,clearTimeout(O),n.abort()}},[re,S,ke]),a.useEffect(()=>{if(!J||J==="failed")return;const n=setTimeout(()=>ne(null),2e3);return()=>clearTimeout(n)},[J]),a.useEffect(()=>{if(!K)return;const n=setTimeout(()=>oe(!1),5e3);return()=>clearTimeout(n)},[K]);const ae=(s==null?void 0:s.shell)??((t==null?void 0:t.platform)==="win32"?"PowerShell":"Terminal"),ge=ae==="PowerShell",U=gs(s,r),B=U.enabled,Q=!!(s!=null&&s.managed),es=Q&&U.permission,le=U.active,X=((Re=s==null?void 0:s.sources)==null?void 0:Re.length)??1,ss=U.title,ee=n=>{const v=We(x??(c?"capture":null),n);return{disabled:v.disabled,"aria-busy":v.busy}},ns=n=>{var v,O,I;E(null),m(n.id),typeof window<"u"&&((v=window.matchMedia)!=null&&v.call(window,"(max-width: 640px)").matches)&&((O=te.current)==null||O.focus({preventScroll:!0}),(I=te.current)==null||I.scrollIntoView({block:"start",behavior:"auto"}))},ts=()=>{const n=Date.now(),v=rs({armedFor:K?"history":null,target:"history",armedAt:Se.current,now:n,gapMs:350});if(v==="arm"){Se.current=n,oe(!0);return}v!=="ignore"&&(oe(!1),m(null),E(null),me(null),Y(null),h("clear"))},Te=()=>{var v;ne(null),me(null),Y(null),m(null),E(null);const n=y==="custom"?[T.trim()]:y==="all"?l:[y];h("prepare",n.length?n:((v=s==null?void 0:s.sources)==null?void 0:v.map(O=>O.destination))??(s!=null&&s.destination?[s.destination]:[]))},ve=d.length?d:j?[{destination:(s==null?void 0:s.destination)??"Collector",command:j}]:[],ce=async(n,v)=>{try{await navigator.clipboard.writeText(n),ne(v)}catch{ne("failed")}},Ee=(n=[])=>n.length?n.map(v=>{var O;return`${((O=b.find(I=>I.id===v))==null?void 0:O.label)??"Session"} · ${v.slice(0,8)}`}).join(", "):"Unidentified session",is=new Set([...((Ie=s==null?void 0:s.sources)==null?void 0:Ie.map(n=>n.destination))??[],...l]),be=(t==null?void 0:t.status)==="observing"?t.connections.filter(n=>n.active&&is.has(n.destination)):[];return e.jsxs("div",{className:"tr-telemetry",children:[e.jsxs("div",{className:"tr-capture-bar",children:[e.jsxs("div",{children:[e.jsxs("h3",{role:"status","aria-live":"polite",children:[e.jsx("span",{className:`tr-live-dot tr-dot-${U.tone}`}),ss]}),e.jsx("p",{className:"tr-note",children:r?"Showing the last successful read. Reconnecting automatically.":B?le?`${le} of ${X} destination${X===1?"":"s"} listening · ${U.traffic?"Traffic observed":"No traffic observed yet"}`:"Monitoring is enabled; the listener is not ready yet.":"Claude may still be sending telemetry."}),B&&e.jsxs("div",{className:"tr-active-destinations",role:"group","aria-label":"Monitored destinations",children:[e.jsxs("span",{children:["Destination",X>1?"s":""]}),(_e=s==null?void 0:s.sources)!=null&&_e.length?s.sources.map(n=>e.jsxs("span",{className:"tr-destination-entry",children:[e.jsx("code",{children:n.destination}),(X>1||!n.active)&&e.jsx("span",{className:n.active?"tr-source-ready":"tr-source-pending",children:n.active?"Listening":"Not listening"}),!n.active&&n.error&&e.jsx("span",{className:"tr-source-error",children:n.error})]},n.destination)):(s==null?void 0:s.destination)&&e.jsx("code",{children:s.destination})]})]}),e.jsx("div",{className:"tr-capture-actions",children:B?e.jsx("button",{className:"btn",...ee("stop"),onClick:()=>void h("stop"),children:"Stop monitoring"}):e.jsx("button",{className:"btn primary",...ee("prepare"),disabled:ee("prepare").disabled||!s||r||(t==null?void 0:t.status)==="unsupported"||y==="all"&&!l.length||y==="custom"&&!T.trim(),onClick:Te,children:x==="prepare"?"Starting…":"Start monitoring"})})]}),!B&&e.jsxs("div",{className:"tr-destination",children:[e.jsxs("label",{children:["Destination",e.jsxs(he,{"aria-label":"Capture destination",value:y,onChange:n=>L(n.target.value),children:[e.jsx("option",{value:"all",children:l.length===1?l[0]:l.length?`All configured destinations (${l.length})`:"Choose a collector…"}),l.map(n=>e.jsx("option",{value:n,children:n},n)),w.length>0&&e.jsx("optgroup",{label:"Observed Claude connections · telemetry unverified",children:w.map(n=>e.jsxs("option",{value:n,children:[n," · observed"]},n))}),e.jsx("option",{value:"custom",children:"Another IPv4 address…"})]})]}),y==="custom"&&e.jsx("input",{className:"tr-input","aria-label":"Collector IPv4 and port",value:T,onChange:n=>$(n.target.value),placeholder:"192.168.1.10:4317"}),!l.length&&y==="all"&&e.jsx("span",{className:"tr-note",children:w.length?"Choose an observed connection or enter your collector address.":"Enter your collector address to monitor traffic. Missing settings do not mean telemetry is off."})]}),(t==null?void 0:t.status)==="unsupported"&&e.jsx("p",{className:"tr-capture-error",role:"status",children:"Live capture is available on macOS, Linux and Windows. You can still inspect a local file."}),p&&e.jsxs("p",{className:"tr-capture-error",role:"alert",children:[p,ge&&p.includes("Npcap")&&e.jsxs(e.Fragment,{children:[" ",e.jsx("a",{href:"https://www.wireshark.org/download.html",target:"_blank",rel:"noreferrer",children:"Get Wireshark"})]})]}),!Q&&B&&!ve.length&&(s==null?void 0:s.state)==="awaiting"&&e.jsxs("div",{className:"tr-activation",children:[e.jsx("p",{children:"Activation is pending. Generate the Terminal instructions again to continue."}),e.jsx("button",{className:"btn",...ee("prepare"),onClick:Te,children:"Show activation instructions"}),e.jsx("p",{className:"tr-note",children:"The previous token will be invalidated."})]}),!Q&&ve.length>0&&B&&e.jsxs("details",{className:"tr-activation",open:!le,children:[e.jsxs("summary",{children:["Activate in ",ae,X>1?` · ${le}/${X} listening`:""]}),e.jsx("p",{children:ge?"Run each command in a separate PowerShell tab. Wireshark with Npcap is required; use an administrator tab if Npcap restricts capture.":"Run each command in a separate Terminal tab. Only tcpdump asks for administrator permission."}),ve.map(n=>e.jsxs("div",{className:"tr-command-entry",children:[e.jsxs("div",{children:[e.jsx("code",{children:n.destination}),e.jsx("button",{className:"btn",onClick:()=>void ce(n.command,n.destination),children:J===n.destination?"Copied":"Copy command"})]}),e.jsx("pre",{className:"tr-command",children:n.command})]},n.destination)),e.jsxs("p",{className:"tr-note",children:["Press Ctrl+C in each ",ae," tab to stop capture. Monitoring continues until you stop it. Commands contain a local token; do not share them."]})]}),es&&e.jsxs("div",{className:"tr-activation",role:"status",children:[e.jsx("strong",{children:"Allow packet capture once"}),e.jsxs("p",{className:"tr-note",children:[ge?"Npcap is restricting access. Allow this user to capture traffic in Npcap setup.":(s==null?void 0:s.platform)==="darwin"?"Install Wireshark’s ChmodBPF permission helper to allow capture without a Terminal command.":"Grant tcpdump packet capture permissions for this user."," ccdeck retries automatically when access is available."]}),e.jsx("a",{href:"https://wiki.wireshark.org/CaptureSetup/CapturePrivileges",target:"_blank",rel:"noreferrer",children:"Capture permission setup"})]}),J==="failed"&&e.jsx("p",{className:"tr-capture-error",role:"alert",children:"Clipboard unavailable. Select the text and copy it manually."}),e.jsxs("div",{className:"tr-workspace","data-empty":!Z,children:[e.jsxs("section",{className:"tr-feed","aria-label":"Collector activity",children:[Z&&e.jsx("div",{className:"tr-feed-head",children:e.jsxs("h3",{children:["Activity ",e.jsx("span",{className:"tr-count",children:ie.length})]})}),(Z||R!==null||g!=="all"||i!=="all")&&e.jsxs("details",{className:"tr-details tr-feed-options",children:[e.jsxs("summary",{children:["Filters and history",g!=="all"||i!=="all"?" · Filtered":""]}),e.jsxs("div",{className:"tr-filter",children:[e.jsxs("label",{children:["Type",e.jsxs(he,{"aria-label":"Message type",value:g,onChange:n=>{m(null),k(n.target.value)},children:[e.jsx("option",{value:"all",children:"All types"}),e.jsx("option",{value:"logs",children:"Events"}),e.jsx("option",{value:"metrics",children:"Metrics"}),e.jsx("option",{value:"traces",children:"Traces"})]})]}),e.jsxs("label",{children:["To",e.jsxs(he,{"aria-label":"Message destination",value:i,onChange:n=>{m(null),u(n.target.value)},children:[e.jsx("option",{value:"all",children:"All destinations"}),[...new Set([...l,...((De=s==null?void 0:s.sources)==null?void 0:De.map(n=>n.destination))??[],...G.map(n=>n.destination),...((s==null?void 0:s.observations)??[]).map(n=>n.destination)])].map(n=>e.jsx("option",{value:n,children:n},n))]})]})]}),e.jsxs("p",{className:"tr-history",children:["Last 24 hours · ",V.length," decoded ",V.length===1?"message":"messages"," · ",H.length," ",H.length===1?"observation":"observations"]}),e.jsxs("div",{className:"tr-capture-actions",children:[e.jsx("button",{className:"btn","aria-pressed":R!==null,title:R?"Show new entries received while the list was paused":"Freeze the list while monitoring continues",disabled:!fe.length&&!H.length&&R===null,onClick:()=>{me(R?null:[...fe]),Y(R?null:((s==null?void 0:s.observations)??[]).map(n=>n.id))},children:R?"Resume list":"Pause list"}),e.jsx("button",{className:K?"btn danger armed":"btn",...ee("clear"),disabled:ee("clear").disabled||!(s!=null&&s.events.length)&&!(($e=s==null?void 0:s.observations)!=null&&$e.length),title:K?"Remove decoded messages and observations. Monitoring continues.":"Clear retained messages and connection observations",onBlur:()=>oe(!1),onClick:ts,children:K?"Confirm clear history":"Clear history"})]})]}),R&&e.jsxs("p",{className:"tr-list-notice",role:"status",children:["List paused · Monitoring continues",pe>0?` · ${pe} new ${pe===1?"entry":"entries"}`:""]}),(Pe=s==null?void 0:s.retention)!=null&&Pe.evictedExports?e.jsxs("p",{className:"tr-note",children:[s.retention.evictedExports.toLocaleString()," older messages removed by history limits."]}):null,ie.length?e.jsx("ul",{className:"tr-connections",onKeyDown:n=>{var Ve;if(!["ArrowDown","ArrowUp","Home","End"].includes(n.key))return;const v=Array.from(n.currentTarget.querySelectorAll(".tr-message")),O=v.indexOf(document.activeElement);if(O<0)return;n.preventDefault();const I=n.key==="Home"?0:n.key==="End"?v.length-1:Math.max(0,Math.min(v.length-1,O+(n.key==="ArrowDown"?1:-1))),q=ie[I];q&&(m(q.kind==="export"?q.event.id:null),E(q.kind==="observation"?q.event.id:null)),(Ve=v[I])==null||Ve.focus()},children:ie.slice(0,A).map(n=>n.kind==="export"?e.jsx("li",{children:e.jsxs("button",{className:"tr-message","aria-pressed":(C==null?void 0:C.id)===n.event.id,onClick:()=>ns(n.event),children:[e.jsx(W,{kind:n.event.signal,className:"tr-row-icon"}),e.jsxs("span",{className:"tr-event-top",children:[e.jsx("strong",{className:"tr-event-name",children:n.event.name}),e.jsx("time",{dateTime:new Date(n.event.at).toISOString(),children:de(n.event.at)})]}),e.jsxs("span",{className:"tr-event-context",children:[e.jsx("span",{className:"tr-signal",children:n.event.signal==="logs"?"Decoded event":n.event.signal==="metrics"?"Decoded metric":"Decoded trace"}),e.jsx("span",{children:Ee(n.event.sessionIds)})]}),e.jsx("code",{className:"tr-event-destination",children:n.event.destination}),e.jsxs("span",{className:"tr-event-bottom",children:[e.jsx("span",{className:`tr-receipt ${n.event.outcome==="accepted"?"tr-tone-ok":n.event.outcome==="rejected"?"tr-tone-error":n.event.outcome==="partial"?"tr-tone-enabled":""}`,children:n.event.outcome==="accepted"?"Accepted":n.event.outcome==="unconfirmed"?"Receipt unconfirmed":n.event.outcome==="partial"?"Partially accepted":n.event.outcome==="rejected"?"Rejected":"Receipt unknown"}),e.jsxs("span",{children:[n.event.count," ",n.event.count===1?"record":"records"]})]})]})},`event-${n.event.id}`):e.jsx("li",{children:e.jsxs("button",{className:"tr-message","aria-pressed":S===n.event.id,onClick:()=>{var v,O,I;m(null),E(n.event.id),(v=window.matchMedia)!=null&&v.call(window,"(max-width: 640px)").matches&&((O=te.current)==null||O.focus({preventScroll:!0}),(I=te.current)==null||I.scrollIntoView({block:"start",behavior:"auto"}))},children:[e.jsx(W,{kind:"connection",className:"tr-row-icon"}),e.jsxs("span",{className:"tr-event-top",children:[e.jsx("strong",{className:"tr-event-name",children:"Connection observed"}),e.jsx("time",{dateTime:new Date(n.event.at).toISOString(),children:de(n.event.at)})]}),e.jsx("code",{className:"tr-event-destination",children:n.event.destination}),e.jsx("span",{children:n.event.reason==="encrypted"?"Encrypted contents":"Contents not decoded"})]})},`observation-${n.event.id}`))}):e.jsxs("div",{className:"tr-empty-capture",children:[!Z&&e.jsx(W,{kind:"radar"}),e.jsx("h4",{children:G.length||(Fe=s==null?void 0:s.observations)!=null&&Fe.length?"No matching activity":B?"Waiting for traffic":"Message capture is off"}),e.jsx("p",{className:"tr-note",children:G.length||(Je=s==null?void 0:s.observations)!=null&&Je.length?"Try another session, type or destination.":B?"New collector activity will appear here. Keep using Claude while Radar listens.":"Choose a destination and start monitoring. New collector activity will appear here."})]}),ie.length>A&&e.jsx("button",{className:"btn",onClick:()=>M(n=>n+100),children:"Show older messages"}),be.length>0&&e.jsxs("details",{className:"tr-details",children:[e.jsxs("summary",{children:["Connections to these collectors (",be.length,")"]}),e.jsx("ul",{className:"tr-live-connections",children:be.map(n=>e.jsxs("li",{children:[e.jsx("code",{children:n.destination}),e.jsxs("span",{children:["Claude · PID ",n.pid," · ",n.workspace??"Workspace unknown"]})]},`${n.pid}:${n.destination}`))}),e.jsx("p",{className:"tr-note",children:"A connection alone does not prove a telemetry message was sent. These connections are not filtered by session."})]})]}),e.jsx("section",{className:"tr-inspector",hidden:!Z,"aria-label":"Activity inspector",ref:te,tabIndex:-1,children:P?e.jsxs("div",{children:[e.jsx("div",{className:"tr-inspector-head",children:e.jsxs("h3",{children:[e.jsx(W,{kind:"connection"}),"Connection details"]})}),e.jsxs("dl",{className:"tr-facts",children:[e.jsxs("div",{children:[e.jsx("dt",{children:"Destination"}),e.jsxs("dd",{className:"tr-fact-copy",children:[e.jsx("code",{children:P.destination}),e.jsx("button",{className:"tr-inline-copy","aria-label":"Copy destination",onClick:()=>void ce(P.destination,"destination"),title:J==="destination"?"Copied destination":"Copy destination",children:e.jsx("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:J==="destination"?e.jsx("path",{d:"m5 12 4 4L19 6"}):e.jsxs(e.Fragment,{children:[e.jsx("rect",{x:"8",y:"8",width:"12",height:"12",rx:"2"}),e.jsx("path",{d:"M16 8V4H4v12h4"})]})})})]})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"Observed"}),e.jsx("dd",{children:e.jsx("time",{dateTime:new Date(P.at).toISOString(),children:new Date(P.at).toLocaleString()})})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"Source"}),e.jsx("dd",{children:e.jsx("code",{children:P.source||"Not identified"})})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"Contents"}),e.jsx("dd",{children:P.reason==="encrypted"?"Encrypted":"Not decoded"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"Session"}),e.jsx("dd",{children:"Not identified"})]})]}),e.jsxs("div",{className:"tr-inspection-limit",children:[e.jsx(W,{kind:"info"}),e.jsxs("div",{children:[e.jsx("h4",{children:P.reason==="encrypted"?"Encrypted connection":"Message contents unavailable"}),e.jsx("p",{children:P.reason==="joined_midstream"?"Radar joined an existing connection. Keep listening: new connections may allow decoding.":P.reason==="encrypted"?"Radar cannot decrypt HTTPS. Inspect the collector’s own records for the contents.":Ne[P.reason]??"This connection could not be decoded."}),e.jsxs("details",{className:"tr-details",children:[e.jsx("summary",{children:"Technical details"}),e.jsx("p",{className:"tr-note",children:Ne[P.reason]??"The decoder could not read this connection."}),e.jsxs("p",{className:"tr-note",children:["There is no decoded JSON or confirmed session ID for this observation. ",P.reason==="joined_midstream"&&"Already captured traffic cannot be reconstructed."]})]})]})]})]}):C?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"tr-inspector-head",children:[e.jsxs("h3",{children:[e.jsx(W,{kind:C.signal}),C.name]}),e.jsxs("p",{className:"tr-note",children:["Decoded ",C.signal==="logs"?"event":C.signal==="metrics"?"metrics":"trace"," · ",Ee(C.sessionIds)," · ",de(C.at)]})]}),e.jsxs("div",{className:"tr-detail-tabs",role:"group","aria-label":"Message format",children:[e.jsx("button",{className:"btn","aria-pressed":xe==="summary",onClick:()=>Ce("summary"),children:"Overview"}),e.jsx("button",{className:"btn","aria-pressed":xe==="json",onClick:()=>Ce("json"),children:"JSON"}),e.jsx("button",{className:"btn tr-copy-json",disabled:!(D!=null&&D.payload)||D.id!==C.id,onClick:()=>void ce(JSON.stringify(D==null?void 0:D.payload,null,2),"json"),children:J==="json"?"Copied":"Copy JSON"})]}),(D==null?void 0:D.id)===C.id?D.payload?xe==="json"?e.jsxs(e.Fragment,{children:[e.jsx("p",{className:"tr-note",children:"Decoded OTLP fields. Unknown protobuf fields and transport headers are excluded."}),e.jsx(ps,{value:D.payload})]}):e.jsxs(e.Fragment,{children:[e.jsxs("dl",{className:"tr-facts",children:[e.jsxs("div",{children:[e.jsx("dt",{children:"Destination"}),e.jsxs("dd",{className:"tr-fact-copy",children:[e.jsx("code",{children:C.destination}),e.jsx("button",{className:"tr-inline-copy","aria-label":"Copy destination",onClick:()=>void ce(C.destination,"destination"),title:J==="destination"?"Copied destination":"Copy destination",children:e.jsx("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:J==="destination"?e.jsx("path",{d:"m5 12 4 4L19 6"}):e.jsxs(e.Fragment,{children:[e.jsx("rect",{x:"8",y:"8",width:"12",height:"12",rx:"2"}),e.jsx("path",{d:"M16 8V4H4v12h4"})]})})})]})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"Time"}),e.jsx("dd",{children:de(C.at)})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"Receipt"}),e.jsx("dd",{children:vs(C)})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"Size"}),e.jsxs("dd",{children:[C.bytes.toLocaleString()," bytes · ",C.count," ",C.count===1?"record":"records"]})]}),((Be=C.response)==null?void 0:Be.message)&&e.jsxs("div",{children:[e.jsx("dt",{children:"Response"}),e.jsx("dd",{children:e.jsx(se,{value:C.response.message})})]})]}),e.jsx(ws,{payload:D.payload,signal:C.signal})]}):e.jsxs("div",{role:"alert",children:[e.jsx("p",{children:D.error}),e.jsx("button",{className:"btn",onClick:()=>Ke(ke+1),children:"Retry"})]}):e.jsx("p",{className:"tr-note",role:"status",children:"Loading message contents…"})]}):e.jsxs("div",{className:"tr-inspector-empty",children:[e.jsx("svg",{width:"40",height:"40",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.25","aria-hidden":"true",children:e.jsx("path",{d:"M8 4H5a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h3M16 4h3a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-3M10 8l-3 4 3 4M14 8l3 4-3 4"})}),e.jsx("h3",{children:N!==null||S!==null?"Activity no longer available":"Inspect collector activity"}),e.jsx("p",{className:"tr-note",children:N!==null||S!==null?"The selected entry is no longer retained or does not match your filter. Select another entry.":U.traffic&&!U.decoded?"Traffic has been observed, but no messages have been decoded. Select a connection observation to see what is known.":"Select an entry to inspect its metadata and any decoded contents."})]})})]}),e.jsxs("footer",{className:"tr-monitor-footer",children:[(B||Z||r)&&e.jsxs("span",{children:[r?"Connection interrupted":B?Q?"Continuous local monitoring":"Local capture":"Not monitoring"," · History in local memory"]}),e.jsxs("details",{children:[e.jsx("summary",{children:"Monitoring limits"}),e.jsxs("div",{children:[e.jsx("p",{children:"Plaintext IPv4 OTLP/gRPC only. HTTPS contents cannot be decoded. Traffic to the chosen addresses can come from any process, not only Claude."}),e.jsx("p",{children:"History covers the last 24 hours, up to 2,000 exports and a 32 MiB payload budget. When a limit is reached, the oldest messages are removed."}),e.jsxs("p",{children:["History stays in local memory. Restarting ccdeck or clearing messages removes it. Closing this modal does not stop capture or clear history; stopping monitoring preserves history. No capture file is written. Use Stop monitoring",!Q&&e.jsxs(e.Fragment,{children:[" and Ctrl+C in ",ae]})," to stop capture."]}),s!=null&&s.bytes?e.jsxs("p",{children:["Captured transport bytes: ",s.bytes.toLocaleString(),". This is not a decoded-message count."]}):null,Q&&e.jsx("p",{children:"Enabled monitoring resumes after a ccdeck restart; the in-memory history does not."}),Object.entries((s==null?void 0:s.issues)??{}).map(([n,v])=>e.jsxs("p",{children:[Ne[n]??"An inspection limitation was observed."," (",v,")"]},n))]})]})]})]})}function Cs({sessionFilter:s="all",onSessions:t}){const[r,c]=a.useState([]),[x,p]=a.useState(""),[j,d]=a.useState(""),[h,o]=a.useState(!1),[b,f]=a.useState(null),[l,w]=a.useState(""),[y,L]=a.useState(!1),T=a.useRef(0),$=async m=>{if(!m)return;const i=++T.current;d(""),o(!0),L(!1);try{if(m.size>10*1024*1024)throw new Error("Choose a file smaller than 10 MB.");const u=await m.text(),g=hs(u);if(i!==T.current)return;t==null||t([...new Set(g.flatMap(k=>k.sessionIds))]),c(g),p(m.name),f(null),w(""),g.length||d("This file is empty. Choose a JSON or JSONL telemetry file.")}catch(u){i===T.current&&d(u instanceof Error?u.message:"Could not read this file.")}finally{i===T.current&&o(!1)}},S=r.filter(m=>(s==="all"||(s==="unidentified"?!m.sessionIds.length:m.sessionIds.includes(s)))&&(!l||m.raw.toLowerCase().includes(l.toLowerCase()))),E=S.slice(0,200),N=b===null?E[0]:S.find(m=>m.line===b);return e.jsxs("div",{className:"tr-file",children:[e.jsxs("div",{className:"tr-file-toolbar",children:[e.jsxs("div",{children:[e.jsx("h3",{children:"Inspect a telemetry file"}),e.jsx("p",{className:"tr-note",children:"Open a local JSON or JSONL file. It stays in your browser and is not uploaded."})]}),e.jsxs("label",{className:"btn tr-file-picker",children:[h?"Reading…":x?"Choose another file":"Open file",e.jsx("input",{type:"file",accept:".json,.jsonl,.ndjson,application/json","aria-label":"Open telemetry file",onChange:m=>{var i;$((i=m.target.files)==null?void 0:i[0]),m.target.value=""}})]})]}),j&&e.jsx("p",{className:"tr-capture-error",role:"alert",children:j}),e.jsxs("div",{className:"tr-workspace",children:[e.jsxs("section",{className:"tr-feed","aria-label":"File records",children:[e.jsxs("h3",{children:[x||"File records"," ",e.jsx("span",{className:"tr-count",children:S.length})]}),r.length>0&&e.jsx("input",{type:"search",className:"tr-input tr-search","aria-label":"Search file contents",placeholder:"Search contents…",value:l,onChange:m=>{w(m.target.value),f(null)}}),E.length?e.jsx("ul",{className:"tr-connections",children:E.map(m=>e.jsx("li",{children:e.jsxs("button",{className:"tr-message","aria-pressed":(N==null?void 0:N.line)===m.line,onClick:()=>{f(m.line),L(!1)},children:[e.jsxs("span",{className:"tr-event-top",children:[e.jsxs("span",{children:["Line ",m.line]}),e.jsx("span",{className:m.valid?"":"tr-tone-enabled",children:m.valid?"JSON":"Invalid JSON"})]}),e.jsx("strong",{children:m.label}),m.timestamp&&e.jsx("span",{children:m.timestamp}),e.jsx("span",{children:m.sessionIds.join(", ")||"Unidentified session"})]})},m.line))}):e.jsxs("div",{className:"tr-empty-capture",children:[e.jsx("h4",{children:r.length?"No matching records":"Your saved records, in one place"}),e.jsx("p",{className:"tr-note",children:r.length?"Try another session or search.":"Choose a file to browse its rows and inspect the original JSON. A saved file is separate from live network capture."})]}),S.length>E.length&&e.jsx("p",{className:"tr-note",children:"Showing the first 200 matching rows. Search to narrow the list."})]}),e.jsx("section",{className:"tr-inspector","aria-label":"File record contents",children:N?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"tr-feed-head",children:[e.jsxs("h3",{children:["Line ",N.line]}),e.jsx("button",{className:"btn",onClick:()=>{navigator.clipboard.writeText(N.raw).then(()=>L(!0),()=>d("Clipboard unavailable. Select the JSON and copy it manually."))},children:y?"Copied":"Copy JSON"})]}),e.jsx("p",{className:"tr-note",children:"Original file contents. This does not prove the record was sent or received."}),e.jsx("pre",{className:"tr-value",children:N.raw})]}):e.jsxs("div",{className:"tr-inspector-empty",children:[e.jsx("h3",{children:"Inspect a record"}),e.jsx("p",{className:"tr-note",children:"Select a row to read its original contents."})]})})]})]})}function As({onClose:s,sessions:t=[]}){const r=os(s),c=as(s),{snapshot:x,failed:p,updateConfig:j}=us(),d=ms();return e.jsx("div",{className:"modal-backdrop",...c,role:"presentation",children:e.jsxs("div",{className:"modal tr-modal",ref:r,role:"dialog","aria-modal":"true","aria-labelledby":"tr-title","aria-describedby":"tr-purpose",onClick:h=>h.stopPropagation(),children:[e.jsxs("header",{className:"modal-head",children:[e.jsxs("div",{className:"tr-identity",children:[e.jsx(W,{kind:"radar"}),e.jsxs("div",{children:[e.jsxs("div",{className:"modal-title",children:[e.jsx("span",{className:"modal-tool-name",id:"tr-title",children:"Telemetry Radar"}),e.jsx("span",{className:"tr-beta",title:"Beta feature — still being tested and improved.",children:"Beta"})]}),e.jsx("p",{id:"tr-purpose",children:"Inspect Claude Code’s collector traffic."})]})]}),e.jsx("button",{className:"glyph-btn",onClick:s,"aria-label":"Close (Esc)",title:"Close (Esc)",children:"×"})]}),e.jsx(ks,{snapshot:x,failed:p,captureState:d,sessions:t,onConfigChange:j})]})})}function ks({snapshot:s,failed:t=!1,captureState:r,sessions:c=[],onConfigChange:x}){var E,N,m,i;const[p,j]=a.useState("monitor"),[d,h]=a.useState([]),[o,b]=a.useState("all"),f=["monitor","configuration","file"],l=u=>{var A;const g=ls(u,f.indexOf(p),f.length);if(g.kind!=="select")return;u.preventDefault();const k=f[g.index];j(k),(A=document.getElementById(`tr-tab-${k}`))==null||A.focus()},w=((E=s==null?void 0:s.config)==null?void 0:E.variables)??[],y=Ge(w),L=ds(w),T=(N=s==null?void 0:s.config)==null?void 0:N.sources.some(u=>u.status==="unavailable"),$=t?[]:qe(s),S=new Map(c.map(u=>[u.id,u.label]));for(const u of((m=r==null?void 0:r.capture)==null?void 0:m.events)??[])for(const g of u.sessionIds??[])S.has(g)||S.set(g,g.slice(0,8));for(const u of d)S.has(u)||S.set(u,u.slice(0,8));return e.jsxs("div",{className:"tr-body",children:[e.jsxs("div",{className:"tr-navigation",children:[e.jsx("div",{className:"tr-tabs",role:"tablist","aria-label":"Radar views",onKeyDown:l,children:[["monitor","Monitor"],["configuration","Configuration"],["file","File"]].map(([u,g])=>e.jsx("button",{id:`tr-tab-${u}`,className:"tr-tab",role:"tab","aria-selected":p===u,"aria-controls":`tr-panel-${u}`,tabIndex:p===u?0:-1,onClick:()=>j(u),children:g},u))}),e.jsxs("label",{className:"tr-session-filter",hidden:!S.size&&o==="all"&&!((i=r==null?void 0:r.capture)!=null&&i.events.length),children:["Session",e.jsxs(he,{value:o,onChange:u=>b(u.target.value),"aria-label":"Session",children:[e.jsx("option",{value:"all",children:"All sessions"}),[...S].map(([u,g])=>e.jsxs("option",{value:u,children:[g," · ",u.slice(0,8)]},u)),e.jsx("option",{value:"unidentified",children:"Unidentified session"})]})]}),e.jsxs("details",{className:"tr-help",children:[e.jsxs("summary",{children:[e.jsx(W,{kind:"info"}),e.jsx("span",{className:"tr-help-label",children:"About Radar"})]}),e.jsxs("div",{children:[e.jsx("h3",{children:"What does Radar show?"}),e.jsx("p",{children:"See what Claude Code sends to your telemetry collectors. Destinations can be configured by you or your organization."}),e.jsx("p",{children:"Radar inspects configured OpenTelemetry exports, including prompts or responses when enabled and captured. It does not show everything Claude knows about you, ordinary AI requests, or Anthropic’s separate service telemetry."}),e.jsx("p",{children:"An address does not identify who owns a collector or who can access its data. Encrypted connections can be observed, but their contents cannot be decoded here."}),e.jsx("a",{href:"https://code.claude.com/docs/en/monitoring-usage",target:"_blank",rel:"noreferrer",children:"Claude Code telemetry documentation"})]})]})]}),e.jsx("div",{className:"tr-page",id:"tr-panel-monitor",role:"tabpanel","aria-labelledby":"tr-tab-monitor",hidden:p!=="monitor",children:r?e.jsx(Ss,{...r,radar:t&&s?{...s,status:"unavailable"}:s,sessionFilter:o,sessions:c}):e.jsx("p",{className:"tr-empty",children:"Reading monitor status…"})}),e.jsxs("div",{className:"tr-page tr-configuration-page",id:"tr-panel-configuration",role:"tabpanel","aria-labelledby":"tr-tab-configuration",hidden:p!=="configuration",children:[e.jsxs("div",{className:"tr-summary",children:[e.jsxs("div",{className:"tr-summary-state",children:[e.jsx("span",{className:`tr-state ${t||!s||T?"tr-tone-unknown":L==="Enabled in settings"?"tr-tone-enabled":"tr-tone-unknown"}`,children:t?"Configuration unavailable":s?T?"Configuration incomplete":L==="Unknown"?"Configuration unknown":L:"Reading settings…"}),e.jsx("span",{className:"tr-note",children:y.length?`${y.length} configured destination${y.length===1?"":"s"}`:"No destination found in the settings files read. Telemetry may still be running."})]}),y.length>0&&e.jsx("div",{className:"tr-endpoints",children:y.map(u=>e.jsx("code",{children:u},u))}),!y.length&&$.length>0&&e.jsxs("div",{className:"tr-observed-summary",children:[e.jsx("span",{className:"tr-state",children:"Claude connections observed"}),e.jsx("div",{className:"tr-endpoints",children:$.map(u=>e.jsx("code",{children:u},u))}),e.jsx("p",{className:"tr-note",children:"These are active connections, not confirmed telemetry messages. Choose an observed address below to inspect its traffic."})]})]}),s?e.jsx(Ts,{snapshot:s,stale:t,onConfigChange:x,sessionLabel:o==="all"?void 0:S.get(o)??o}):e.jsx("p",{className:"tr-empty",children:t?"Could not read settings. Sampling will retry.":"Reading local settings…"})]}),e.jsx("div",{className:"tr-page",id:"tr-panel-file",role:"tabpanel","aria-labelledby":"tr-tab-file",hidden:p!=="file",children:e.jsx(Cs,{onSessions:h,sessionFilter:o})})]})}function Ts({snapshot:s,sessionLabel:t,stale:r=!1,onConfigChange:c}){var E,N,m;const[x,p]=a.useState(null),[j,d]=a.useState(null),h=a.useRef(null),[o,b]=a.useState(""),[f,l]=a.useState(""),w=(E=x??s.config)==null?void 0:E.sources.some(i=>i.status==="unavailable");a.useEffect(()=>{p(null)},[s.config]);const y=((N=x??s.config)==null?void 0:N.variables)??[],L=async(i,u)=>{if(!Ue(h.current)||r)return;h.current=i,d(i),l(""),b("");const g=new AbortController,k=setTimeout(()=>g.abort(),2e4);try{const A=await fetch("/api/system/traffic-radar/configuration",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:i,enabled:u}),signal:g.signal}),M=await A.json();if(!A.ok||!M.ok)throw new Error(M.error??"Could not save this setting.");p(M.config),c==null||c(M.config),b("Saved to user settings. Start a new Claude session to apply the change.")}catch(A){l(g.signal.aborted?"Saving timed out. Check the current setting and try again.":A instanceof Error?A.message:"Could not save this setting. Try again.")}finally{clearTimeout(k),h.current=null,d(null)}},T=y.filter(i=>i.key.endsWith("ENDPOINT")),$=y.filter(i=>i.key.startsWith("OTEL_LOG_")||i.key.includes("ENABLE")||i.key.includes("TELEMETRY_BETA")),S={CLAUDE_CODE_ENABLE_TELEMETRY:"Telemetry",OTEL_LOG_USER_PROMPTS:"Your prompts",OTEL_LOG_ASSISTANT_RESPONSES:"Claude responses",OTEL_LOG_TOOL_DETAILS:"Tool details",OTEL_LOG_TOOL_CONTENT:"Tool contents",OTEL_LOG_RAW_API_BODIES:"Full API bodies",OTEL_LOG_MANAGED_SETTINGS:"Managed settings",CLAUDE_CODE_ENHANCED_TELEMETRY_BETA:"Enhanced telemetry",ENABLE_ENHANCED_TELEMETRY_BETA:"Enhanced telemetry"};return e.jsxs("section",{className:"tr-config","aria-label":"Telemetry configuration",children:[e.jsx("h3",{children:"What Claude is configured to send"}),r&&e.jsx("p",{className:"tr-warning",role:"status",children:"Last successfully read settings. Current configuration could not be verified."}),w&&e.jsx("p",{className:"tr-warning",role:"status",children:"Some settings sources could not be read. Editing is unavailable until they can be verified."}),e.jsx("p",{className:"tr-note",children:"Read from local Claude settings. The effective environment of a running session is not verified."}),t&&e.jsxs("p",{className:"tr-note",children:["Selected session: ",t,". These shared settings are evidence; this session’s runtime values are unverified."]}),e.jsx("h4",{children:"Destinations"}),T.length?e.jsx("ul",{className:"tr-destinations",children:T.map(i=>e.jsxs("li",{children:[e.jsx("code",{children:i.value}),e.jsxs("span",{children:[i.source," · ",i.key]})]},`${i.source}:${i.key}`))}):e.jsx("p",{className:"tr-note",children:"No destination found in the sources read. This does not mean telemetry is off."}),e.jsx("h4",{children:"Telemetry and content"}),e.jsx("p",{className:"tr-note",children:"Controls save shared user settings for new Claude sessions. Organization and managed flags require an administrator."}),o&&e.jsx("p",{className:"tr-note",role:"status",children:o}),f&&e.jsx("p",{className:"tr-warning",role:"alert",children:f}),$.length?e.jsx("dl",{className:"tr-variables tr-flags",children:$.map(i=>{const u=i.value.startsWith("Enabled"),g=u||i.value==="Disabled",k=We(j,i.key),A=`${S[i.key]??i.key} (${i.source})`,M=r||w||i.editable!==!0&&!i.blockedReason;return e.jsxs("div",{children:[e.jsxs("dt",{children:[e.jsx("strong",{children:S[i.key]??i.key}),e.jsx("code",{children:i.key}),e.jsx("span",{children:i.source}),i.blockedReason&&!w&&e.jsx("span",{children:i.blockedReason})]}),e.jsxs("dd",{className:"tr-flag-control",children:[e.jsxs("span",{className:"tr-flag-value",children:[e.jsx("span",{className:`tr-state ${u?"tr-tone-enabled":"tr-tone-unknown"}`,children:i.value}),i.rawValue!==void 0&&e.jsx("code",{className:"tr-raw-flag",children:i.rawValue})]}),e.jsxs("span",{className:"tr-flag-action",children:[i.editable!==!0||M?e.jsx("span",{className:"tr-control-note",title:i.blockedReason,children:M?"Unavailable":"Managed"}):g?e.jsx("button",{type:"button",className:"switch",role:"switch","data-tone":"warn","aria-checked":u,"aria-label":A,"aria-busy":k.busy,disabled:k.disabled,title:`Turn ${u?"off":"on"} for new Claude sessions`,onClick:()=>void L(i.key,!u),children:e.jsx("span",{className:"switch-knob"})}):e.jsx("span",{className:"tr-unknown-actions",children:[!0,!1].map(_=>e.jsx("button",{type:"button",className:"btn","aria-label":`${_?"Enable":"Disable"} ${A}`,"aria-busy":k.busy,disabled:k.disabled,onClick:()=>void L(i.key,_),children:_?"Enable":"Disable"},String(_)))}),j===i.key&&e.jsx("span",{className:"tr-control-note",role:"status",children:"Saving…"})]})]})]},`${i.source}:${i.key}`)})}):e.jsx("p",{className:"tr-note",children:"No telemetry flags found in the settings read."}),!T.length&&s.connections.length>0&&e.jsxs("details",{className:"tr-details",children:[e.jsxs("summary",{children:["Observed Claude connections (",s.connections.length,")"]}),e.jsx("ul",{className:"tr-live-connections",children:s.connections.map(i=>e.jsxs("li",{children:[e.jsx("code",{children:i.destination}),e.jsxs("span",{children:[r||i.active===null?"Visibility unavailable":i.active?"Active connection":"Previously observed"," · PID ",i.pid," · ",i.workspace??"Workspace unknown"]})]},`${i.pid}:${i.destination}`))}),e.jsx("p",{className:"tr-note",children:"A running process can keep previously loaded settings after a settings file disappears. Connections alone do not identify telemetry or prove a message was sent."})]}),e.jsxs("details",{className:"tr-details",children:[e.jsx("summary",{children:"All variables and sources"}),e.jsx("dl",{className:"tr-variables",children:y.map(i=>e.jsxs("div",{children:[e.jsxs("dt",{children:[e.jsx("code",{children:i.key}),e.jsx("span",{children:i.source})]}),e.jsx("dd",{children:i.value})]},`${i.source}:${i.key}`))}),e.jsx("ul",{className:"tr-sources",children:(m=s.config)==null?void 0:m.sources.map(i=>e.jsxs("li",{children:[e.jsx("span",{children:i.name}),e.jsx("span",{children:i.status==="read"?"Read":i.status==="absent"?"Not found":"Unavailable"})]},i.name))}),e.jsx("p",{className:"tr-note",children:"Endpoint credentials and headers are hidden. Settings can differ from an already running process."})]})]})}export{Ts as Configuration,ks as TrafficRadarView,As as default};