chatdeck 0.1.0__tar.gz

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 (31) hide show
  1. chatdeck-0.1.0/LICENSE +21 -0
  2. chatdeck-0.1.0/PKG-INFO +164 -0
  3. chatdeck-0.1.0/README.md +146 -0
  4. chatdeck-0.1.0/pyproject.toml +34 -0
  5. chatdeck-0.1.0/setup.cfg +4 -0
  6. chatdeck-0.1.0/src/chatdeck/__init__.py +2 -0
  7. chatdeck-0.1.0/src/chatdeck/__main__.py +3 -0
  8. chatdeck-0.1.0/src/chatdeck/autostart.py +91 -0
  9. chatdeck-0.1.0/src/chatdeck/cli.py +152 -0
  10. chatdeck-0.1.0/src/chatdeck/config.py +34 -0
  11. chatdeck-0.1.0/src/chatdeck/demo.py +141 -0
  12. chatdeck-0.1.0/src/chatdeck/desktop.py +93 -0
  13. chatdeck-0.1.0/src/chatdeck/hooks.py +131 -0
  14. chatdeck-0.1.0/src/chatdeck/server.py +345 -0
  15. chatdeck-0.1.0/src/chatdeck/static/index.html +342 -0
  16. chatdeck-0.1.0/src/chatdeck/static/logo-192.png +0 -0
  17. chatdeck-0.1.0/src/chatdeck/static/logo-512.png +0 -0
  18. chatdeck-0.1.0/src/chatdeck/static/logo.ico +0 -0
  19. chatdeck-0.1.0/src/chatdeck/static/logo.svg +13 -0
  20. chatdeck-0.1.0/src/chatdeck/terminals.py +218 -0
  21. chatdeck-0.1.0/src/chatdeck/transcripts.py +216 -0
  22. chatdeck-0.1.0/src/chatdeck/usage.py +96 -0
  23. chatdeck-0.1.0/src/chatdeck/windows/bubble.ps1 +303 -0
  24. chatdeck-0.1.0/src/chatdeck/windows/focus_wt.ps1 +68 -0
  25. chatdeck-0.1.0/src/chatdeck/windows/start.ps1 +35 -0
  26. chatdeck-0.1.0/src/chatdeck.egg-info/PKG-INFO +164 -0
  27. chatdeck-0.1.0/src/chatdeck.egg-info/SOURCES.txt +29 -0
  28. chatdeck-0.1.0/src/chatdeck.egg-info/dependency_links.txt +1 -0
  29. chatdeck-0.1.0/src/chatdeck.egg-info/entry_points.txt +2 -0
  30. chatdeck-0.1.0/src/chatdeck.egg-info/top_level.txt +1 -0
  31. chatdeck-0.1.0/tests/test_chatdeck.py +343 -0
