claude-code-kanban 7.4.0 → 7.6.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 (48) hide show
  1. package/README.md +5 -3
  2. package/cli.js +95 -29
  3. package/hub-app.json +5 -0
  4. package/lib/panes.js +41 -6
  5. package/lib/parsers.js +42 -3
  6. package/lib/retention.js +54 -6
  7. package/lib/scratchpad-dir.js +19 -2
  8. package/lib/session-cache.js +1 -1
  9. package/lib/session-events.js +174 -47
  10. package/lib/show.js +385 -0
  11. package/lib/task-dir.js +26 -0
  12. package/lib/terminal-client.js +14 -1
  13. package/lib/terminal-host.js +2 -1
  14. package/lib/terminal.js +28 -8
  15. package/package.json +1 -1
  16. package/plugin/plugins/claude-code-kanban/.claude-plugin/plugin.json +2 -5
  17. package/plugin/plugins/claude-code-kanban/hooks/doorbell.ts +136 -0
  18. package/plugin/plugins/claude-code-kanban/hooks/index.ts +4 -0
  19. package/plugin/plugins/claude-code-kanban/hooks/show.ts +120 -0
  20. package/plugin/plugins/claude-code-kanban/skills/dispatch/SKILL.md +2 -0
  21. package/plugin/plugins/claude-code-kanban/skills/kanban/SKILL.md +3 -3
  22. package/plugin/plugins/claude-code-kanban/skills/show/SKILL.md +45 -0
  23. package/plugin/plugins/claude-code-kanban/skills/show/references/NOTICE.md +27 -0
  24. package/plugin/plugins/claude-code-kanban/skills/show/references/chart.md +58 -0
  25. package/plugin/plugins/claude-code-kanban/skills/show/references/diagram/architecture.md +18 -0
  26. package/plugin/plugins/claude-code-kanban/skills/show/references/diagram/er.md +10 -0
  27. package/plugin/plugins/claude-code-kanban/skills/show/references/diagram/flow.md +13 -0
  28. package/plugin/plugins/claude-code-kanban/skills/show/references/diagram/layers.md +10 -0
  29. package/plugin/plugins/claude-code-kanban/skills/show/references/diagram/sequence.md +19 -0
  30. package/plugin/plugins/claude-code-kanban/skills/show/references/diagram/state.md +12 -0
  31. package/plugin/plugins/claude-code-kanban/skills/show/references/diagram/swimlane.md +10 -0
  32. package/plugin/plugins/claude-code-kanban/skills/show/references/diagram/timeline.md +9 -0
  33. package/plugin/plugins/claude-code-kanban/skills/show/references/diagram/tree.md +10 -0
  34. package/plugin/plugins/claude-code-kanban/skills/show/references/diagram.md +87 -0
  35. package/plugin/plugins/claude-code-kanban/skills/show/references/explain.md +98 -0
  36. package/plugin/plugins/claude-code-kanban/skills/show/references/mockup.md +30 -0
  37. package/plugin/plugins/claude-code-kanban/tests/doorbell.test.ts +133 -0
  38. package/plugin/plugins/claude-code-kanban/tests/show.test.ts +137 -0
  39. package/public/app.js +2151 -1108
  40. package/public/index.html +19 -36
  41. package/public/style.css +1047 -125
  42. package/public/terminal-frame.js +0 -2
  43. package/public/tree-highlight.js +137 -15
  44. package/server.js +237 -178
  45. package/lib/dispatch.js +0 -32
  46. package/plugin/plugins/claude-code-kanban/monitors.json +0 -8
  47. package/plugin/plugins/claude-code-kanban/scripts/postman.js +0 -82
  48. package/plugin/plugins/claude-code-kanban/skills/follow/SKILL.md +0 -47
@@ -313,8 +313,6 @@
313
313
  return open(m);
314
314
  case 'detach':
315
315
  return detach();
316
- case 'send':
317
- return send(m.msg);
318
316
  case 'theme':
319
317
  return applyTheme(m.options);
320
318
  case 'font':
