ccdeck 3.43.3 → 3.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +74 -20
  2. package/THIRD_PARTY_NOTICES.md +43 -7
  3. package/bin/cli/commands.js +613 -0
  4. package/bin/cli/help.js +26 -3
  5. package/bin/cli/one-shot.js +2 -2
  6. package/bin/cli/picker.js +86 -0
  7. package/bin/cli/startup.js +13 -0
  8. package/bin/deck.js +60 -16
  9. package/dist/web/assets/{BrowserWatchModal-DZTMfoGp.js → BrowserWatchModal-DMIUygWz.js} +1 -1
  10. package/dist/web/assets/{RadioPanel-DFqW-MUV.js → RadioPanel-DNB_AHSs.js} +1 -1
  11. package/dist/web/assets/TrafficRadar-Ch0LVdSl.js +8 -0
  12. package/dist/web/assets/UsageHistoryModal-Du0ZkIJ1.js +1 -0
  13. package/dist/web/assets/app-CQIstkMF.js +318 -0
  14. package/dist/web/assets/app-Ch-0CYlK.css +1 -0
  15. package/dist/web/assets/{index-DqxnkNs2.js → index-BQbOfxbL.js} +1 -1
  16. package/dist/web/index.html +3 -3
  17. package/package.json +2 -1
  18. package/release-notes.json +40 -0
  19. package/src/server/account-notify.mjs +206 -9
  20. package/src/server/account-pick.mjs +160 -0
  21. package/src/server/account-projects.mjs +55 -15
  22. package/src/server/account-routes.mjs +47 -4
  23. package/src/server/account-watch.mjs +48 -4
  24. package/src/server/activity.mjs +3 -2
  25. package/src/server/args.mjs +112 -2
  26. package/src/server/autoswitch-failover.mjs +138 -0
  27. package/src/server/claude-accounts.mjs +115 -14
  28. package/src/server/claude-dir.mjs +142 -5
  29. package/src/server/claude-identity.mjs +23 -0
  30. package/src/server/cli-lines.mjs +224 -0
  31. package/src/server/codex-cost-usage.mjs +67 -0
  32. package/src/server/codex-enrichment.mjs +53 -9
  33. package/src/server/codex-review-policy.d.mts +4 -0
  34. package/src/server/codex-review-policy.mjs +11 -0
  35. package/src/server/codex-skills.mjs +146 -0
  36. package/src/server/codex-watch.mjs +5 -2
  37. package/src/server/cswap-auto-choice.mjs +53 -0
  38. package/src/server/cswap-auto-loop.mjs +100 -9
  39. package/src/server/deck-client.mjs +112 -0
  40. package/src/server/deck-prefs.mjs +30 -6
  41. package/src/server/event-pipeline.mjs +19 -3
  42. package/src/server/event-routes.mjs +15 -0
  43. package/src/server/exec.mjs +6 -2
  44. package/src/server/index.mjs +17 -1
  45. package/src/server/installer.mjs +149 -8
  46. package/src/server/lan-copies.mjs +43 -5
  47. package/src/server/lan-deck.mjs +3 -0
  48. package/src/server/lan-engine.mjs +7 -2
  49. package/src/server/lan-manifest.mjs +2 -2
  50. package/src/server/lan-serve.mjs +17 -2
  51. package/src/server/lan-sync.mjs +5 -5
  52. package/src/server/lan-wire.mjs +3 -3
  53. package/src/server/login-guard.mjs +162 -0
  54. package/src/server/login-service.mjs +3 -0
  55. package/src/server/pinned-build.mjs +5 -0
  56. package/src/server/prefs-routes.mjs +4 -0
  57. package/src/server/process-command.mjs +4 -1
  58. package/src/server/reports.mjs +7 -1
  59. package/src/server/request-gates.mjs +7 -0
  60. package/src/server/session-read-gate.mjs +5 -3
  61. package/src/server/session-summary.mjs +281 -0
  62. package/src/server/skill-hook.mjs +162 -0
  63. package/src/server/stop-continue-gate.d.mts +8 -0
  64. package/src/server/stop-continue-gate.mjs +77 -0
  65. package/src/server/stop-continue.mjs +544 -0
  66. package/src/server/stop-failure.d.mts +10 -0
  67. package/src/server/stop-failure.mjs +97 -0
  68. package/src/server/swap-log.mjs +19 -10
  69. package/src/server/tool-audit-report.mjs +5 -3
  70. package/src/server/tool-audit-routes.mjs +5 -3
  71. package/src/server/tool-audit-skills.mjs +82 -0
  72. package/src/server/tool-audit.mjs +53 -11
  73. package/src/server/traffic-radar-capture.mjs +10 -1
  74. package/src/server/traffic-radar-monitor.mjs +4 -2
  75. package/src/server/traffic-radar-routes.mjs +13 -0
  76. package/src/server/traffic-radar-service.mjs +133 -0
  77. package/src/server/traffic-radar-wire.mjs +4 -3
  78. package/src/server/usage-day.mjs +1 -1
  79. package/dist/web/assets/TrafficRadar-CPzsjc2O.js +0 -1
  80. package/dist/web/assets/UsageHistoryModal-CfVS-N_Z.js +0 -1
  81. package/dist/web/assets/app-DsHSufBt.css +0 -1
  82. package/dist/web/assets/app-HpFVXK_g.js +0 -310
package/README.md CHANGED
@@ -98,7 +98,7 @@ The deck opens on these eight pictures the first time it runs — they are the w
98
98
  | **Live DAG** | Nodes are agents and edges are spawns; each agent's latest tool calls sit beside its node and light up while they run. In-flight edges animate, settled ones dim. |
99
99
  | **Both providers, one canvas** | Claude Code through hooks, Codex through its rollout log. The model chip (`Opus 5`, `GPT-5.5`) tells them apart. Codex child sessions are connected when their rollout records an explicit parent. Each thread keeps its own tool calls and token totals. |
100
100
  | **Cost and quota, live** | Spend per model and per session, plus Claude and Codex quota windows as they refill. |
101
- | **Tool activity** | Which tools and commands your Claude Code and Codex agents ran on this machine, over the last day or up to 90 days: how many calls, how long they took, which ones fail. Kept on this machine and nowhere else. |
101
+ | **Tool activity** | Which tools and commands your Claude Code and Codex agents ran on this machine, over the last day or up to 90 days: how many calls, how long they took, which ones fail — and, on its Skills tab, which skills they used, how often and when last. Kept on this machine and nowhere else. |
102
102
  | **Double-click to inspect** | Any node opens its prompt, tool calls, token usage and timing in the side panel. A single click selects it and frames its session. |
