claude-code-kanban 1.9.0 → 1.10.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.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Claude Task Viewer
1
+ # Claude Code Kanban
2
2
 
3
3
  A real-time Kanban board for **observing** Claude Code tasks. See what Claude is working on, track dependencies between tasks, and manage task cleanup and priority.
4
4
 
@@ -8,7 +8,7 @@ A real-time Kanban board for **observing** Claude Code tasks. See what Claude is
8
8
 
9
9
  ## Why Use This?
10
10
 
11
- When Claude Code breaks down complex work into tasks, you get visibility into its thinking — but only in the terminal. Claude Task Viewer gives you a persistent, visual dashboard to:
11
+ When Claude Code breaks down complex work into tasks, you get visibility into its thinking — but only in the terminal. Claude Code Kanban gives you a persistent, visual dashboard to:
12
12
 
13
13
  - **See the big picture** — All your sessions and tasks in one place
14
14
  - **Know what's happening now** — Live Updates show exactly what Claude is doing across all sessions
@@ -24,6 +24,11 @@ Claude Code controls task state — the viewer shows you what's happening:
24
24
  - **Task dependencies** — Visualise blockedBy/blocks relationships to understand the critical path
25
25
  - **Live activity feed** — Real-time stream of all in-progress tasks across every session
26
26
 
27
+ ### Agent Teams Support
28
+ - **Team detection** — Automatically detects team sessions with multiple agents
29
+ - **Owner filtering** — Filter Kanban board by team member with color-coded agent indicators
30
+ - **Member count badges** — See how many agents are working in each session
31
+
27
32
  ### Cleanup Operations
28
33
  - **Delete tasks** — Remove tasks with the delete button or press `D` (includes safety checks for dependencies)
29
34
  - **Bulk delete** — Delete all tasks in a session at once
@@ -32,8 +37,9 @@ Claude Code controls task state — the viewer shows you what's happening:
32
37
  View and organize your Claude Code sessions:
33
38
  - **Session discovery** — Automatically finds all sessions in `~/.claude/tasks/` and `~/.claude/projects/`
34
39
  - **View project paths** — See the full filesystem path for each project
40
+ - **Git branch display** — See which branch each session is working on
35
41
  - **Fuzzy search** — Search across session names, task descriptions, and project paths with instant filtering
36
- - **Session limits** — Filter to show only active sessions or a specific number of recent sessions
42
+ - **Session filters** — Filter by active/all sessions and by project
37
43
 
38
44
  ### Keyboard Shortcuts
39
45
  - `?` — Show help with all keyboard shortcuts
@@ -45,15 +51,22 @@ View and organize your Claude Code sessions:
45
51
  ### Quick start
46
52
 
47
53
  ```bash
48
- npx claude-task-viewer
54
+ npx claude-code-kanban
49
55
  ```
50
56
 
51
57
  Open http://localhost:3456
52
58
 
59
+ ### Global install
60
+
61
+ ```bash
62
+ npm install -g claude-code-kanban
63
+ claude-code-kanban --open
64
+ ```
65
+
53
66
  ### From source
54
67
 
55
68
  ```bash
56
- git clone https://github.com/L1AD/claude-task-viewer.git
69
+ git clone https://github.com/NikiforovAll/claude-task-viewer.git
57
70
  cd claude-task-viewer
58
71
  npm install
59
72
  npm start
@@ -73,6 +86,8 @@ Claude Code stores tasks in `~/.claude/tasks/`. Each session has its own folder:
73
86
 
74
87
  The viewer watches this directory and pushes updates via Server-Sent Events. Changes appear instantly — no polling, no refresh needed.
75
88
 
89
+ If port 3456 is already in use, the server automatically falls back to a random available port.
90
+
76
91
  ## Task Structure
77
92
 
78
93
  ```json
@@ -94,13 +109,13 @@ The viewer watches this directory and pushes updates via Server-Sent Events. Cha
94
109
 
95
110
  ```bash
96
111
  # Custom port
97
- PORT=8080 npx claude-task-viewer
112
+ PORT=8080 npx claude-code-kanban
98
113
 
99
114
  # Open browser automatically
100
- npx claude-task-viewer --open
115
+ npx claude-code-kanban --open
101
116
 
102
117
  # Use a different Claude config directory (for multiple accounts)
103
- npx claude-task-viewer --dir=~/.claude-work
118
+ npx claude-code-kanban --dir=~/.claude-work
104
119
  ```
105
120
 
106
121
  ## API
@@ -112,6 +127,7 @@ npx claude-task-viewer --dir=~/.claude-work
112
127
  | `/api/tasks/all` | GET | Get all tasks across all sessions |
113
128
  | `/api/tasks/:session/:task` | DELETE | Delete a task (checks dependencies) |