@@ -11,7 +11,8 @@ const treeHighlight = (() => {
11
11
  String.raw`^([+-](?=[ \t│├└▼]|$))?([ \t]*(?:[│├└─▼↓→ \t]*[│├└─▼↓→][ \t]*)?)(${NAME})?(.*)$`,
12
12
  'su',
13
13
  );
14
- const INDENT_NODE_LINE = new RegExp(String.raw`^([ \t]*)${NAME}(\([^()]*\))?( {2,}\S.*)?$`, 'u');
14
+ const FRAME = new RegExp(String.raw`^( *)(~.*?|${NAME}(?:\([^()]*\))?)( \*)?(?:( {2,})(\S(?:.*\S)?))?( *)$`, 'u');
15
+ const LOC = /^[^\s:]+:\d+(?::\d+)?$/;
15
16
 
16
17
  function escapeText(s) {
17
18
  return s
@@ -36,25 +37,61 @@ const treeHighlight = (() => {
36
37
  return false;
37
38
  }
38
39
 
39
- // A call tree drawn with indentation only: every line is one name or call, with an optional
40
- // note after two spaces. Prose and pseudocode have single-spaced words, so they never match.
41
- function looksLikeIndentTree(text, diff) {
42
- const lines = text.split('\n').filter((l) => l.trim());
43
- if (lines.length < 3) return false;
44
- const indents = new Set();
45
- for (const raw of lines) {
46
- if (diff && !/^[ +-]/.test(raw)) return false;
47
- const m = (diff ? raw.slice(1) : raw).match(INDENT_NODE_LINE);
48
- if (!m) return false;
49
- indents.add(m[1].length);
40
+ // The notation of skills/show/references/explain.md: frames indented by spaces, one name or
41
+ // call each, an optional ` *` focus mark, a note after 2+ spaces, `~` folds, and `+`/`-` in
42
+ // column 1 for a change. Prose and pseudocode have single-spaced words, so they never parse.
43
+ function parseStack(text) {
44
+ const lines = text.split('\n');
45
+ const diff = lines.some((l) => /^[+-]/.test(l));
46
+ const rows = [];
47
+ let roots = [];
48
+ let stack = [];
49
+ for (const line of lines) {
50
+ if (!line.trim()) {
51
+ rows.push({ blank: true, text: line });
52
+ roots = [];
53
+ stack = [];
54
+ continue;
55
+ }
56
+ const marker = diff ? line[0] : '';
57
+ if (diff && !/[ +-]/.test(marker)) return null;
58
+ const m = line.slice(marker.length).match(FRAME);
59
+ if (!m) return null;
60
+ const [, ind, label, star = '', gap = '', note = '', tail] = m;
61
+ while (stack.length && stack[stack.length - 1].indent >= ind.length) stack.pop();
62
+ const parent = stack[stack.length - 1] || null;
63
+ const sibs = parent ? parent.kids : roots;
64
+ const row = {
65
+ id: rows.length,
66
+ marker,
67
+ indent: ind.length,
68
+ parent,
69
+ sibs,
70
+ label,
71
+ fold: label.startsWith('~'),
72
+ star,
73
+ gap,
74
+ note,
75
+ tail,
76
+ kids: [],
77
+ };
78
+ sibs.push(row);
79
+ stack.push(row);
80
+ rows.push(row);
50
81
  }
51
- return indents.size >= 2;
82
+ return rows;
83
+ }
84
+
85
+ // An untagged call tree: 3 frames at least, on 2 indent levels at least.
86
+ function looksLikeIndentTree(text) {
87
+ const frames = parseStack(text)?.filter((r) => !r.blank) || [];
88
+ return frames.length >= 3 && new Set(frames.map((r) => r.indent)).size >= 2;
52
89
  }
53
90
 
54
91
  function wantsTree(lang, text) {
55
92
  const tag = (lang || '').trim().toLowerCase();
56
93
  if (TREE_TAGS.has(tag)) return true;
57
- return PLAIN_TAGS.has(tag) && (looksLikeTree(text) || looksLikeIndentTree(text, tag === 'diff'));
94
+ return PLAIN_TAGS.has(tag) && (looksLikeTree(text) || looksLikeIndentTree(text));
58
95
  }
59
96
 
60
97
  function highlightLine(line) {
@@ -68,7 +105,92 @@ const treeHighlight = (() => {
68
105
  return text.split('\n').map(highlightLine).join('\n');
69
106
  }
70
107
 
71
- return { wantsTree, highlightTree };
108
+ const isLast = (row) => row.sibs[row.sibs.length - 1] === row;
109
+
110
+ function guideCells(row) {
111
+ const g = Array(row.indent).fill('');
112
+ if (row.parent) {
113
+ for (let a = row.parent; a.parent; a = a.parent) if (!isLast(a)) g[a.parent.indent] = 'v';
114
+ g[row.parent.indent] = isLast(row) ? 'l' : 't';
115
+ if (row.parent.indent + 1 < row.indent) g[row.parent.indent + 1] = 'h';
116
+ }
117
+ return g.map((c) => (c ? `<span class="ct-g ct-${c}"> </span>` : ' ')).join('');
118
+ }
119
+
120
+ function renderRow(row) {
121
+ if (row.blank) return `<span class="ct-row">${row.text}</span>`;
122
+ const cls = ['ct-row'];
123
+ if (row.star) cls.push('ct-focus');
124
+ if (row.marker === '+') cls.push('ct-add');
125
+ if (row.marker === '-') cls.push('ct-del');
126
+ const above = [];
127
+ for (let a = row.parent; a; a = a.parent) above.push(a);
128
+ if (above.some((a) => !a.open)) cls.push('ct-hidden');
129
+ if (above.some((a) => a.fold)) cls.push('ct-lib');
130
+ let attrs = above.length ? ` data-ct-in="${above.map((a) => a.id).join(' ')}"` : '';
131
+ let label;
132
+ if (row.fold) {
133
+ cls.push('ct-fold');
134
+ label = `<span class="ct-tilde">~</span>${escapeText(row.label.slice(1))}`;
135
+ } else {
136
+ label = span('ct-name', row.label);
137
+ }
138
+ if (row.kids.length) {
139
+ cls.push('ct-has');
140
+ attrs += ` data-ct="${row.id}" role="button" tabindex="0" aria-expanded="${row.open}"`;
141
+ }
142
+ return (
143
+ `<span class="${cls.join(' ')}"${attrs}>` +
144
+ span('ct-m', row.marker) +
145
+ guideCells(row) +
146
+ label +
147
+ span('ct-star', row.star) +
148
+ row.gap +
149
+ span(LOC.test(row.note) ? 'ct-loc' : 'ct-note', row.note) +
150
+ row.tail +
151
+ '</span>'
152
+ );
153
+ }
154
+
155
+ // Rows are blocks, so the newlines between them sit in hidden spans: the text stays
156
+ // byte-identical and the rows draw no blank lines.
157
+ function renderTree(text) {
158
+ const rows = parseStack(text);
159
+ if (!rows) return `<pre><code class="hljs language-tree">${highlightTree(text)}</code></pre>`;
160
+ for (let i = rows.length - 1; i >= 0; i--) {
161
+ const r = rows[i];
162
+ if (r.parent && (r.star || r.marker === '+' || r.marker === '-' || r.holdsPoint)) r.parent.holdsPoint = true;
163
+ }
164
+ for (const r of rows) r.open = !r.fold || !!r.holdsPoint;
165
+ return `<pre><code class="hljs ct">${rows.map(renderRow).join('<span class="ct-nl">\n</span>')}</code></pre>`;
166
+ }
167
+
168
+ function toggleFold(fold) {
169
+ fold.setAttribute('aria-expanded', String(fold.getAttribute('aria-expanded') !== 'true'));
170
+ const code = fold.closest('code');
171
+ const closed = new Set();
172
+ for (const f of code.querySelectorAll('.ct-has[aria-expanded="false"]')) closed.add(f.dataset.ct);
173
+ for (const r of code.querySelectorAll('[data-ct-in]')) {
174
+ r.classList.toggle(
175
+ 'ct-hidden',
176
+ r.dataset.ctIn.split(' ').some((id) => closed.has(id)),
177
+ );
178
+ }
179
+ }
180
+
181
+ // A note in the LOC form → { path, line }, else null.
182
+ function parseLoc(text) {
183
+ if (!LOC.test(text)) return null;
184
+ const [path, line] = text.split(':');
185
+ return { path, line: Math.max(1, Number(line)) };
186
+ }
187
+
188
+ // A trailing newline does not start a line.
189
+ function lineCount(text) {
190
+ return Math.max(1, text.split('\n').length - (text.endsWith('\n') ? 1 : 0));
191
+ }
192
+
193
+ return { wantsTree, highlightTree, renderTree, toggleFold, parseLoc, lineCount };
72
194
  })();
73
195
 
74
196
  if (typeof module === 'object' && module.exports) module.exports = treeHighlight;