103
103
  | **Survives restarts** | Events are appended to this platform's log directory (see `--history` below) and replayed on open. |
104
104
  | **Accounts without a terminal** | Sign a new Claude account in, move one or your whole set to another machine, rename, reorder, remove — from the panel. |
@@ -127,11 +127,11 @@ The panels open from the window's edges: the session list and the Claude account
127
127
 
128
128
  Two notes can appear at the bottom of the canvas: **Undo**, for a few seconds after `R` or the re-arrange button lays the canvas out again — `Ctrl+Z` (`⌘Z` on a Mac) does the same — and **Resume**, which turns auto-fit back on after you have panned or zoomed.
129
129
 
130
- Everything the page lets you set apart from the music — the theme, the text size and spacing, the character on the canvas, the single-key shortcuts, the sounds and notifications, how long Tool activity keeps its history — is in **Settings**, behind the gear at the right of the topbar, or `Cmd+,` (`Ctrl+,` on Windows and Linux); the music is in **Radio**, below. Changes apply immediately except startup recovery, which applies at the next server start. **Settings › General** holds twelve color themes, Gruvbox Dark, Gruvbox Light and Gruvbox Material the newest — a fresh install starts in Rider Black rather than following your system's light or dark setting, a theme you picked stays, `T` steps through all twelve, and the deck fades from one theme to the next over 180 ms, or switches at once when your system asks for reduced motion — and the character: Deck companion, Fox or Penguin, with a switch to hide it. **Settings › Text & density** has five text sizes — Smallest, Smaller, Default, Larger and Largest, 11, 12, 13, 15 and 17 px for body text — and five densities for the spacing in lists, panels and controls: Tightest, Tighter, Compact, Default and Comfortable, at 30%, 50%, 70%, 100% and 140% of the designed spacing. Both are kept in this browser, and neither is a zoom: your browser's zoom and the canvas's stay where they were, the panels widen for larger text, and the cards on the canvas follow it. **Reset to defaults** puts both back. The sounds and how loud each tone is are in **Settings › Sounds**, which `V` opens straight away; `M` turns sounds off or on from anywhere. `?` lists every key. If you use voice control or press letters by accident, turn off **Single-key shortcuts** in **Settings › General**: letters, `?` and Space then do nothing, while `Cmd+,`, `Esc`, `Enter`, `Tab`, `Delete` and the arrows keep working.
130
+ Everything the page lets you set apart from the music — the theme, the text size and spacing, the character on the canvas, the single-key shortcuts, the sounds and notifications, how long Tool activity keeps its history — is in **Settings**, behind the gear at the right of the topbar, or `Cmd+,` (`Ctrl+,` on Windows and Linux); the music is in **Radio**, below. Changes apply immediately except startup recovery, which applies at the next server start. **Settings › Appearance** holds twelve color themes, Gruvbox Dark, Gruvbox Light and Gruvbox Material the newest — a fresh install starts in Rider Black rather than following your system's light or dark setting, a theme you picked stays, `T` steps through all twelve, and the deck fades from one theme to the next over 180 ms, or switches at once when your system asks for reduced motion — and the character: Deck companion, Fox or Penguin, with a switch to hide it. **Settings › Text & density** has five text sizes — Smallest, Smaller, Default, Larger and Largest, 11, 12, 13, 15 and 17 px for body text — and five densities for the spacing in lists, panels and controls: Tightest, Tighter, Compact, Default and Comfortable, at 30%, 50%, 70%, 100% and 140% of the designed spacing. Both are kept in this browser, and neither is a zoom: your browser's zoom and the canvas's stay where they were, the panels widen for larger text, and the cards on the canvas follow it. **Reset to defaults** puts both back. The sounds and how loud each tone is are in **Settings › Sounds**, which `V` opens straight away; `M` turns sounds off or on from anywhere. `?` lists every key. If you use voice control or press letters by accident, turn off **Single-key shortcuts** in **Settings › Keyboard**, which also lists every shortcut: letters, `?` and Space then do nothing, while `Cmd+,`, `Esc`, `Enter`, `Tab`, `Delete` and the arrows keep working.
131
131
 
132
- **Radio**, on the right sidebar beside the character (in **More** on a phone), opens a responsive Radio Explorer with a large interactive map, station discovery and a persistent player. Filter by country, sound or language, save favorites, return to recent stations, or choose **Surprise me**. Your existing Claude FM, Lofi Girl and custom streams are under **Your streams**. Select a station to inspect it, then press Play to listen; closing Radio or hiding the character keeps it playing. Favorites, history and the last catalogue are saved in this browser. Control music directly in Radio Explorer; character settings are in **Settings › General**.
132
+ **Radio**, on the right sidebar beside the character (in **More** on a phone), opens a responsive Radio Explorer with a large interactive map, station discovery and a persistent player. Filter by country, sound or language, save favorites, return to recent stations, or choose **Surprise me**. Your existing Claude FM, Lofi Girl and custom streams are under **Your streams**. Select a station to inspect it, then press Play to listen; closing Radio or hiding the character keeps it playing. Favorites, history and the last catalogue are saved in this browser. Control music directly in Radio Explorer; character settings are in **Settings › Appearance**.
133
133
 