114
129
  | `/api/tasks/:session/:task/note` | POST | Add a note to a task |
130
+ | `/api/teams/:name` | GET | Load team configuration |
115
131
  | `/api/events` | GET | SSE stream for live updates |
116
132
 
117
133
  ## Design Philosophy
@@ -120,25 +136,6 @@ npx claude-task-viewer --dir=~/.claude-work
120
136
 
121
137
  **Limited interaction:** You can delete tasks and add notes, but task status, subject, and description reflect Claude's actual work and can only be changed by Claude Code itself.
122
138
 
123
- ## Roadmap
124
-
125
- ### ✅ Completed
126
- - **Real-time observation** — Live updates feed showing what Claude is doing across all sessions
127
- - **Task dependencies** — Visualise blockedBy/blocks relationships
128
- - **Task deletion** — Delete tasks with dependency checking
129
- - **Keyboard shortcuts** — ?, D, Esc for quick actions
130
- - **Session discovery** — Automatic detection of all Claude Code sessions
131
- - **Search** — Search across sessions and tasks
132
-
133
- ### 🚧 Planned
134
- - **Enhanced search & filter** — Filter by status, dependencies, date ranges
135
- - **Session grouping** — Group sessions by project or time period
136
- - **Task timeline** — See when tasks were created and completed
137
- - **Export** — Export session data for analysis or reporting
138
- - **Desktop notifications** — Optional notifications when tasks complete
139
-
140
- [Open an issue](https://github.com/L1AD/claude-task-viewer/issues) with ideas or feedback.
141
-
142
139
  ## License
143
140
 
144
141
  MIT
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "claude-code-kanban",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "A web-based Kanban board for viewing Claude Code tasks with agent teams support",
5
5
  "main": "server.js",
6
6
  "bin": {
7
- "claude-task-viewer": "./server.js"
7
+ "claude-code-kanban": "./server.js"
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node server.js",
package/public/index.html CHANGED
@@ -4,6 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>CC Kanban</title>
7
+ <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='6' fill='%231a1a1a'/%3E%3Crect x='4' y='6' width='7' height='20' rx='2' fill='%23e8927c'/%3E%3Crect x='13' y='6' width='7' height='14' rx='2' fill='%23e8927c'/%3E%3Crect x='22' y='6' width='7' height='8' rx='2' fill='%23e8927c'/%3E%3C/svg%3E">
7
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
10
  <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Playfair+Display:wght@400;500;600&display=swap" rel="stylesheet">
@@ -173,6 +174,12 @@
173
174
  padding: 0 16px 10px;
174
175
  }
175
176
 
177
+ .reset-btn {
178
+ flex-shrink: 0;
179
+ width: 32px;
180
+ height: 32px;
181
+ }
182
+
176
183
  .filter-dropdown {
177
184
  flex: 1;
178
185
  appearance: none;
@@ -340,31 +347,14 @@
340
347
 
341
348
  .session-branch {
342
349
  font-size: 10px;
343
- color: var(--accent);
344
- margin-top: 3px;
345
- padding: 2px 6px;
346
- background: var(--border);
347
- border-radius: 3px;
348
- display: inline-block;
349
- font-family: var(--mono);
350
- white-space: nowrap;
351
- overflow: hidden;
352
- text-overflow: ellipsis;
353
- }
354
-
355
- .session-branch {
356
- font-size: 10px;
357
- color: var(--accent);
358
- margin-top: 3px;
359
- padding: 2px 6px;
360
- background: var(--border);
361
- border-radius: 3px;
362
- display: inline-block;
350
+ color: var(--text-muted);
351
+ margin-top: 2px;
352
+ display: block;
353
+ font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
363
354
  white-space: nowrap;
364
355
  overflow: hidden;
365
356
  text-overflow: ellipsis;
366
- max-width: 100%;
367
- font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
357
+ opacity: 0.7;
368
358
  }
369
359
 
370
360
  .session-progress {
@@ -1650,6 +1640,12 @@
1650
1640
  <option value="50">Show 50</option>
1651
1641
  <option value="all">Show All</option>
1652
1642
  </select>
1643
+ <button class="icon-btn reset-btn" onclick="resetState()" title="Reset all filters" aria-label="Reset all filters">
1644
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
1645
+ <path d="M3 12a9 9 0 1 1 9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/>
1646
+ <path d="M3 22v-6h6"/>
1647
+ </svg>
1648
+ </button>
1653
1649
  </div>
1654
1650
  <div id="sessions-list" class="sessions-list"></div>
1655
1651
  </div>
@@ -1758,14 +1754,57 @@
1758
1754
  let currentSessionId = null;
1759
1755
  let currentTasks = [];
1760
1756
  let viewMode = 'session';
1761
- let sessionFilter = localStorage.getItem('sessionFilter') || 'all'; // 'all' or 'active'
1762
- let sessionLimit = localStorage.getItem('sessionLimit') || '20'; // '10', '20', '50', 'all'
1757
+ let sessionFilter = 'active';
1758
+ let sessionLimit = '20';
1763
1759
  let filterProject = null; // null = all projects, or project path to filter
1764
1760
  let searchQuery = ''; // Search query for fuzzy search
1765
1761
  let allTasksCache = []; // Cache all tasks for search
1766
1762
  let bulkDeleteSessionId = null; // Track session for bulk delete
1767
1763
  let ownerFilter = '';
1768
1764
 
1765
+ function getUrlState() {
1766
+ const params = new URLSearchParams(window.location.search);
1767
+ return {
1768
+ session: params.get('session'),
1769
+ view: params.get('view'),
1770
+ filter: params.get('filter'),
1771
+ limit: params.get('limit'),
1772
+ project: params.get('project'),
1773
+ owner: params.get('owner'),
1774
+ search: params.get('search'),
1775
+ };
1776
+ }
1777
+
1778
+ function updateUrl() {
1779
+ const params = new URLSearchParams();
1780
+ if (viewMode === 'all') params.set('view', 'all');
1781
+ if (currentSessionId) params.set('session', currentSessionId);
1782
+ if (sessionFilter !== 'active') params.set('filter', sessionFilter);
1783
+ if (sessionLimit !== '20') params.set('limit', sessionLimit);
1784
+ if (filterProject) params.set('project', filterProject);
1785
+ if (ownerFilter) params.set('owner', ownerFilter);
1786
+ if (searchQuery) params.set('search', searchQuery);
1787
+ const qs = params.toString();
1788
+ const url = qs ? `?${qs}` : window.location.pathname;
1789
+ history.replaceState(null, '', url);
1790
+ }
1791
+
1792
+ function resetState() {
1793
+ history.replaceState(null, '', window.location.pathname);
1794
+ sessionFilter = 'active';
1795
+ sessionLimit = '20';
1796
+ filterProject = null;
1797
+ ownerFilter = '';
1798
+ searchQuery = '';
1799
+ viewMode = 'all';
1800
+ currentSessionId = null;
1801
+ const searchInput = document.getElementById('search-input');
1802
+ if (searchInput) searchInput.value = '';
1803
+ document.getElementById('search-clear-btn')?.classList.remove('visible');
1804
+ loadPreferences();
1805
+ fetchSessions().then(() => showAllTasks());
1806
+ }
1807
+
1769
1808
  // DOM
1770
1809
  const sessionsList = document.getElementById('sessions-list');
1771
1810
  const noSession = document.getElementById('no-session');
@@ -1826,6 +1865,7 @@
1826
1865
  clearBtn.classList.remove('visible');
1827
1866
  }
1828
1867
 
1868
+ updateUrl();
1829
1869
  renderSessions();
1830
1870
  }
1831
1871
 
@@ -1834,6 +1874,7 @@
1834
1874
  searchInput.value = '';
1835
1875
  searchQuery = '';
1836
1876
  document.getElementById('search-clear-btn').classList.remove('visible');
1877
+ updateUrl();
1837
1878
  renderSessions();
1838
1879
  }
