claude-code-kanban 4.6.0 → 4.7.1
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 +2 -2
- package/cli.js +2 -2
- package/lib/parsers.js +35 -2
- package/package.json +1 -1
- package/plugin/plugins/claude-code-kanban/.claude-plugin/plugin.json +1 -1
- package/plugin/plugins/claude-code-kanban/skills/kanban/SKILL.md +2 -2
- package/public/app.js +48 -9
- package/public/style.css +4 -16
- package/server.js +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ All changes are non-destructive — existing settings are preserved.
|
|
|
34
34
|
npx claude-code-kanban --open
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
Open http://localhost:
|
|
37
|
+
Open http://localhost:3541 (or use `--open` to auto-launch the browser).
|
|
38
38
|
|
|
39
39
|
### 3. Use Claude Code as usual
|
|
40
40
|
|
|
@@ -64,7 +64,7 @@ npx claude-code-kanban --open # Auto-open browser
|
|
|
64
64
|
npx claude-code-kanban --dir=~/.claude-work # Custom Claude config dir
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
If port
|
|
67
|
+
If port 3541 is in use, the server falls back to a random available port.
|
|
68
68
|
|
|
69
69
|
### Global install
|
|
70
70
|
|
package/cli.js
CHANGED
|
@@ -145,7 +145,7 @@ function printTopHelp() {
|
|
|
145
145
|
console.log(' --help, -h Show help (top-level, noun-level, or leaf-level)');
|
|
146
146
|
console.log(' --version, -v Print version and exit');
|
|
147
147
|
console.log('\nServer mode (no subcommand):');
|
|
148
|
-
console.log(' --port <n> Port to listen on (default
|
|
148
|
+
console.log(' --port <n> Port to listen on (default 3541)');
|
|
149
149
|
console.log(' --dir <path> Override Claude config dir (default ~/.claude)');
|
|
150
150
|
console.log(' --open Open browser on start');
|
|
151
151
|
console.log(' --install, --uninstall Install or remove the agent-spy hook');
|
|
@@ -187,7 +187,7 @@ function getArgValue(args, name) {
|
|
|
187
187
|
return args[idx + 1] && !args[idx + 1].startsWith('--') ? args[idx + 1] : null;
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
function cliPort() { return process.env.PORT ||
|
|
190
|
+
function cliPort() { return process.env.PORT || 3541; }
|
|
191
191
|
function unreachable() { return `Cannot reach cck server on port ${cliPort()}. Start it first with "claude-code-kanban".`; }
|
|
192
192
|
|
|
193
193
|
class CliUnreachable extends Error { constructor() { super(unreachable()); this.code = 'unreachable'; } }
|
package/lib/parsers.js
CHANGED
|
@@ -442,13 +442,20 @@ function getSystemMessageLabel(text) {
|
|
|
442
442
|
const statusMatch = text.match(/<status>([^<]+)<\/status>/);
|
|
443
443
|
return statusMatch ? `Background task ${statusMatch[1]}` : 'Background task notification';
|
|
444
444
|
}
|
|
445
|
-
|
|
445
|
+
// The "Compacted (ctrl+o…)" stdout echo is redundant with the isCompactSummary
|
|
446
|
+
// chip (modern Claude Code stores the summary inline). Skipping it lets the one
|
|
447
|
+
// summary-bearing chip stand alone instead of trailing a bare marker that the
|
|
448
|
+
// resume prompt sorts away from, preventing collapse.
|
|
449
|
+
if (text.includes('<local-command-stdout>') && text.includes('Compacted')) return '__skip__';
|
|
446
450
|
if (text.includes('<local-command-stdout>')) return 'Command output';
|
|
447
451
|
if (text.includes('<local-command-caveat>')) return 'System notification';
|
|
448
452
|
if (text.includes('.output completed') && text.includes('Background command')) return 'Background task completed';
|
|
449
453
|
if (text.startsWith('This session is being continued from a previous conversation')) return '__skip__';
|
|
450
454
|
if (text.includes('<command-name>/clear</command-name>')) return '__skip__';
|
|
451
|
-
|
|
455
|
+
// The /compact trigger record is redundant: the boundary stdout + isCompactSummary
|
|
456
|
+
// record already render as one expandable "Compacted" chip. Skip it so the chip
|
|
457
|
+
// collapses cleanly instead of leaving a separate marker where the user typed it.
|
|
458
|
+
if (text.includes('<command-name>/compact</command-name>')) return '__skip__';
|
|
452
459
|
return null;
|
|
453
460
|
}
|
|
454
461
|
|
|
@@ -641,6 +648,26 @@ function readRecentMessages(jsonlPath, limit = 10) {
|
|
|
641
648
|
}
|
|
642
649
|
}
|
|
643
650
|
} else if (obj.type === 'user' && obj.message?.role === 'user' && !obj.isMeta) {
|
|
651
|
+
if (obj.isCompactSummary) {
|
|
652
|
+
// Surface the compaction summary as a single expandable "Compacted"
|
|
653
|
+
// chip (compactSummary), never a raw bubble. The boundary marker plus
|
|
654
|
+
// the /compact command/stdout records collapse into this one entry below.
|
|
655
|
+
const raw = typeof obj.message.content === 'string'
|
|
656
|
+
? obj.message.content
|
|
657
|
+
: Array.isArray(obj.message.content)
|
|
658
|
+
? obj.message.content.filter((b) => b.type === 'text').map((b) => b.text).join('\n')
|
|
659
|
+
: '';
|
|
660
|
+
const summary = raw
|
|
661
|
+
.replace(/^This session is being continued[^\n]*\n+(The summary below[^\n]*\n+)?/i, '')
|
|
662
|
+
.trim();
|
|
663
|
+
messages.push({
|
|
664
|
+
type: 'user',
|
|
665
|
+
systemLabel: 'Compacted',
|
|
666
|
+
compactSummary: summary || null,
|
|
667
|
+
timestamp: obj.timestamp
|
|
668
|
+
});
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
644
671
|
if (typeof obj.message.content === 'string') {
|
|
645
672
|
const t = obj.message.content;
|
|
646
673
|
const tmMatch = t.match(/<teammate-message\s+([^>]*)>([\s\S]*?)<\/teammate-message>/);
|
|
@@ -801,6 +828,12 @@ function readRecentMessages(jsonlPath, limit = 10) {
|
|
|
801
828
|
messages.sort((a, b) => (a.timestamp || '').localeCompare(b.timestamp || ''));
|
|
802
829
|
for (let i = messages.length - 1; i > 0; i--) {
|
|
803
830
|
if (messages[i].systemLabel === 'Compacted' && messages[i - 1].systemLabel === 'Compacted') {
|
|
831
|
+
// Carry the summary body onto the surviving chip so collapsing the
|
|
832
|
+
// boundary + /compact command + stdout + isCompactSummary records into
|
|
833
|
+
// one entry never drops the expandable summary.
|
|
834
|
+
if (messages[i].compactSummary && !messages[i - 1].compactSummary) {
|
|
835
|
+
messages[i - 1].compactSummary = messages[i].compactSummary;
|
|
836
|
+
}
|
|
804
837
|
messages.splice(i, 1);
|
|
805
838
|
}
|
|
806
839
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: kanban
|
|
3
3
|
description: Drive the claude-code-kanban browser dashboard from this Claude session. Use this skill when the user mentions "kanban" together with "session" — e.g. "open this session in kanban", "show kanban", "focus current session in kanban", "pin/unpin a session in kanban", "preview this file in kanban", or asks to peek/view a kanban session.
|
|
4
|
-
compatibility: Requires the `claude-code-kanban` CLI on PATH and the server running locally (default port
|
|
4
|
+
compatibility: Requires the `claude-code-kanban` CLI on PATH and the server running locally (default port 3541).
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Kanban Skill
|
|
@@ -69,6 +69,6 @@ Add `--json` to any list-style verb for machine-readable output.
|
|
|
69
69
|
|
|
70
70
|
## Troubleshooting
|
|
71
71
|
|
|
72
|
-
- **"Cannot reach cck server on port
|
|
72
|
+
- **"Cannot reach cck server on port 3541"** → ask the user to start it: `claude-code-kanban` (or `npm start` in the cck repo).
|
|
73
73
|
- **Different port** → set `PORT=<n>` env var when invoking the CLI.
|
|
74
74
|
- **Ambiguous session prefix (HTTP 409)** → use the full id. With `${CLAUDE_SESSION_ID}` this won't happen.
|
package/public/app.js
CHANGED
|
@@ -511,6 +511,8 @@ function setActivityFilter(kind) {
|
|
|
511
511
|
// waiting is a sub-state of active — couple them so one click covers all running sessions
|
|
512
512
|
toggleActivityKind('active');
|
|
513
513
|
toggleActivityKind('waiting');
|
|
514
|
+
// Expand sections that have active sessions so they're visible on click
|
|
515
|
+
if (activityFilter.has('active')) expandActiveGroups();
|
|
514
516
|
} else {
|
|
515
517
|
toggleActivityKind(kind);
|
|
516
518
|
}
|
|
@@ -2829,6 +2831,8 @@ function renderSessions() {
|
|
|
2829
2831
|
|
|
2830
2832
|
// Group active sessions by project
|
|
2831
2833
|
if (sessionFilter === 'active') {
|
|
2834
|
+
// Auto-expand a collapsed section when newly-active work lands in it (live refresh).
|
|
2835
|
+
expandActiveGroups({ onlyNew: true });
|
|
2832
2836
|
const groups = new Map();
|
|
2833
2837
|
const ungrouped = [];
|
|
2834
2838
|
for (const session of filteredSessions) {
|
|
@@ -3212,8 +3216,7 @@ async function onColumnDrop(e) {
|
|
|
3212
3216
|
|
|
3213
3217
|
//#region KEYBOARD_NAV
|
|
3214
3218
|
function selectTask(taskId, sessionId) {
|
|
3215
|
-
|
|
3216
|
-
if (prev) prev.classList.remove('selected');
|
|
3219
|
+
clearTaskSelection();
|
|
3217
3220
|
selectedTaskId = taskId;
|
|
3218
3221
|
selectedSessionId = sessionId;
|
|
3219
3222
|
taskHighlightDimmed = false;
|
|
@@ -3330,6 +3333,11 @@ function clearKbSelection() {
|
|
|
3330
3333
|
if (prev) prev.classList.remove('kb-selected');
|
|
3331
3334
|
}
|
|
3332
3335
|
|
|
3336
|
+
function clearTaskSelection() {
|
|
3337
|
+
const prev = document.querySelector('.task-card.selected');
|
|
3338
|
+
if (prev) prev.classList.remove('selected');
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3333
3341
|
function selectSessionByIndex(idx, items) {
|
|
3334
3342
|
items = items || getNavigableItems();
|
|
3335
3343
|
if (items.length === 0) return;
|
|
@@ -3368,11 +3376,37 @@ function setGroupCollapsed(header, collapsed) {
|
|
|
3368
3376
|
header.classList.toggle('collapsed', collapsed);
|
|
3369
3377
|
const container = getGroupSessionsContainer(header);
|
|
3370
3378
|
if (container) container.classList.toggle('collapsed', collapsed);
|
|
3379
|
+
persistCollapsedGroups();
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
function persistCollapsedGroups() {
|
|
3371
3383
|
try {
|
|
3372
3384
|
localStorage.setItem('collapsedGroups', JSON.stringify([...collapsedProjectGroups]));
|
|
3373
3385
|
} catch (_) {}
|
|
3374
3386
|
}
|
|
3375
3387
|
|
|
3388
|
+
let prevActiveSessionIds = null; // null until primed by the first onlyNew pass
|
|
3389
|
+
|
|
3390
|
+
// Expand collapsed project sections that contain active sessions; leave the rest collapsed.
|
|
3391
|
+
// onlyNew: expand only for sessions that became active since the last call, so a section the
|
|
3392
|
+
// user manually collapsed stays collapsed until NEW active work lands in it (live refresh path).
|
|
3393
|
+
// The first onlyNew pass only primes the tracking set — it respects saved collapse state on load.
|
|
3394
|
+
// onlyNew=false force-expands every section with active work (explicit chip click).
|
|
3395
|
+
function expandActiveGroups({ onlyNew = false } = {}) {
|
|
3396
|
+
const primed = prevActiveSessionIds !== null;
|
|
3397
|
+
const activeIds = new Set();
|
|
3398
|
+
let changed = false;
|
|
3399
|
+
for (const s of sessions) {
|
|
3400
|
+
if (!isSessionActive(s)) continue;
|
|
3401
|
+
activeIds.add(s.id);
|
|
3402
|
+
if (onlyNew && (!primed || prevActiveSessionIds.has(s.id))) continue;
|
|
3403
|
+
if (collapsedProjectGroups.delete(s.project || '__ungrouped__')) changed = true;
|
|
3404
|
+
}
|
|
3405
|
+
prevActiveSessionIds = activeIds;
|
|
3406
|
+
if (changed) persistCollapsedGroups();
|
|
3407
|
+
// renderSessions() re-renders headers/containers from the updated set
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3376
3410
|
function isGroupHeader(el) {
|
|
3377
3411
|
return el.classList.contains('project-group-header') || el.classList.contains('pinned-sub-header');
|
|
3378
3412
|
}
|
|
@@ -3434,8 +3468,7 @@ function setFocusZone(zone) {
|
|
|
3434
3468
|
// Clear all zone visuals
|
|
3435
3469
|
sidebar.classList.remove('sidebar-focused');
|
|
3436
3470
|
clearKbSelection();
|
|
3437
|
-
|
|
3438
|
-
if (selCard) selCard.classList.remove('selected');
|
|
3471
|
+
clearTaskSelection();
|
|
3439
3472
|
|
|
3440
3473
|
focusZone = zone;
|
|
3441
3474
|
if (zone === 'sidebar') {
|
|
@@ -3761,10 +3794,8 @@ async function addNote(event, taskId, sessionId) {
|
|
|
3761
3794
|
function closeDetailPanel() {
|
|
3762
3795
|
detailPanel.classList.remove('visible');
|
|
3763
3796
|
document.getElementById('delete-task-btn').style.display = 'none';
|
|
3764
|
-
// Keep the task selected
|
|
3765
|
-
taskHighlightDimmed =
|
|
3766
|
-
const sel = document.querySelector('.task-card.selected');
|
|
3767
|
-
if (sel) sel.classList.remove('selected');
|
|
3797
|
+
// Keep the task selected AND highlighted after closing (no dim-off).
|
|
3798
|
+
taskHighlightDimmed = false;
|
|
3768
3799
|
}
|
|
3769
3800
|
|
|
3770
3801
|
let deleteTaskId = null;
|
|
@@ -4618,7 +4649,10 @@ document.addEventListener('keydown', (e) => {
|
|
|
4618
4649
|
return;
|
|
4619
4650
|
}
|
|
4620
4651
|
if (e.key === 'Escape') {
|
|
4621
|
-
|
|
4652
|
+
// Plain unfocus — drop the keyboard cursor without jumping into the board
|
|
4653
|
+
document.querySelector('.sidebar').classList.remove('sidebar-focused');
|
|
4654
|
+
clearKbSelection();
|
|
4655
|
+
focusZone = 'board';
|
|
4622
4656
|
return;
|
|
4623
4657
|
}
|
|
4624
4658
|
}
|
|
@@ -4669,6 +4703,11 @@ document.addEventListener('keydown', (e) => {
|
|
|
4669
4703
|
if (detailPanel.classList.contains('visible')) closeDetailPanel();
|
|
4670
4704
|
else if (agentLogMode) exitAgentLogMode();
|
|
4671
4705
|
else if (messagePanelOpen) toggleMessagePanel();
|
|
4706
|
+
else {
|
|
4707
|
+
// Nothing open — plain unfocus: drop the task-card selection highlight
|
|
4708
|
+
clearTaskSelection();
|
|
4709
|
+
selectedTaskId = null;
|
|
4710
|
+
}
|
|
4672
4711
|
return;
|
|
4673
4712
|
}
|
|
4674
4713
|
|
package/public/style.css
CHANGED
|
@@ -454,22 +454,15 @@ body::before {
|
|
|
454
454
|
padding: 12px 14px;
|
|
455
455
|
margin-bottom: 4px;
|
|
456
456
|
background: transparent;
|
|
457
|
-
border: 1px solid
|
|
457
|
+
border: 1px solid var(--border);
|
|
458
458
|
border-radius: 8px;
|
|
459
459
|
text-align: left;
|
|
460
460
|
cursor: pointer;
|
|
461
461
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
462
|
-
box-shadow:
|
|
463
|
-
0 1px 3px rgba(0, 0, 0, 0.12),
|
|
464
|
-
0 1px 2px rgba(0, 0, 0, 0.08);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
.session-item:hover {
|
|
468
|
-
background: var(--bg-hover);
|
|
469
462
|
}
|
|
470
463
|
|
|
471
464
|
.session-item.active {
|
|
472
|
-
background: var(--bg-
|
|
465
|
+
background: var(--bg-deep);
|
|
473
466
|
border-color: var(--accent);
|
|
474
467
|
}
|
|
475
468
|
|
|
@@ -1173,7 +1166,7 @@ body::before {
|
|
|
1173
1166
|
height: 100vh;
|
|
1174
1167
|
background: var(--bg-surface);
|
|
1175
1168
|
border-left: 1px solid color-mix(in srgb, var(--border) 35%, transparent);
|
|
1176
|
-
box-shadow: -
|
|
1169
|
+
box-shadow: -1px 0 12px rgba(0, 0, 0, 0.04);
|
|
1177
1170
|
display: none;
|
|
1178
1171
|
flex-direction: column;
|
|
1179
1172
|
z-index: 100;
|
|
@@ -2011,7 +2004,7 @@ body::before {
|
|
|
2011
2004
|
height: 100vh;
|
|
2012
2005
|
background: var(--bg-deep);
|
|
2013
2006
|
border-left: 1px solid color-mix(in srgb, var(--border) 35%, transparent);
|
|
2014
|
-
box-shadow: -
|
|
2007
|
+
box-shadow: -1px 0 12px rgba(0, 0, 0, 0.04);
|
|
2015
2008
|
display: none;
|
|
2016
2009
|
flex-direction: column;
|
|
2017
2010
|
z-index: 99;
|
|
@@ -4228,11 +4221,6 @@ pre.mermaid svg {
|
|
|
4228
4221
|
width: 0;
|
|
4229
4222
|
}
|
|
4230
4223
|
|
|
4231
|
-
.sidebar-focused .session-item.active {
|
|
4232
|
-
background: transparent;
|
|
4233
|
-
border-color: var(--accent);
|
|
4234
|
-
}
|
|
4235
|
-
|
|
4236
4224
|
.sidebar-focused .session-item.active::before {
|
|
4237
4225
|
width: 0;
|
|
4238
4226
|
}
|
package/server.js
CHANGED