134
- **Tool activity**, on the stripe on the right below Usage history (in **More** on a phone), is a page of which tools and commands the Claude Code and Codex agents on this machine used. For the last 24 hours, 7, 30 or 90 days, and both CLIs or one, it lists each tool's calls, its total and usual time and its failures, sortable by any of them and searchable, under up to three findings worth a look: the tool that takes the most time, one that fails often, the slowest. A row opens that tool's details beside the list: who used it, its calls per day, its latest and slowest calls. A shell command counts per operation — `git status`, `git push` and `npm run build` are rows of their own — and an MCP tool under its own name. A call's time runs from the agent starting the tool to its result, so it includes any wait for your approval, and Claude Code and Codex time their tools differently. It counts what the deck captures from the first time it runs at 3.42.0 or later — earlier sessions are not read back — and it is kept apart from the canvas: Clear empties the canvas and leaves the tool history. What it keeps, and where, is in [What it touches](#what-it-touches).
134
+ **Tool activity**, on the stripe on the right below Usage history (in **More** on a phone), is a page of which tools and commands the Claude Code and Codex agents on this machine used. For the last 24 hours, 7, 30 or 90 days, and both CLIs or one, it lists each tool's calls, its total and usual time and its failures, sortable by any of them and searchable, under up to three findings worth a look: the tool that takes the most time, one that fails often, the slowest. A row opens that tool's details beside the list: who used it, its calls per day, its latest and slowest calls. A shell command counts per operation — `git status`, `git push` and `npm run build` are rows of their own — and an MCP tool under its own name. A call's time runs from the agent starting the tool to its result, so it includes any wait for your approval, and Claude Code and Codex time their tools differently. Its **Skills** tab, beside **Tools** at the top, lists the skills those agents used, ranked by uses, with when each last ran: a skill Claude calls, a slash command you type (only when the deck is started with `AGENTS_DECK_SKILL_HOOK=1` and every Claude Code on the machine is 2.1.116 or newer — see [How it works](#how-it-works)), a skill you pick in Codex, and one Codex picks itself, detected when it reads that skill's instructions. The same command run again in a session with the same arguments, such as a `/loop` tick, is a repeat and is left out of the count. A skill opens its details: its uses per day, the projects it ran in, and its latest uses, each linking to its session while that is still on the canvas. It counts what the deck captures from the first time it runs at 3.42.0 or later — earlier sessions are not read back — and it is kept apart from the canvas: Clear empties the canvas and leaves the tool history. What it keeps, and where, is in [What it touches](#what-it-touches).
135
135
 
136
136
  No config file. No account. What your sessions contain — your prompts, the replies, the files they touch — is never reported anywhere. The deck does send usage reports, on by default: its version, your system, your IP address and a device fingerprint, rough counts such as how many sessions ran in a day, and the errors it hits. It has no switch for them; `AGENTS_DECK_NO_REPORTS=1` keeps them off from the first start.
137
137
 
@@ -180,7 +180,13 @@ phone. Three views keep configuration, observed traffic and saved files separate
180
180
  the list. Filter by session, destination or message type, then select a message for its
181
181
  readable overview or decoded **JSON**. Session attribution comes from IDs in
182
182
  the captured payload; absent IDs remain unidentified, and an export containing
183
- multiple session IDs is shown under each matching session.
183
+ multiple session IDs is shown under each matching session. **With prompt**
184
+ narrows the list to exports carrying prompt fields, redacted ones included,
185
+ and their rows preview the exported text. The overview leads with what an
186
+ export holds — error, prompt, response, project, tool, model, tokens, cost —
187
+ and keeps the rest under **All record fields**; the JSON tree can be searched
188
+ and collapsed. Drag the divider, or focus it and use the arrow keys, to size
189
+ the list.
184
190
  - **Configuration** shows configured destinations, telemetry/content flags and
185
191
  their sources. Enabled flags are highlighted and recognized boolean values
186
192
  are shown as written. These are settings-file evidence, not confirmation of a
@@ -249,6 +255,13 @@ destinations are saved, never packet contents, tokens or decoded messages.
249
255
  Capture contents are available through the local loopback UI, not LAN endpoints.
250
256
  Local clients of the same server share its capture.
251
257
 
258
+ **Automatic monitoring** is off unless you turn it on in **Settings → Radar →
259
+ Monitor automatically**. On, ccdeck finds the collector itself — from Claude's
260
+ settings, a monitor you started, or an active IPv4 OTLP/gRPC connection on port
261
+ 4317 — monitors it without Radar open, and retries every five seconds while
262
+ none is available; Monitor then hides Start and the destination picker. Off
263
+ stops it, and nothing is looked for or retried.
264
+
252
265
  Configuration and connection sampling runs only while Radar is open and the
253
266
  browser page is visible. A connection does not prove an upload, and absence of
254
267
  observed traffic does not mean telemetry is disabled. Native-process sampling
@@ -310,9 +323,13 @@ Two capture paths feed one SSE stream, which feeds one canvas.
310
323
 
311
324
  ```
312
325
  SessionStart · UserPromptSubmit · PreToolUse · PostToolUse · PostToolUseFailure
313
- SubagentStart · SubagentStop · Stop · SessionEnd · Notification
326
+ SubagentStart · SubagentStop · Stop · StopFailure · SessionEnd · Notification
314
327
  ```
315
328
 
329
+ `StopFailure`, which Claude Code sends instead of `Stop` when a turn ends on an API error, is added only when every Claude Code the deck finds on disk is 2.1.78 or newer, read from its install rather than by running it. Older ones did not know the event, and before 2.1.101 an event name Claude Code did not know made it ignore the whole `settings.json`. When the deck cannot tell which version is installed, it leaves the entry out.
330
+
331
+ `UserPromptExpansion`, which lets Tool activity's Skills tab count the slash commands you type, is off by default, and there is no switch for it in Settings. The deck adds it only when you start it with `AGENTS_DECK_SKILL_HOOK=1` (any other value is off), and then only when every Claude Code the deck finds is 2.1.116 or newer, the first that knows the event. An install that does not say its version is asked `claude --version`; one that still does not say counts as too old. Until 2.1.101 a hook event Claude Code did not know made it ignore the whole `settings.json` without saying so, so the deck would rather count no typed commands than risk that. Turned on, it writes that one entry, and the first time it does, it keeps the `settings.json` it replaced as `agent-dag/settings.before-skill-hook.json` beside the hook, readable only by you. Off, it changes nothing in `settings.json`, with one exception: start the deck without the variable, or with an older Claude Code, after a start that added the entry, and that entry is taken back out. Nothing else is touched, and a `UserPromptExpansion` hook of your own stays. What the Skills tab records — the skill's name, the session it ran in and its project folder's name — stays on this machine (see [What it touches](#what-it-touches)).
332
+
316
333
  Each one fires the bundled `hook.js`, which POSTs the event JSON to the running server. The hook is fire-and-forget with a 1-second timeout: if the deck is not running, your session is not slowed down and nothing fails.
317
334
 
318
335
  **OpenAI Codex** — Codex CLI hooks do not fire reliably on Windows, so nothing is installed at all. The server tails Codex's own rollout files at `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl` and reconstructs the equivalent stream — session start, prompts, tool calls, token usage, model. No hook install, no trust prompt. Set `CODEX_HOME` to override the path.
@@ -325,9 +342,9 @@ Opening Radio asks the public [Radio Browser](https://www.radio-browser.info/) d
325
342
 
326
343
  It never steers an agent or edits your code, but it is not read-only either — besides the hook entry and its own event log, it manages the two tools it leans on, and it refreshes the Codex token it reads quota with, rewriting `~/.codex/auth.json` the way `codex` itself does. It also reads your browser's history, because Browser Watch is on unless you switch it off; that is its own section below, because it is the one thing here that is about you rather than about an agent.
327
344
 
328
- Telemetry Radar's flag switches write to `~/.claude/settings.json` too: each puts the flag you set — `CLAUDE_CODE_ENABLE_TELEMETRY`, `OTEL_LOG_USER_PROMPTS` and the other telemetry and content flags Radar lists — in that file's `env` block, as `"1"` or `"0"`. Switching a flag off writes `"0"` rather than removing it, and `--uninstall`, which removes ccdeck's hook entries, leaves these values as you set them. Starting Radar's monitoring writes `~/.agents-deck/radar-monitor-<port>.json`: the opt-in and the destinations a deck on that port resumes, never packets or decoded messages. And the quota readings the deck takes are kept in `quota-history.json`, in its data directory (see [Uninstall](#uninstall)), for 35 days and up to 2 MB, under a hash of the account rather than its address, for the Usage panel's quota history.
345
+ Telemetry Radar's flag switches write to `~/.claude/settings.json` too: each puts the flag you set — `CLAUDE_CODE_ENABLE_TELEMETRY`, `OTEL_LOG_USER_PROMPTS` and the other telemetry and content flags Radar lists — in that file's `env` block, as `"1"` or `"0"`. Switching a flag off writes `"0"` rather than removing it, and `--uninstall`, which removes ccdeck's hook entries, leaves these values as you set them. Starting Radar's monitoring writes `~/.agents-deck/radar-monitor-<port>.json`: the opt-in and the destinations a deck on that port resumes, never packets or decoded messages; turning on automatic monitoring writes `radar-settings-<port>.json` beside it, the switch and the collectors it found. And the quota readings the deck takes are kept in `quota-history.json`, in its data directory (see [Uninstall](#uninstall)), for 35 days and up to 2 MB, under a hash of the account rather than its address, for the Usage panel's quota history.
329
346
 
330
- Tool activity keeps its history in `tool-audit/`, in the same data directory: a file a day with a line per tool call — Claude Code or Codex, when it started and ended, whether it failed, and the tool's name or, for a shell command, its operation (`git push`, `npm run build`) — and a `settings.json` holding how long to keep it, when it started counting, and a random id for this device that nothing sends anywhere. The rest of a command line, the tool's arguments and output, paths and session ids are not saved, and nothing in the folder leaves the machine. It keeps 90 days unless you pick 30 or 60 under **Keep history** in **Settings › General › Tool history**, which also shows the space it takes and has **Clear history…**; older days are deleted as they fall out. `--no-persist` does not stop it — that flag is about the events log — and `--uninstall` leaves it, `--purge` too (see [Uninstall](#uninstall)).
347
+ Tool activity keeps its history in `tool-audit/`, in the same data directory: a file a day with a line per tool call — Claude Code or Codex, when it started and ended, whether it failed, and the tool's name or, for a shell command, its operation (`git push`, `npm run build`) — with a line per skill used, holding the skill's name, the session it ran in and the name of its project folder, and a `settings.json` holding how long to keep it, when it started counting, and a random id for this device that nothing sends anywhere. The rest of a command line, the tool's or the skill's arguments and output, and paths are not saved, nor a tool call's session id, and nothing in the folder leaves the machine. It keeps 90 days unless you pick 30 or 60 under **Keep history** in **Settings › Data › Tool history**, which also shows the space it takes and has **Clear history…**; older days are deleted as they fall out. `--no-persist` does not stop it — that flag is about the events log — and `--uninstall` leaves it, `--purge` too (see [Uninstall](#uninstall)).
331
348
 
332
349
  What does go out is short and ordinary: a ~20-byte version check against the npm registry (plus one small request to confirm a version it has not seen before), installs and daily version checks for the two tools the deck manages (claude-swap from PyPI, ccusage from npm), on an Apple Silicon Mac whose sensors stay silent one release lookup and one binary download from GitHub for `macmon`, and, while the page is open or one of the two quota notifications is on, quota reads to Anthropic and OpenAI signed with your own credentials — that is where those numbers live. While the page is open, and while the desktop app has a session running or waiting, it also reads Anthropic's and OpenAI's public status pages (`status.claude.com`, `status.openai.com`), unsigned, about every three minutes, and once a minute at most while a page is not answering, so an outage on their side shows as a chip in the topbar instead of looking like a fault on yours. While the machine panel is open, on a machine with Claude Code, it times the way to Claude every thirty seconds — a DNS lookup of `api.anthropic.com` and a TCP handshake on port 443, with nothing sent over it — for the panel's latency reading. While the Browser Watch panel is open, it looks up the address of Claude's browser relay, `bridge.claudeusercontent.com`, about every thirty seconds — a DNS query to your resolver — to tell whether a running browser holds a connection to it. The [desktop app](#desktop-app) also asks this repository's GitHub releases for its own updates. With **Local network** on, which it is unless you switch it off, the deck also announces itself to other decks on your local network over UDP (port 45317) and pairs with the ones that answer — see [Local network](#local-network). The deck also sends usage reports to `api.ccdeck.dev`: an install event (naming the ccdeck.dev page the command was copied from, when the command says so with `--ref`, which the site's copy buttons add; a page name and nothing else, sent once), an update event (saying how the new version arrived: the deck's own update, the desktop app's updater, npx, npm or a source checkout), one "active" a day, one "uninstall" when you run `ccdeck --uninstall` (which asks, at a terminal, one skippable question about why, from a short list), one "activated" when a new install's first session arrives (how long after the install, in rough buckets such as "under five minutes", and whether it was a Claude or a Codex session), one "rated" if you answer the one question the deck asks about itself after about a week of use — "How useful is ccdeck to you?", 0 to 10, which you can skip — with the number you picked and how many days you had used the deck, as a range (nothing is sent for a question you put off or leave), and the errors it runs into, with every path outside ccdeck's own code, project folder names, the machine name in every web address, every IP address and the machine name a network error carries (the loopback, such as `127.0.0.1:4317`, stays, so a local port error still reads as one), email addresses and keys scrubbed out (a line of ccdeck's own code keeps its place in the package, such as `ccdeck/src/server/tail.mjs:42`, or in the page, such as `<deck>/assets/index-abc.js:1:2345`, so the error still says where it happened). Each carries the version, your OS and CPU architecture, the channel (desktop, npm or a source checkout), your IP address and the machine's rough shape — the number of CPU cores, the amount of RAM, the locale, the shell, the terminal, and the Claude Code and Codex versions. The install, update, active, activated, rated and uninstall reports also carry the runtime and a device fingerprint (a stable hashed machine id); an error report carries neither. The first four also say whether the Claude hooks went in and Codex is being watched, with the daily "active" also carrying how many sessions, subagents and projects you ran on the last day you used the deck, that day's date, and which of the deck's own features you used that day — fixed names such as "usage history" or "account switch", a yes for each and nothing about what you did with them — plus how the deck held up and what it runs with: how long this run took to start, that day's peak memory and how many events it took in, each as a rough bucket such as "under two seconds" or "256–512 MB", the Claude and Codex plan as its category only (such as "Max 5x" or "Plus", read from the file each CLI keeps beside its login, and nothing else from it), and how many Claude accounts and paired machines the deck holds, as counts. The fingerprint is a one-way hash of stable machine traits, never those traits in the clear; everything else is coarse counts and short tokens — no path, no project name, no free text. These reports are no longer anonymous: the install id is random, but the fingerprint follows this machine and the server records the IP the report arrives from. What still never leaves is your sessions, prompts, files, project names and paths. The deck has no switch for them; `AGENTS_DECK_NO_REPORTS=1` keeps them off from the first start. While the deck runs it also sends a tiny heartbeat every few minutes, the install id and nothing else, so the count of decks running right now, and how many hours a day a deck stays open, can be seen. **Send feedback** (the topbar's Feedback button; on a phone, under More) sends what you write, and any screenshots and contact you add, to the same place, with the ccdeck version and your system (such as `darwin-arm64`), which the dialog names before you send, and, while the usage reports are on, the same device fingerprint they carry, which it does not show; `AGENTS_DECK_NO_REPORTS=1` keeps the fingerprint home and the feedback still goes. As with a report, the server records the IP address it arrives from. The people who make ccdeck read it there and may open a public issue from it, never with the contact you gave or your screenshots. `AGENTS_DECK_NO_INSTALL=1` turns off everything but the quota reads; `AGENTS_DECK_NO_DOWNLOAD=1` is the narrower version — no `uv` binary is fetched, the managed installs stay.
333
350
 
@@ -353,19 +370,20 @@ The Accounts panel reads the store [claude-swap](https://github.com/realiti4/cla
353
370
 
354
371
  The deck installs that package itself, so it installs it **bounded**: `claude-swap~=0.26`, which is `>= 0.26, == 0.*`, and when PyPI can be reached the exact version it resolved rather than the range. What `cswap --version` reports afterwards has to be that version, or the deck says so and leaves the panel dark rather than driving a copy it cannot account for — this is the tool that holds your Claude logins. The daily upgrade is bounded by the same specifier, and what it left behind is written to `~/.agents-deck/cswap-upgrade.json`.
355
372
 
356
- **Switch** on an account's row makes it the active one. New sessions start on it; sessions already running are not restarted, and pick it up on their next message — up to about 30 seconds later on macOS. **Auto-switch**, off until you turn it on, does the same without a press: about once a minute the deck runs claude-swap's own engine, which, once the active account passes your threshold (90% unless you choose another) of its 5-hour or 7-day window, moves Claude Code to the account with the most room left. Every account keeps its own limits; when no other account is under the threshold, nothing switches. **Custom…**, last in the threshold picker, takes any whole percent from 50 to 99 and refuses anything else under the field without saving it; the threshold is claude-swap's own setting, kept on this machine.
373
+ **Switch** on an account's row makes it the active one. New sessions start on it; sessions already running are not restarted, and pick it up on their next message — up to about 30 seconds later on macOS. Because every session on the machine shares that one login, the deck will not switch to an account whose saved login claude-swap has already seen refused: it asks claude-swap to read the account first when a read is due, and if the login has expired the row says so, with **Sign in again**, and nothing moves. When it cannot tell — offline, or claude-swap does not answer in time — the switch goes ahead. **Auto-switch**, off until you turn it on, does the same without a press: about once a minute the deck runs claude-swap's own engine, which, once the active account passes your threshold (90% unless you choose another) of its 5-hour or 7-day window, moves Claude Code to the account with the most room left. Every account keeps its own limits; when no other account is under the threshold, nothing switches. Auto-switch also moves Claude Code off an account whose login has expired, once Claude Code has signed it out and claude-swap's stored copy of it no longer works either, to the account with the most room under the threshold that claude-swap has not set aside and no local `cswap run` session is using; the expired one stays in the list, waiting for you to sign it in again. When Claude Code sessions stop on an expired login or a usage limit, Auto-switch runs once right away instead of waiting for its next minute, and the cards say `Stopped · login expired` or `Stopped · usage limit` until the session does something again: type `continue` in each one once a working login is active. A session running in the background (`claude --bg`, agent view) gets **Continue** on its card instead, enabled once the active account's usage reading shows a working login with room since the stop: it runs Claude Code's own [`claude stop` and `claude --resume <session> --bg`](https://code.claude.com/docs/en/sessions) in the session's folder, and the card says how that went. **Continue stopped background sessions**, in **Settings › General**, off until you turn it on, presses it for you, once per stop; a session that stops again right after is left for you. **Custom…**, last in the threshold picker, takes any whole percent from 50 to 99 and refuses anything else under the field without saving it; the threshold is claude-swap's own setting, kept on this machine.
357
374
 
358
375
  Two choices change where Auto-switch goes, and do nothing while it is off. **Next account**, under the switch, is **Most room left** — claude-swap's own pick, and the default — or **Soonest weekly reset**: the account whose 7-day window resets soonest, as long as that is within 95 hours, so quota that is about to reset is used first. With it, the deck also moves below the threshold, to an account that resets at least an hour sooner than the one in use, and never sooner than half an hour after the last move. **Set as home**, in an account's **⋯**, marks the row with ⌂: Auto-switch takes you there, does not move you off it before the threshold, and comes back once its 5-hour window has reset. Only an account comfortably under the threshold, read in the last 15 minutes, signed in, in rotation, not set aside by claude-swap and not in use in a local `cswap run` session is moved to. An account reported as active on another paired device remains eligible for Home and Soonest weekly reset. If Home has stale readings, Auto-switch asks claude-swap to collect before choosing a weekly alternative; if Home is still unavailable afterward, it uses the alternative. A switch you make yourself is left alone until that account reaches the threshold, or until you set a home or pick a rule again. When no account fits either rule, claude-swap decides, as before. Both choices are kept on this machine.
359
376
 
360
377
  **Account capacity**, the gauge in the panel's header, says how many accounts are ready and what their 5-hour and 7-day windows have left. Its **Reset calendar** view lays out today and the next six days, with each account's 7-day reset on the day it falls, and marks the readings that need refreshing.
361
378
 
362
- **Account notifications** are three switches in **Settings › Notifications** (the gear in the topbar, or `Cmd+,`; `Ctrl+,` on Windows and Linux), each on its own and none of them tied to **Notifications while closed**: they are sent whether the deck is open or not, because nothing on the page says them. The desktop app shows them as ccdeck, without a sound.
379
+ **Account notifications** are four switches in **Settings › Notifications** (the gear in the topbar, or `Cmd+,`; `Ctrl+,` on Windows and Linux), each on its own and none of them tied to **Notifications while closed**: they are sent whether the deck is open or not, because nothing on the page says them. The desktop app shows them as ccdeck, without a sound.
363
380
 
364
- - **Account auto-switched**, on unless you turn it off. When Auto-switch moves Claude Code to another account, one notification says where to and, when the engine's own reading shows it, why: `Switched to work · personal@example.com reached 90%`, `Back on work · your home account` or `Switched to side · weekly limit resets in 1d 7h`, with an account's alias when it has one. A switch you make yourself is not announced.
381
+ - **Account auto-switched**, on unless you turn it off. When Auto-switch moves Claude Code to another account, one notification says where to and, when the engine's own reading shows it, why: `Switched to work · personal@example.com reached 90%`, `Back on work · your home account` or `Switched to side · weekly limit resets in 1d 7h`, with an account's alias when it has one. A switch away from an expired login says so: `Switched to work · personal@example.com's login expired, sign in again to use it`; and when no other account is ready to take over, that is said once rather than every minute. A switch you make yourself is not announced.
365
382
  - **Quota at 90% and 100%**, off until you turn it on. When a window of the Claude or Codex account you are on reaches 90%, and once more at 100%: Claude's 5-hour, 7-day and per-model windows, and every Codex window the Usage panel shows, the extra limits included — `5-hour usage reached 90% · resets in 2h 14m`. Only the account in use is watched, not every account you have stored. When reaching it is what makes Auto-switch move the account, the switch notification says it and this one is not sent, unless the switch notification is off.
366
383
  - **Quota reset**, off until you turn it on. When one of those windows that had reached 90% starts over: `5-hour window available again`. A window that never got that high resets without a word.
384
+ - **Sessions stopped on login or limit**, on unless you turn it off. When Claude Code sessions stop on an expired login or a usage limit, one notification for all of them says how many stopped and to type `continue` in each. When the Auto-switch run that follows moves the account, the switch notification says it instead, so one incident is one notification.
367
385
 
368
- Each is said once per window. The deck writes what it has said to `account-notify.json`, beside its settings, so a restart does not say it again, and nothing said about one account counts for another. With either quota switch on, the deck reads the quota of the accounts in use about every two minutes even while no page is open — the same reads, through the same caches and limits, as the Usage panel's. `AGENTS_DECK_NO_NOTIFY=1` keeps all three off.
386
+ Each is said once per window. The deck writes what it has said to `account-notify.json`, beside its settings, so a restart does not say it again, and nothing said about one account counts for another. With either quota switch on, the deck reads the quota of the accounts in use about every two minutes even while no page is open — the same reads, through the same caches and limits, as the Usage panel's. `AGENTS_DECK_NO_NOTIFY=1` keeps all four off.
369
387
 
370
388
  **`+` → Sign in** runs `claude auth login`, shows you the link, and hands the result to `cswap add`. The sign-in usually completes by itself once you approve it in the browser; only when the page shows a code — as it does on a deck opened from another machine — is there one to paste back into the dialog. The account you were using **stays active** — signing in replaces the live credentials, so the previous one is switched back the moment the new one is recorded. A pasted code goes straight into the CLI's stdin on this machine; it is never stored, logged, or sent anywhere else.
371
389
 
@@ -380,10 +398,36 @@ An import adds what is missing and leaves a working account exactly as it is. Th
380
398
  > [!WARNING]
381
399
  > A share carries the **live login of every account in it, in the clear** — claude-swap's export format has no encryption, and five ticked boxes is five passwords on your clipboard. It expires ten minutes after it is made and imports refuse it after that. While it lives, treat it as those passwords: anything that can read your clipboard can read the accounts.
382
400
 
383
- **Rename**, **Move to slot…** and **Remove** are on the same row menu. Removal takes two clicks and cannot be undone.
401
+ **Rename**, **Move to slot…** and **Remove** are on the same row menu. Removal takes two clicks and cannot be undone. Removing the active account leaves Claude Code signed in as it; when that login is the expired one, the deck moves Claude Code to the working account with the most room left that claude-swap has not set aside and no local `cswap run` session is using, or says that none could take over.
384
402
 
385
403
  Step by step: [Switch between multiple Claude Code accounts](https://ccdeck.dev/guides/claude-accounts/) · [Copy your Claude Code login to another machine](https://ccdeck.dev/guides/move-an-account/)
386
404
 
405
+ ## From the terminal
406
+
407
+ The Accounts panel and the session list answer from any terminal on the machine too, which is the whole of what you have when you reach it over SSH:
408
+
409
+ ```
410
+ ccdeck accounts every Claude account, its 5h / 7d usage and resets
411
+ ccdeck switch work move the live login: a slot number, an email, an alias,
412
+ or any part of one that names a single account
413
+ ccdeck switch --best where Auto-switch would go: your home account or the soonest
414
+ weekly reset when you set one, else the most room left
415
+ ccdeck switch pick from a list (arrows, Enter; Esc cancels)
416
+ ccdeck usage the active account's limits, every lane
417
+ ccdeck sessions what each session is doing, the ones waiting on you first
418
+ ccdeck open the deck in your browser — over SSH, the line that opens it on yours
419
+ ```
420
+
421
+ `swap` and `use` are other names for `switch`, `ls` for `accounts` and `ps` for `sessions`; `status`, `stop` and `logs` are `--status`, `--stop` and `--logs`. `ccdeck swap 1 2` does not exchange two slots the way claude-swap's `cswap swap` does — it says so and points you there.
422
+
423
+ **The commands ask the deck that is running.** A switch from the terminal goes through the same route as **Switch** on a row, so it waits for the same lock as the panel, Auto-switch and a sign-in in flight, the panel shows it at once, and it lands in the Projects report's history. With no deck running, `accounts`, `switch` and `usage` read and drive claude-swap's store directly; `sessions` and `open` need a deck, since sessions are what a deck watches. After a switch, the command says how many running Claude sessions move with it — all of them, from their next message.
424
+
425
+ **Over SSH**, `ccdeck open` prints the line to run on your own computer, `ssh -N -o ExitOnForwardFailure=yes -L 14317:127.0.0.1:4317 you@that-machine` (with `-p` when sshd is not on 22), and the deck then opens at `http://127.0.0.1:14317` in your own browser. Your end is not 4317, so it cannot land on a deck of your own. The deck still listens only on 127.0.0.1; the tunnel is your SSH connection.
426
+
427
+ **For scripts**, every command takes `--json` — one object on stdout with `schemaVersion` and `ok` — and `--port <n>` to name a deck. Nothing prompts without a terminal: `ccdeck switch` with no account named and no terminal is an error, not a wait. Exit codes are 0 done, 1 failed, 2 the command line was wrong (an account nothing matches, a misspelled command), and 3 no deck is running.
428
+
429
+ A misspelled command is refused — `ccdeck acounts` suggests `accounts` and starts nothing — so a typo over SSH never leaves a deck behind.
430
+
387
431
  ## Local network
388
432
 
389
433
  A Claude login expires on the machine that has not used it for a while, and stays alive on the one that has. With two or three machines that is a `claude login` a week, each time on the machine you are not sitting at. **Local network** is the last section of the Claude accounts panel, and it is that chore done for you: decks on one network find each other, pair, and a login that has expired on one is copied back from another that still has it.
@@ -415,6 +459,8 @@ Four pictures, which are also the guide the section opens from `See how it works
415
459
 
416
460
  **What is shared, and with whom.** Nothing until you tick a login, and then only that login, and only with paired decks. A login ticked on one deck arrives on every deck paired with it: added where it is missing, repaired where it has expired, and never in place of one that still works. The deck asks the machines it finds, and a machine that is asked waits for somebody there to press `accept` unless its `Say yes to every deck that asks` switch, in the same dialog, is on. That switch starts on, so your own machines pair without a press. It also means a deck the switch pairs can add the logins ticked on it to this machine, and Auto-switch, if it is on, may later move Claude Code to one of them — so on a network you do not own, turn the switch off. What still takes a person is the tick: a paired deck is handed nothing until somebody ticks a login here. A login that arrives from a deck somebody here accepted or paired by invite is ticked here too, so this machine can repair the next one; a deck the switch paired is handed only the logins a person ticked on this deck, never the ones that were ticked by arriving, so untick and tick such a login again to hand it to those decks as well — and what a deck the switch paired brings here is not ticked here at all. What crosses the wire is the same live credential a share carries, sealed to the deck it is addressed to, so treat the pairing decision as the moment that matters. Unpairing stops future rounds; a login already copied stays where it went.
417
461
 
462
+ **The login a machine is signed in to stays on that machine** while it is in use there, ticked or not. A Claude login renews once, so two machines on one copied login race at each renewal and the one that renews second is signed out (“Login expired”). The dialog marks it **in use here · not sent** when it is ticked, a paired deck reads it as **in use there** (or does not see it, while this deck hides which account it is on), and it goes out like any other ticked login as soon as this machine switches to another account. A paired deck that already holds a copy of that login, from before this machine switched to it, can still renew it first and sign this machine out. To hand it out anyway, turn on **Send the login this deck is using too** in the same dialog.
463
+
418
464
  On a Mac, **cannot share here** is different from **expired here**. It means this ccdeck process cannot read the Keychain copy, so that machine is not used as a credential source and another deck does not keep trying to “repair” the same healthy slot every minute. A LAN import is checked after it lands too; if the receiving Mac still cannot read the Keychain, the round reports that problem instead of claiming the login was repaired.
419
465
 
420
466
  Step by step: [Repair expired Claude Code logins across machines](https://ccdeck.dev/guides/local-network/)
@@ -423,8 +469,10 @@ Step by step: [Repair expired Claude Code logins across machines](https://ccdeck
423
469
 
424
470
  ```
425
471
  ccdeck [options]
472
+ ccdeck <command> [options] accounts, switch, usage, sessions, open — see From the terminal
426
473
 
427
- -p, --port <number> Preferred port (default: 4317; fallback: random 4318–4400)
474
+ -p, --port <number> Preferred port (default: 4317; fallback: random 4318–4400);
475
+ with a command, the deck to ask
428
476
  --no-open Don't open the browser automatically
429
477
  --foreground Hold the terminal, the way every version before 3.20
430
478
  did (Ctrl+C stops the deck again)
@@ -460,13 +508,16 @@ ccdeck [options]
460
508
  --uninstall Remove ccdeck's hooks from settings files, and name
461
509
  the files that still hold this deck's private key
462
510
  --purge With --uninstall: delete those files too
511
+ --json With a command: one object on stdout, for scripts
512
+ --best With `switch`: the account Auto-switch would pick
463
513
  -h, --help Show this help
464
514
  -v, --version Print the version and exit
465
515
  ```
466
516
 
467
- Anything else on the command line is reported as an unknown option and then
468
- ignored — the deck still starts, so a typo costs you a warning rather than a
469
- dashboard.
517
+ A word in first position that is not a command — [From the terminal](#from-the-terminal)
518
+ lists them — is refused, so `ccdeck acounts` never starts a deck. Anything else
519
+ on the command line is reported as an unknown option and then ignored — the deck
520
+ still starts, so a typo costs you a warning rather than a dashboard.
470
521
 
471
522
  `--foreground` is there for anything that was relying on the old behaviour — a
472
523
  wrapper script, a CI step, a supervisor of your own that starts the deck and
@@ -558,6 +609,7 @@ Environment:
558
609
  | `AGENTS_DECK_CSWAP` | Full path to `cswap`, when it lives somewhere unusual |
559
610
  | `AGENTS_DECK_CLAUDE` | Full path to the `claude` CLI |
560
611
  | `AGENTS_DECK_CCUSAGE` | Full path to your own `ccusage`, used ahead of everything else |
612
+ | `AGENTS_DECK_SKILL_HOOK=1` | Add the `UserPromptExpansion` hook, so Tool activity's Skills tab counts the slash commands you type — only where every Claude Code found is 2.1.116 or newer. Off by default, and only `1` turns it on |
561
613
  | `CLAUDE_SWAP_BACKUP` | Override the claude-swap store root the Accounts panel reads |
562
614
  | `CCDECK_HOME` | Put everything the deck writes — its state and its log — under this directory instead of the platform default. Wins over every rule below it |
563
615
  | `CLAUDE_CONFIG_DIR` | Override `~/.claude` — the hook entry, the event log and everything else the deck writes move with it |
@@ -603,14 +655,16 @@ platform's **data** directory, and the platform's **log** directory.
603
655
  `prefs.json` is the one to delete deliberately: it holds this deck's **Local
604
656
  network private key**, the one every deck you paired with has pinned. Leaving it
605
657
  behind leaves that identity on the machine. (`~/.agents-deck/` holds the managed
606
- tools and the deck's records about them, and `radar-monitor-<port>.json` once
607
- Telemetry Radar has monitored — the destinations it resumes — but no key.)
658
+ tools and the deck's records about them, `radar-monitor-<port>.json` once
659
+ Telemetry Radar has monitored — the destinations it resumes — and
660
+ `radar-settings-<port>.json` once its automatic monitoring has been switched,
661
+ but no key.)
608
662
 
609
663
  The data directory also holds Tool activity's `tool-audit/` folder — up to 90
610
664
  days of which tools and commands your agents ran, and how long they took — and
611
665
  `quota-history.json`. Neither holds a key, so `--uninstall` leaves both, and so
612
666
  does `--purge` below, which deletes only the files that can hold one. **Clear
613
- history…** under **Settings › General › Tool history** empties the tool history
667
+ history…** under **Settings › Data › Tool history** empties the tool history
614
668
  while the deck runs; deleting the folder removes it.
615
669
 
616
670
  `uv tool uninstall claude-swap` (or `pipx uninstall claude-swap`) removes the
@@ -70,7 +70,7 @@ arrives with its own licence beside it. They are named for completeness, so that
70
70
  "what ccdeck ships" and "what ccdeck can run" are not confused. Every one of them
71
71
  is off under `AGENTS_DECK_NO_INSTALL=1`.
72
72
 
73
- ## Bundled packages (26)
73
+ ## Bundled packages (27)
74
74
 
75
75
  | Package | Version | Licence | Copyright |
76
76
  | --- | --- | --- | --- |
@@ -92,6 +92,7 @@ is off under `AGENTS_DECK_NO_INSTALL=1`.
92
92
  | `dagre` | 0.8.5 | MIT | Copyright (c) 2012-2014 Chris Pettitt |
93
93
  | `graphlib` | 2.1.8 | MIT | Copyright (c) 2012-2014 Chris Pettitt |
94
94
  | `hls.js` | 1.7.3 | Apache-2.0 | Copyright (c) 2017 Dailymotion (http://www.dailymotion.com) |
95
+ | `json-edit-react` | 2.0.4 | MIT | Copyright (c) 2024 Carl Smith |
95
96
  | `lodash` | 4.18.1 | MIT | Copyright OpenJS Foundation and other contributors <https://openjsf.org/> |
96
97
  | `react` | 18.3.1 | MIT | Copyright (c) Facebook, Inc. and its affiliates. |
97
98
  | `react-dom` | 18.3.1 | MIT | Copyright (c) Facebook, Inc. and its affiliates. |
@@ -109,12 +110,16 @@ for more than a notice — a copy of the licence itself travels with the work
109
110
  and it is compatible with version 3 of the GNU licences, which is the version
110
111
  ccdeck is under.
111
112
 
112
- `hls.js` is the one entry that is not in the page every visit loads. It is its
113
- own chunk, `dist/web/assets/hls-*.js`, fetched only when somebody plays an HLS
114
- custom station in a browser without native HLS (#1208) — but that chunk is in
115
- `dist/web` like every other, and so in the tarball. None of these licences is
116
- changed by ccdeck's relicensing, and no copyleft obligation flows back to
117
- their authors.
113
+ `hls.js` and `json-edit-react` are the two entries that are not in the page
114
+ every visit loads. `hls.js` is its own chunk, `dist/web/assets/hls-*.js`,
115
+ fetched only when somebody plays an HLS custom station in a browser without
116
+ native HLS (#1208). `json-edit-react` is the JSON tree in Radar's event
117
+ inspector and is compiled into Radar's chunk,
118
+ `dist/web/assets/TrafficRadar-*.js`, fetched only when somebody opens Radar; it
119
+ adds its own stylesheet from that JavaScript the first time it renders, so it
120
+ has no CSS file of its own. Both chunks are in `dist/web` like every other, and
121
+ so in the tarball. None of these licences is changed by ccdeck's relicensing,
122
+ and no copyleft obligation flows back to their authors.
118
123
 
119
124
  ### Declared but not emitted (3)
120
125
 
@@ -655,6 +660,37 @@ If the Work includes a "NOTICE" text file as part of its distribution, then any
655
660
  END OF TERMS AND CONDITIONS
656
661
  ```
657
662
 
663
+ ### 15. `json-edit-react` 2.0.4
664
+
665
+ Reproduced verbatim from `node_modules/json-edit-react/LICENSE`:
666
+
667
+ ```text
668
+ MIT License
669
+
670
+ Copyright (c) 2024 Carl Smith
671
+
672
+ Permission is hereby granted, free of charge, to any person obtaining a copy
673
+ of this software and associated documentation files (the "Software"), to deal
674
+ in the Software without restriction, including without limitation the rights
675
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
676
+ copies of the Software, and to permit persons to whom the Software is
677
+ furnished to do so, subject to the following conditions:
678
+
679
+ The above copyright notice and this permission notice shall be included in all
680
+ copies or substantial portions of the Software.
681
+
682
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
683
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
684
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
685
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
686
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
687
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
688
+ SOFTWARE.
689
+ ```
690
+
691
+ Source: https://github.com/CarlosNZ/json-edit-react. The package has no
692
+ dependencies of its own; React, its one peer, is §1.
693
+
658
694
  ## Radio world map
659
695
 
660
696
  The radio map includes simplified country geometry from Natural Earth