1839
1880
 
@@ -2090,12 +2131,14 @@
2090
2131
  currentTasks = newTasks;
2091
2132
  currentSessionId = sessionId;
2092
2133
  ownerFilter = '';
2134
+ updateUrl();
2093
2135
  renderSession();
2094
2136
  } catch (error) {
2095
2137
  console.error('Failed to fetch tasks:', error);
2096
2138
  currentTasks = [];
2097
2139
  currentSessionId = sessionId;
2098
2140
  lastCurrentTasksHash = '';
2141
+ updateUrl();
2099
2142
  renderSession();
2100
2143
  }
2101
2144
  }
@@ -2111,6 +2154,7 @@
2111
2154
  tasks = tasks.filter(t => t.project === filterProject);
2112
2155
  }
2113
2156
  currentTasks = tasks;
2157
+ updateUrl();
2114
2158
  renderAllTasks();
2115
2159
  renderSessions();
2116
2160
  } catch (error) {
@@ -2709,12 +2753,13 @@
2709
2753
  let refreshTimer = null;
2710
2754
  function debouncedRefresh(sessionId, isMetadata) {
2711
2755
  clearTimeout(refreshTimer);
2756
+ const delay = isMetadata ? 2000 : 500;
2712
2757
  refreshTimer = setTimeout(() => {
2713
2758
  fetchSessions().catch(err => console.error('[SSE] fetchSessions failed:', err));
2714
2759
  if (currentSessionId && (isMetadata || sessionId === currentSessionId)) {
2715
2760
  fetchTasks(currentSessionId);
2716
2761
  }
2717
- }, 500);
2762
+ }, delay);
2718
2763
  }