chatdeck-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Abdifatah
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: chatdeck
3
+ Version: 0.1.0
4
+ Summary: A local dashboard for your Claude Code chats: what's running, what needs you, and what it costs.
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/Abdifatah09/chatdeck
7
+ Project-URL: Issues, https://github.com/Abdifatah09/chatdeck/issues
8
+ Keywords: claude,claude-code,dashboard,ai,cli
9
+ Classifier: Environment :: Console
10
+ Classifier: Environment :: Web Environment
11
+ Classifier: Operating System :: MacOS
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Python :: 3
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Dynamic: license-file
18
+
19
+ # chatdeck
20
+
21
+ A local dashboard for your Claude Code chats: see which chats are running and which need you,
22
+ read and search old chats, track usage against your plan limits, and clean up transcripts.
23
+ Python 3.9+ standard library only, no dependencies.
24
+
25
+ ![A tour of chatdeck: a chat waiting for permission, chats grouped by project, searching and reading an old chat, and the light theme](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/media/tour.gif)
26
+
27
+ ## Features
28
+
29
+ - **Running now** – every live session with its context size and cost. Sessions stuck on a
30
+ permission prompt or a question get an amber **Waiting for you** badge and an optional chime.
31
+ **Open** jumps to the chat's terminal tab, **Rename** runs `/rename` in it so Claude names it after what it's about (double-click the name to type your own), and big chats get a
32
+ **Compact** button that runs `/compact`.
33
+ - **Saved chats** – read any chat without resuming it, search across all messages,
34
+ rename (so you know what's in it), export to Markdown, resume in a terminal, delete.
35
+ - **Usage** – tokens today and this week, top projects, 24h cost, and your 5-hour and weekly
36
+ limits with a forecast of when you'd hit them at the current pace.
37
+ - **Cleanup** – delete empty chats or chats older than 7, 30 or 90 days. Shows disk usage.
38
+ Running chats are never deleted.
39
+ - **By project** view: one section per project (git repo, including its worktrees and subfolders)
40
+ with its running and saved chats, and a **New chat** button that starts Claude Code in that project. Sort by recent, size, cost or messages. Light and dark themes.
41
+ Keyboard shortcuts: `/` search, `j`/`k` move, `Enter` read, `r` resume, `e` rename, `x` select, `Del` delete, `?` help.
42
+ - **Windows corner button and tray icon** – pop the dashboard up from the corner of the screen.
43
+
44
+ ## Install
45
+
46
+ ```sh
47
+ pipx install chatdeck # once it's on PyPI; until then:
48
+ pipx install git+https://github.com/Abdifatah09/chatdeck
49
+ ```
50
+
51
+ No pip? Clone the repo and run `./install.sh`. It puts a `chatdeck` command in `~/.local/bin`
52
+ that runs straight from the clone.
53
+
54
+ ## Try it first
55
+
56
+ ```sh
57
+ chatdeck demo
58
+ ```
59
+
60
+ opens chatdeck on a set of made-up chats (in a temporary folder, deleted when you stop it with Ctrl+C),
61
+ so you can click around without touching your own. The screenshots here are from the demo.
62
+
63
+ ## Use
64
+
65
+ ```sh
66
+ chatdeck # start the dashboard and open it
67
+ chatdeck setup # add the Claude Code hooks for plan limits and "waiting for you"
68
+ chatdeck autostart on # start it at login (Windows via WSL, macOS, Linux)
69
+ chatdeck bubble # Windows: the corner button and tray icon
70
+ chatdeck demo # try it with made-up chats
71
+ chatdeck stop # stop the background server
72
+ chatdeck uninstall # take the hooks and autostart out again
73
+ ```
74
+
75
+ `chatdeck setup` keeps your existing status line: it saves the usage data first, then runs
76
+ yours, so your status line looks the same. `chatdeck uninstall` puts it back, and your previous
77
+ settings are backed up in `~/.chatdeck/backups`.
78
+
79
+ ## Privacy and security
80
+
81
+ - Everything stays on your machine. chatdeck reads the transcripts Claude Code already writes to
82
+ `~/.claude/projects` and sends nothing anywhere: no accounts, no telemetry, no network calls
83
+ except to itself.
84
+ - The server binds to 127.0.0.1 only. Every API call needs a token that changes each run, sent in a
85
+ custom header, so other websites open in your browser can't call it.
86
+ - It only changes things you ask it to: deleting a chat removes its transcript (running chats are
87
+ never deleted), renaming adds a title line to it, and Rename and Compact on a running chat type
88
+ `/rename` or `/compact` into that chat's terminal, only while it's idle.
89
+ - `chatdeck setup` edits `~/.claude/settings.json` and backs it up first; `chatdeck uninstall` undoes it.
90
+
91
+ ## See it in action
92
+
93
+ | Find any chat by what was said in it | Every project in one place |
94
+ |---|---|
95
+ | ![searching for "upgrade" and opening the matching chat](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/media/search.gif) | ![switching to the by-project view, folding a project and the New chat button](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/media/projects.gif) |
96
+
97
+ | Keyboard only: `j`/`k`, `Enter`, `e` to rename, `?` for help | Running chats, at a glance |
98
+ |---|---|
99
+ | ![moving through saved chats, reading one and renaming it from the keyboard](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/media/keyboard.gif) | ![three running chats: one waiting for permission, one working, one getting big](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/media/running.png) |
100
+
101
+ ## Screenshots
102
+
103
+ ![chatdeck showing three running chats, one waiting for permission, plan limits and a week of usage](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/dashboard.png)
104
+
105
+ | By project | Reading a chat |
106
+ |---|---|
107
+ | ![chats grouped by project](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/by-project.png) | ![a saved chat open in the viewer](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/viewer.png) |
108
+
109
+ | Light theme | The Windows pop-up |
110
+ |---|---|
111
+ | ![the light theme](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/light.png) | ![the pop-up from the corner button](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/popup.png) |
112
+
113
+ ## Windows corner button and tray icon
114
+
115
+ A small always-on-top button sits in the bottom-left corner of the screen. Click it and the
116
+ dashboard pops up above it; click again, or anywhere else, and it tucks away. The badge counts
117
+ running chats and turns amber, with a pulsing ring, when one is waiting for you. Drag it to move
118
+ it; right-click to hide it or for more. The same badge sits in the notification area by the
119
+ clock, where a click pops the dashboard up in the bottom-right corner. It needs Chrome or Edge.
120
+ Start it with `chatdeck bubble`, or let `chatdeck autostart on` start it at login.
121
+
122
+ ## Terminals
123
+
124
+ **Resume**, **Open** and **Agents view** open or find a terminal for you:
125
+
126
+ | Platform | Opens chats in | Open on a running chat |
127
+ |---|---|---|
128
+ | WSL | Windows Terminal (a new window each time) | finds its tab on any virtual desktop and switches to it |
129
+ | macOS | Terminal, or iTerm2 if you run the dashboard from it | finds its tab by tty |
130
+ | Linux | gnome-terminal, konsole, kitty, alacritty, wezterm, xfce4-terminal, tilix, foot or xterm | finds its window with `wmctrl` or `xdotool` (X11) |
131
+
132
+ When no terminal can be opened, the dashboard copies the command to paste instead.
133
+ Finding a running chat works by briefly giving its tab a unique title, so it doesn't work for
134
+ tabs whose title is locked (Windows Terminal's `suppressApplicationTitle`).
135
+
136
+ ## Settings
137
+
138
+ Settings live in `~/.chatdeck/config.json`, and environment variables override them:
139
+
140
+ - `port` / `CHATDECK_PORT`: default 47123
141
+ - `terminal` / `CHATDECK_TERMINAL`: which terminal to use, e.g. `kitty` or `iterm`
142
+ - `tabs` / `CHATDECK_TABS` (WSL): `true` opens chats as tabs in your last Windows Terminal window instead of a new window each
143
+ - `vdlib` / `CHATDECK_VDLIB` (WSL): Windows path to [VirtualDesktopLib.cs](https://github.com/MScholtes/VirtualDesktop),
144
+ which makes switching to a chat on another virtual desktop reliable
145
+ - `CHATDECK_DIR`: where chatdeck keeps its data, default `~/.chatdeck`
146
+
147
+ ## Tests
148
+
149
+ ```sh
150
+ python3 -m unittest discover -s tests
151
+ ```
152
+
153
+ ## Screenshots and videos
154
+
155
+ Everything in `docs/` is recorded from `chatdeck demo` by a script, so it can be redone after UI changes
156
+ (needs Node; uses Segoe UI from `/mnt/c/Windows/Fonts` when run under WSL):
157
+
158
+ ```sh
159
+ cd scripts/promo && npm install && npx playwright-core install chromium-headless-shell
160
+ npm run record # or just some: npm run record -- shots tour search projects keyboard
161
+ ```
162
+
163
+ It writes the README screenshots to `docs/` and the GIFs, MP4s, framed screenshots and a 1280×640
164
+ social preview card to `docs/media/`.
@@ -0,0 +1,146 @@
1
+ # chatdeck
2
+
3
+ A local dashboard for your Claude Code chats: see which chats are running and which need you,
4
+ read and search old chats, track usage against your plan limits, and clean up transcripts.
5
+ Python 3.9+ standard library only, no dependencies.
6
+
7
+ ![A tour of chatdeck: a chat waiting for permission, chats grouped by project, searching and reading an old chat, and the light theme](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/media/tour.gif)
8
+
9
+ ## Features
10
+
11
+ - **Running now** – every live session with its context size and cost. Sessions stuck on a
12
+ permission prompt or a question get an amber **Waiting for you** badge and an optional chime.
13
+ **Open** jumps to the chat's terminal tab, **Rename** runs `/rename` in it so Claude names it after what it's about (double-click the name to type your own), and big chats get a
14
+ **Compact** button that runs `/compact`.
15
+ - **Saved chats** – read any chat without resuming it, search across all messages,
16
+ rename (so you know what's in it), export to Markdown, resume in a terminal, delete.
17
+ - **Usage** – tokens today and this week, top projects, 24h cost, and your 5-hour and weekly
18
+ limits with a forecast of when you'd hit them at the current pace.
19
+ - **Cleanup** – delete empty chats or chats older than 7, 30 or 90 days. Shows disk usage.
20
+ Running chats are never deleted.
21
+ - **By project** view: one section per project (git repo, including its worktrees and subfolders)
22
+ with its running and saved chats, and a **New chat** button that starts Claude Code in that project. Sort by recent, size, cost or messages. Light and dark themes.
23
+ Keyboard shortcuts: `/` search, `j`/`k` move, `Enter` read, `r` resume, `e` rename, `x` select, `Del` delete, `?` help.
24
+ - **Windows corner button and tray icon** – pop the dashboard up from the corner of the screen.
25
+
26
+ ## Install
27
+
28
+ ```sh
29
+ pipx install chatdeck # once it's on PyPI; until then:
30
+ pipx install git+https://github.com/Abdifatah09/chatdeck
31
+ ```
32
+
33
+ No pip? Clone the repo and run `./install.sh`. It puts a `chatdeck` command in `~/.local/bin`
34
+ that runs straight from the clone.
35
+
36
+ ## Try it first
37
+
38
+ ```sh
39
+ chatdeck demo
40
+ ```
41
+
42
+ opens chatdeck on a set of made-up chats (in a temporary folder, deleted when you stop it with Ctrl+C),
43
+ so you can click around without touching your own. The screenshots here are from the demo.
44
+
45
+ ## Use
46
+
47
+ ```sh
48
+ chatdeck # start the dashboard and open it
49
+ chatdeck setup # add the Claude Code hooks for plan limits and "waiting for you"
50
+ chatdeck autostart on # start it at login (Windows via WSL, macOS, Linux)
51
+ chatdeck bubble # Windows: the corner button and tray icon
52
+ chatdeck demo # try it with made-up chats
53
+ chatdeck stop # stop the background server
54
+ chatdeck uninstall # take the hooks and autostart out again
55
+ ```
56
+
57
+ `chatdeck setup` keeps your existing status line: it saves the usage data first, then runs
58
+ yours, so your status line looks the same. `chatdeck uninstall` puts it back, and your previous
59
+ settings are backed up in `~/.chatdeck/backups`.
60
+
61
+ ## Privacy and security
62
+
63
+ - Everything stays on your machine. chatdeck reads the transcripts Claude Code already writes to
64
+ `~/.claude/projects` and sends nothing anywhere: no accounts, no telemetry, no network calls
65
+ except to itself.
66
+ - The server binds to 127.0.0.1 only. Every API call needs a token that changes each run, sent in a
67
+ custom header, so other websites open in your browser can't call it.
68
+ - It only changes things you ask it to: deleting a chat removes its transcript (running chats are
69
+ never deleted), renaming adds a title line to it, and Rename and Compact on a running chat type
70
+ `/rename` or `/compact` into that chat's terminal, only while it's idle.
71
+ - `chatdeck setup` edits `~/.claude/settings.json` and backs it up first; `chatdeck uninstall` undoes it.
72
+
73
+ ## See it in action
74
+
75
+ | Find any chat by what was said in it | Every project in one place |
76
+ |---|---|
77
+ | ![searching for "upgrade" and opening the matching chat](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/media/search.gif) | ![switching to the by-project view, folding a project and the New chat button](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/media/projects.gif) |
78
+
79
+ | Keyboard only: `j`/`k`, `Enter`, `e` to rename, `?` for help | Running chats, at a glance |
80
+ |---|---|
81
+ | ![moving through saved chats, reading one and renaming it from the keyboard](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/media/keyboard.gif) | ![three running chats: one waiting for permission, one working, one getting big](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/media/running.png) |
82
+
83
+ ## Screenshots
84
+
85
+ ![chatdeck showing three running chats, one waiting for permission, plan limits and a week of usage](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/dashboard.png)
86
+
87
+ | By project | Reading a chat |
88
+ |---|---|
89
+ | ![chats grouped by project](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/by-project.png) | ![a saved chat open in the viewer](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/viewer.png) |
90
+
91
+ | Light theme | The Windows pop-up |
92
+ |---|---|
93
+ | ![the light theme](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/light.png) | ![the pop-up from the corner button](https://raw.githubusercontent.com/Abdifatah09/chatdeck/main/docs/popup.png) |
94
+
95
+ ## Windows corner button and tray icon
96
+
97
+ A small always-on-top button sits in the bottom-left corner of the screen. Click it and the
98
+ dashboard pops up above it; click again, or anywhere else, and it tucks away. The badge counts
99
+ running chats and turns amber, with a pulsing ring, when one is waiting for you. Drag it to move
100
+ it; right-click to hide it or for more. The same badge sits in the notification area by the
101
+ clock, where a click pops the dashboard up in the bottom-right corner. It needs Chrome or Edge.
102
+ Start it with `chatdeck bubble`, or let `chatdeck autostart on` start it at login.
103
+
104
+ ## Terminals
105
+
106
+ **Resume**, **Open** and **Agents view** open or find a terminal for you:
107
+
108
+ | Platform | Opens chats in | Open on a running chat |
109
+ |---|---|---|
110
+ | WSL | Windows Terminal (a new window each time) | finds its tab on any virtual desktop and switches to it |
111
+ | macOS | Terminal, or iTerm2 if you run the dashboard from it | finds its tab by tty |
112
+ | Linux | gnome-terminal, konsole, kitty, alacritty, wezterm, xfce4-terminal, tilix, foot or xterm | finds its window with `wmctrl` or `xdotool` (X11) |
113
+
114
+ When no terminal can be opened, the dashboard copies the command to paste instead.
115
+ Finding a running chat works by briefly giving its tab a unique title, so it doesn't work for
116
+ tabs whose title is locked (Windows Terminal's `suppressApplicationTitle`).
117
+
118
+ ## Settings
119
+
120
+ Settings live in `~/.chatdeck/config.json`, and environment variables override them:
121
+
122
+ - `port` / `CHATDECK_PORT`: default 47123
123
+ - `terminal` / `CHATDECK_TERMINAL`: which terminal to use, e.g. `kitty` or `iterm`
124
+ - `tabs` / `CHATDECK_TABS` (WSL): `true` opens chats as tabs in your last Windows Terminal window instead of a new window each
125
+ - `vdlib` / `CHATDECK_VDLIB` (WSL): Windows path to [VirtualDesktopLib.cs](https://github.com/MScholtes/VirtualDesktop),
126
+ which makes switching to a chat on another virtual desktop reliable
127
+ - `CHATDECK_DIR`: where chatdeck keeps its data, default `~/.chatdeck`
128
+
129
+ ## Tests
130
+
131
+ ```sh
132
+ python3 -m unittest discover -s tests
133
+ ```
134
+
135
+ ## Screenshots and videos
136
+
137
+ Everything in `docs/` is recorded from `chatdeck demo` by a script, so it can be redone after UI changes
138
+ (needs Node; uses Segoe UI from `/mnt/c/Windows/Fonts` when run under WSL):
139
+
140
+ ```sh
141
+ cd scripts/promo && npm install && npx playwright-core install chromium-headless-shell
142
+ npm run record # or just some: npm run record -- shots tour search projects keyboard
143
+ ```
144
+
145
+ It writes the README screenshots to `docs/` and the GIFs, MP4s, framed screenshots and a 1280×640
146
+ social preview card to `docs/media/`.
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"] # for license = "MIT" and license-files
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "chatdeck"
7
+ dynamic = ["version"]
8
+ description = "A local dashboard for your Claude Code chats: what's running, what needs you, and what it costs."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.9"
13
+ dependencies = []
14
+ keywords = ["claude", "claude-code", "dashboard", "ai", "cli"]
15
+ classifiers = [
16
+ "Environment :: Console",
17
+ "Environment :: Web Environment",
18
+ "Operating System :: MacOS",
19
+ "Operating System :: POSIX :: Linux",
20
+ "Programming Language :: Python :: 3",
21
+ ]
22
+
23
+ [project.urls]
24
+ Homepage = "https://github.com/Abdifatah09/chatdeck"
25
+ Issues = "https://github.com/Abdifatah09/chatdeck/issues"
26
+
27
+ [project.scripts]
28
+ chatdeck = "chatdeck.cli:main"
29
+
30
+ [tool.setuptools.dynamic]
31
+ version = { attr = "chatdeck.__version__" }
32
+
33
+ [tool.setuptools.package-data]
34
+ chatdeck = ["static/*", "windows/*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,2 @@
1
+ """chatdeck: a local dashboard for Claude Code chats."""
2
+ __version__ = "0.1.0"
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ main()
@@ -0,0 +1,91 @@
1
+ """Starting chatdeck at login.
2
+
3
+ WSL: a "chatdeck" shortcut in the Windows Startup folder runs the launcher (server in WSL, then
4
+ the corner bubble and tray icon); another in the Start menu also opens the dashboard.
5
+ macOS: a LaunchAgent. Linux: a systemd user service, or an XDG autostart entry without systemd."""
6
+ import os, shutil, subprocess
7
+
8
+ from . import config, desktop, hooks, terminals
9
+
10
+ LABEL = "io.github.chatdeck"
11
+ MAC_PLIST = os.path.expanduser(f"~/Library/LaunchAgents/{LABEL}.plist")
12
+ SYSTEMD_UNIT = os.path.expanduser("~/.config/systemd/user/chatdeck.service")
13
+ XDG_ENTRY = os.path.expanduser("~/.config/autostart/chatdeck.desktop")
14
+
15
+
16
+ def _serve_args(port):
17
+ """The command line that runs the server, as a list (for launchd/systemd)."""
18
+ exe = shutil.which("chatdeck")
19
+ if exe: return [exe, "serve", "--port", str(port)]
20
+ return None # running from a clone without the installed command: use the shell form
21
+
22
+
23
+ def _shell_cmd(port):
24
+ return f"{hooks.command()} serve --port {port}"
25
+
26
+
27
+ def enable(port, hotkey=""):
28
+ k = terminals.kind()
29
+ if k == "wsl":
30
+ distro = os.environ.get("WSL_DISTRO_NAME") or "Ubuntu"
31
+ folder = desktop.install_windows_files(port, distro, hooks.command())
32
+ f = desktop.win_folders()
33
+ desktop.shortcut(f["startup"] + "\\chatdeck.lnk", folder)
34
+ desktop.shortcut(f["programs"] + "\\chatdeck.lnk", folder, "-Open", hotkey)
35
+ return [f"Windows Startup: starts chatdeck and the corner button at login",
36
+ f"Start menu: \"chatdeck\" opens the dashboard" + (f" ({hotkey})" if hotkey else "")]
37
+ if k == "mac":
38
+ args = _serve_args(port) or ["/bin/sh", "-lc", _shell_cmd(port)]
39
+ items = "".join(f"<string>{a}</string>" for a in args)
40
+ os.makedirs(os.path.dirname(MAC_PLIST), exist_ok=True)
41
+ with open(MAC_PLIST, "w") as fh:
42
+ fh.write(f'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" '
43
+ f'"http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict>'
44
+ f"<key>Label</key><string>{LABEL}</string><key>ProgramArguments</key><array>{items}</array>"
45
+ f"<key>RunAtLoad</key><true/><key>KeepAlive</key><true/>"
46
+ f"<key>StandardErrorPath</key><string>{config.DATA}/server.log</string></dict></plist>\n")
47
+ subprocess.run(["launchctl", "bootout", f"gui/{os.getuid()}/{LABEL}"], capture_output=True)
48
+ subprocess.run(["launchctl", "bootstrap", f"gui/{os.getuid()}", MAC_PLIST], capture_output=True)
49
+ return ["LaunchAgent: chatdeck starts at login"]
50
+ if k == "linux":
51
+ if shutil.which("systemctl") and subprocess.run(["systemctl", "--user", "is-system-running"], capture_output=True).returncode in (0, 1):
52
+ exec_line = " ".join(_serve_args(port) or ["/bin/sh", "-lc", f"'{_shell_cmd(port)}'"])
53
+ os.makedirs(os.path.dirname(SYSTEMD_UNIT), exist_ok=True)
54
+ with open(SYSTEMD_UNIT, "w") as fh:
55
+ fh.write(f"[Unit]\nDescription=chatdeck dashboard for Claude Code\n\n[Service]\nExecStart={exec_line}\n"
56
+ f"Restart=on-failure\n\n[Install]\nWantedBy=default.target\n")
57
+ subprocess.run(["systemctl", "--user", "daemon-reload"], capture_output=True)
58
+ subprocess.run(["systemctl", "--user", "enable", "--now", "chatdeck.service"], capture_output=True)
59
+ return ["systemd user service: chatdeck starts at login"]
60
+ os.makedirs(os.path.dirname(XDG_ENTRY), exist_ok=True)
61
+ with open(XDG_ENTRY, "w") as fh:
62
+ fh.write(f"[Desktop Entry]\nType=Application\nName=chatdeck\nExec=/bin/sh -lc '{_shell_cmd(port)}'\n"
63
+ f"X-GNOME-Autostart-enabled=true\n")
64
+ return ["desktop autostart entry: chatdeck starts when you log in"]
65
+ raise SystemExit("Starting at login isn't supported on this system.")
66
+
67
+
68
+ def disable():
69
+ k = terminals.kind(); done = []
70
+ if k == "wsl":
71
+ f = desktop.win_folders()
72
+ for lnk in (f["startup"] + "\\chatdeck.lnk", f["programs"] + "\\chatdeck.lnk"):
73
+ p = desktop.to_wsl(lnk)
74
+ if os.path.exists(p): os.remove(p); done.append(f"removed {lnk}")
75
+ elif k == "mac":
76
+ subprocess.run(["launchctl", "bootout", f"gui/{os.getuid()}/{LABEL}"], capture_output=True)
77
+ if os.path.exists(MAC_PLIST): os.remove(MAC_PLIST); done.append("removed the LaunchAgent")
78
+ elif k == "linux":
79
+ if os.path.exists(SYSTEMD_UNIT):
80
+ subprocess.run(["systemctl", "--user", "disable", "--now", "chatdeck.service"], capture_output=True)
81
+ os.remove(SYSTEMD_UNIT); done.append("removed the systemd user service")
82
+ if os.path.exists(XDG_ENTRY): os.remove(XDG_ENTRY); done.append("removed the autostart entry")
83
+ return done
84
+
85
+
86
+ def status():
87
+ k = terminals.kind()
88
+ if k == "wsl": return os.path.exists(desktop.to_wsl(desktop.win_folders()["startup"] + "\\chatdeck.lnk"))
89
+ if k == "mac": return os.path.exists(MAC_PLIST)
90
+ if k == "linux": return os.path.exists(SYSTEMD_UNIT) or os.path.exists(XDG_ENTRY)
91
+ return False
@@ -0,0 +1,152 @@
1
+ """chatdeck command line.
2
+
3
+ chatdeck start the dashboard (in the background) and open it
4
+ chatdeck serve run the server in the foreground (what autostart uses)
5
+ chatdeck stop stop a background server
6
+ chatdeck setup add the Claude Code hooks for plan limits and "waiting for you"
7
+ chatdeck autostart on|off start chatdeck at login, or stop doing that
8
+ chatdeck uninstall remove the hooks and autostart
9
+ chatdeck demo try it with made-up chats (nothing real is read or changed)
10
+ chatdeck bubble Windows (WSL): start the corner button and tray icon
11
+ chatdeck hook ... used by Claude Code itself"""
12
+ import argparse, os, signal, subprocess, sys, time, urllib.request
13
+
14
+ from . import __version__, config, hooks
15
+
16
+
17
+ def _up(port):
18
+ try:
19
+ with urllib.request.urlopen(f"http://127.0.0.1:{port}/", timeout=2) as r:
20
+ return b"<title>chatdeck</title>" in r.read(4096)
21
+ except OSError:
22
+ return False
23
+
24
+
25
+ def _start_background(port):
26
+ os.makedirs(config.DATA, exist_ok=True)
27
+ log = open(os.path.join(config.DATA, "server.log"), "ab")
28
+ env = dict(os.environ, PYTHONPATH=os.pathsep.join(filter(None, [os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
29
+ os.environ.get("PYTHONPATH")])))
30
+ subprocess.Popen([sys.executable, "-m", "chatdeck", "serve", "--port", str(port)], stdout=log, stderr=log,
31
+ stdin=subprocess.DEVNULL, start_new_session=True, env=env)
32
+ for _ in range(40):
33
+ if _up(port): return True
34
+ time.sleep(0.25)
35
+ return False
36
+
37
+
38
+ def cmd_open(a):
39
+ from . import desktop
40
+ if not _up(a.port):
41
+ print(f"Starting chatdeck on http://127.0.0.1:{a.port} ...")
42
+ if not _start_background(a.port):
43
+ sys.exit(f"chatdeck didn't start - see {os.path.join(config.DATA, 'server.log')}")
44
+ if not a.no_open: desktop.open_dashboard(f"http://127.0.0.1:{a.port}/")
45
+ print(f"chatdeck is running at http://127.0.0.1:{a.port}")
46
+ if not os.path.exists(os.path.join(config.DATA, "status")) and "statusline_command" not in config.load():
47
+ print("Tip: run `chatdeck setup` so it can show your plan limits and which chats are waiting for you.")
48
+
49
+
50
+ def cmd_serve(a):
51
+ from . import server
52
+ if _up(a.port): sys.exit(f"chatdeck is already running on port {a.port}.")
53
+ print(f"chatdeck on http://127.0.0.1:{a.port} (Ctrl+C to stop)", flush=True)
54
+ try:
55
+ server.serve(a.port)
56
+ except OSError as e:
57
+ sys.exit(f"Can't use port {a.port}: {e.strerror}. Pick another with --port.")
58
+ except KeyboardInterrupt:
59
+ pass
60
+
61
+
62
+ def cmd_stop(a):
63
+ try:
64
+ with open(os.path.join(config.DATA, "server.pid")) as f: pid = int(f.read())
65
+ os.kill(pid, signal.SIGTERM); print("Stopped chatdeck.")
66
+ except (OSError, ValueError):
67
+ print("chatdeck isn't running in the background.")
68
+
69
+
70
+ def cmd_setup(a):
71
+ for line in hooks.install(): print(" " + line)
72
+ print(f"Done. Your previous settings were backed up to {os.path.join(config.DATA, 'backups')}.")
73
+ print("New and restarted Claude Code sessions pick this up.")
74
+
75
+
76
+ def cmd_uninstall(a):
77
+ done = hooks.uninstall()
78
+ try:
79
+ from . import autostart
80
+ done += autostart.disable()
81
+ except SystemExit:
82
+ pass
83
+ for line in done or ["nothing to remove"]: print(" " + line)
84
+
85
+
86
+ def cmd_autostart(a):
87
+ from . import autostart
88
+ if a.state == "on":
89
+ cfg = config.load(); cfg["port"] = a.port; config.save(cfg)
90
+ for line in autostart.enable(a.port, a.hotkey): print(" " + line)
91
+ elif a.state == "off":
92
+ for line in autostart.disable() or ["it wasn't set to start at login"]: print(" " + line)
93
+ else:
94
+ print("on" if autostart.status() else "off")
95
+
96
+
97
+ def cmd_bubble(a):
98
+ from . import desktop, terminals
99
+ if terminals.kind() != "wsl": sys.exit("The corner button is Windows-only for now (run chatdeck from WSL).")
100
+ desktop.start_bubble(a.port)
101
+
102
+
103
+ def cmd_demo(a):
104
+ import shutil, tempfile
105
+ from . import demo, desktop
106
+ root = tempfile.mkdtemp(prefix="chatdeck-demo-")
107
+ try:
108
+ env = dict(os.environ, HOME=root, CHATDECK_DIR=demo.build(root), CHATDECK_DEMO="1",
109
+ PYTHONPATH=os.pathsep.join(filter(None, [os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
110
+ os.environ.get("PYTHONPATH")])))
111
+ srv = subprocess.Popen([sys.executable, "-m", "chatdeck", "serve", "--port", str(a.port)], env=env)
112
+ url = f"http://127.0.0.1:{a.port}/"
113
+ for _ in range(40):
114
+ if _up(a.port) or srv.poll() is not None: break
115
+ time.sleep(0.25)
116
+ if not a.no_open and srv.poll() is None: desktop.open_dashboard(url)
117
+ srv.wait()
118
+ except KeyboardInterrupt:
119
+ srv.wait()
120
+ finally:
121
+ shutil.rmtree(root, ignore_errors=True)
122
+
123
+
124
+ def cmd_hook(a):
125
+ if a.name == "statusline": hooks.statusline()
126
+ elif a.name == "waiting": hooks.waiting()
127
+
128
+
129
+ def main(argv=None):
130
+ p = argparse.ArgumentParser(prog="chatdeck", description="A local dashboard for your Claude Code chats.")
131
+ p.add_argument("--version", action="version", version=f"chatdeck {__version__}")
132
+ sub = p.add_subparsers(dest="cmd")
133
+ def port_arg(sp): sp.add_argument("--port", type=int, default=config.port(), help=f"default {config.port()}")
134
+ o = sub.add_parser("open", help="start the dashboard if needed and open it (the default)")
135
+ port_arg(o); o.add_argument("--no-open", action="store_true", help="start it but don't open a window")
136
+ port_arg(sub.add_parser("serve", help="run the server in the foreground"))
137
+ sub.add_parser("stop", help="stop a background server")
138
+ sub.add_parser("setup", help="add the Claude Code hooks chatdeck uses")
139
+ sub.add_parser("uninstall", help="remove the hooks and autostart")
140
+ s = sub.add_parser("autostart", help="start chatdeck at login: on, off or status")
141
+ s.add_argument("state", choices=["on", "off", "status"]); port_arg(s)
142
+ s.add_argument("--hotkey", default="", help='Windows: shortcut key for the Start menu entry, e.g. "Ctrl+Alt+A"')
143
+ d = sub.add_parser("demo", help="try chatdeck with made-up chats")
144
+ d.add_argument("--port", type=int, default=47199, help="default 47199"); d.add_argument("--no-open", action="store_true")
145
+ port_arg(sub.add_parser("bubble", help="Windows: start the corner button and tray icon"))
146
+ h = sub.add_parser("hook", help=argparse.SUPPRESS); h.add_argument("name", choices=["statusline", "waiting"])
147
+ argv = list(sys.argv[1:] if argv is None else argv)
148
+ if not argv or (argv[0].startswith("-") and argv[0] not in ("-h", "--help", "--version")):
149
+ argv.insert(0, "open") # plain `chatdeck` (with or without options) starts and opens the dashboard
150
+ a = p.parse_args(argv)
151
+ {"open": cmd_open, "serve": cmd_serve, "stop": cmd_stop, "setup": cmd_setup, "uninstall": cmd_uninstall,
152
+ "autostart": cmd_autostart, "demo": cmd_demo, "bubble": cmd_bubble, "hook": cmd_hook}[a.cmd](a)
@@ -0,0 +1,34 @@
1
+ """Where chatdeck keeps its data, and its settings (~/.chatdeck/config.json).
2
+ Environment variables (CHATDECK_PORT, CHATDECK_TERMINAL, CHATDECK_VDLIB, CHATDECK_DIR) override the file."""
3
+ import json, os
4
+
5
+ HOME = os.path.expanduser("~")
6
+ DATA = os.environ.get("CHATDECK_DIR") or os.path.join(HOME, ".chatdeck")
7
+ FILE = os.path.join(DATA, "config.json")
8
+ DEFAULT_PORT = 47123
9
+ CLAUDE_SETTINGS = os.path.join(HOME, ".claude", "settings.json")
10
+ DEMO = bool(os.environ.get("CHATDECK_DEMO")) # `chatdeck demo`: made-up chats, nothing outside them is touched
11
+
12
+
13
+ def load():
14
+ try:
15
+ with open(FILE) as f: return json.load(f)
16
+ except (OSError, ValueError):
17
+ return {}
18
+
19
+
20
+ def save(cfg):
21
+ os.makedirs(DATA, exist_ok=True)
22
+ tmp = FILE + ".tmp"
23
+ with open(tmp, "w") as f: json.dump(cfg, f, indent=2)
24
+ os.replace(tmp, FILE)
25
+
26
+
27
+ def get(key, default=None):
28
+ env = os.environ.get("CHATDECK_" + key.upper())
29
+ if env: return env
30
+ return load().get(key, default)
31
+
32
+
33
+ def port():
34
+ return int(get("port") or DEFAULT_PORT)