2719
2764
 
2720
2765
  eventSource.onmessage = (event) => {
@@ -2778,18 +2823,19 @@
2778
2823
 
2779
2824
  function filterBySessions(value) {
2780
2825
  sessionFilter = value;
2781
- localStorage.setItem('sessionFilter', sessionFilter);
2826
+ updateUrl();
2782
2827
  renderSessions();
2783
2828
  }
2784
2829
 
2785
2830
  function changeSessionLimit(value) {
2786
2831
  sessionLimit = value;
2787
- localStorage.setItem('sessionLimit', sessionLimit);
2832
+ updateUrl();
2788
2833
  fetchSessions();
2789
2834
  }
2790
2835
 
2791
2836
  function filterByProject(project) {
2792
2837
  filterProject = project || null;
2838
+ updateUrl();
2793
2839
  renderSessions();
2794
2840
  fetchLiveUpdates();
2795
2841
  showAllTasks();
@@ -2956,23 +3002,52 @@
2956
3002
  const c = value ? getOwnerColor(value) : null;
2957
3003
  select.style.color = c ? c.color : '';
2958
3004
  select.style.backgroundColor = c ? c.bg : '';
3005
+ updateUrl();
2959
3006
  renderKanban();
2960
3007
  }
2961
3008
 
2962
3009
  // Init
2963
3010
  loadTheme();
3011
+
3012
+ const urlState = getUrlState();
3013
+ sessionFilter = urlState.filter || 'active';
3014
+ sessionLimit = urlState.limit || '20';
3015
+ filterProject = urlState.project || null;
3016
+ ownerFilter = urlState.owner || '';
3017
+ searchQuery = urlState.search || '';
3018
+
2964
3019
  loadPreferences();
2965
3020
  setupEventSource();
2966
3021
 
2967
- // Fetch sessions and show newest one by default
3022
+ if (urlState.search) {
3023
+ document.getElementById('search-input').value = urlState.search;
3024
+ document.getElementById('search-clear-btn').classList.add('visible');
3025
+ }
3026
+
2968
3027
  fetchSessions().then(() => {
2969
- if (sessions.length > 0) {
2970
- // Sessions are already sorted by newest first from API
3028
+ if (urlState.view === 'all') {
3029
+ showAllTasks();
3030
+ } else if (urlState.session) {
3031
+ fetchTasks(urlState.session);
3032
+ } else if (sessions.length > 0) {
2971
3033
  fetchTasks(sessions[0].id);
2972
3034
  } else {
2973
3035
  showAllTasks();
2974
3036
  }
2975
3037
  });
3038
+
3039
+ window.addEventListener('popstate', () => {
3040
+ const s = getUrlState();
3041
+ sessionFilter = s.filter || 'active';
3042
+ sessionLimit = s.limit || '20';
3043
+ filterProject = s.project || null;
3044
+ ownerFilter = s.owner || '';
3045
+ searchQuery = s.search || '';
3046
+ loadPreferences();
3047
+ if (s.view === 'all') showAllTasks();
3048
+ else if (s.session) fetchTasks(s.session);
3049
+ else if (sessions.length > 0) fetchTasks(sessions[0].id);
3050
+ });
2976
3051
  </script>
2977
3052
 
2978
3053
  <!-- Help Modal -->
package/server.js CHANGED
@@ -517,11 +517,27 @@ projectsWatcher.on('all', (event, filePath) => {
517
517
  });
518
518
 
519
519
  // Start server
520
- app.listen(PORT, () => {
521
- console.log(`Claude Task Viewer running at http://localhost:${PORT}`);
520
+ const server = app.listen(PORT, () => {
521
+ const actualPort = server.address().port;
522
+ console.log(`Claude Task Viewer running at http://localhost:${actualPort}`);
522
523
 
523
- // Open browser if --open flag is passed
524
524
  if (process.argv.includes('--open')) {
525
- import('open').then(open => open.default(`http://localhost:${PORT}`));
525
+ import('open').then(open => open.default(`http://localhost:${actualPort}`));
526
+ }
527
+ });
528
+
529
+ server.on('error', (err) => {
530
+ if (err.code === 'EADDRINUSE') {
531
+ console.log(`Port ${PORT} in use, trying random port...`);
532
+ const fallback = app.listen(0, () => {
533
+ const actualPort = fallback.address().port;
534
+ console.log(`Claude Task Viewer running at http://localhost:${actualPort}`);
535
+
536
+ if (process.argv.includes('--open')) {
537
+ import('open').then(open => open.default(`http://localhost:${actualPort}`));
538
+ }
539
+ });
540
+ } else {
541
+ throw err;
526
542
  }
527
543
  });