claude-code-kanban 2.4.0 → 3.0.0-rc.2
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/install.js +148 -175
- package/package.json +2 -3
- package/plugin/.claude-plugin/marketplace.json +13 -0
- package/plugin/plugins/claude-code-kanban/.claude-plugin/plugin.json +6 -0
- package/plugin/plugins/claude-code-kanban/hooks/hooks.json +88 -0
- package/{hooks → plugin/plugins/claude-code-kanban/scripts}/agent-spy.sh +8 -6
- package/{hooks → plugin/plugins/claude-code-kanban/scripts}/context-status.sh +2 -2
- package/server.js +3 -2
package/install.js
CHANGED
|
@@ -7,24 +7,13 @@ const readline = require('readline');
|
|
|
7
7
|
const { execSync } = require('child_process');
|
|
8
8
|
|
|
9
9
|
const CLAUDE_DIR = path.join(os.homedir(), '.claude');
|
|
10
|
+
const CCK_DIR = path.join(CLAUDE_DIR, '.cck');
|
|
10
11
|
const HOOKS_DIR = path.join(CLAUDE_DIR, 'hooks');
|
|
11
12
|
const SETTINGS_PATH = path.join(CLAUDE_DIR, 'settings.json');
|
|
12
|
-
const
|
|
13
|
-
const
|
|
13
|
+
const PLUGIN_SRC = path.join(__dirname, 'plugin');
|
|
14
|
+
const PLUGIN_DEST = path.join(CCK_DIR, 'plugin');
|
|
15
|
+
const CTX_SCRIPT_SRC = path.join(PLUGIN_SRC, 'plugins', 'claude-code-kanban', 'scripts', 'context-status.sh');
|
|
14
16
|
const CTX_SCRIPT_DEST = path.join(HOOKS_DIR, 'context-status.sh');
|
|
15
|
-
const CTX_SCRIPT_SRC = path.join(__dirname, 'hooks', 'context-status.sh');
|
|
16
|
-
const AGENT_ACTIVITY_DIR = path.join(CLAUDE_DIR, 'agent-activity');
|
|
17
|
-
|
|
18
|
-
const HOOK_COMMAND = '~/.claude/hooks/agent-spy.sh';
|
|
19
|
-
const HOOK_EVENTS = [
|
|
20
|
-
{ event: 'SessionStart' },
|
|
21
|
-
{ event: 'SubagentStart' },
|
|
22
|
-
{ event: 'SubagentStop' },
|
|
23
|
-
{ event: 'TeammateIdle' },
|
|
24
|
-
{ event: 'PermissionRequest' },
|
|
25
|
-
{ event: 'PreToolUse', matcher: 'AskUserQuestion' },
|
|
26
|
-
{ event: 'PostToolUse' },
|
|
27
|
-
];
|
|
28
17
|
|
|
29
18
|
// ANSI helpers
|
|
30
19
|
const green = s => `\x1b[32m${s}\x1b[0m`;
|
|
@@ -43,136 +32,136 @@ function prompt(question) {
|
|
|
43
32
|
});
|
|
44
33
|
}
|
|
45
34
|
|
|
46
|
-
|
|
47
|
-
console.log(`\n ${bold('claude-code-kanban')} — Agent Log hook installer\n`);
|
|
48
|
-
|
|
49
|
-
// 1. Check bash
|
|
50
|
-
process.stdout.write(' Checking bash... ');
|
|
35
|
+
function runCLI(cmd, okPatterns = []) {
|
|
51
36
|
try {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
} catch {
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
|
|
37
|
+
const out = execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
38
|
+
return { ok: true, output: out };
|
|
39
|
+
} catch (e) {
|
|
40
|
+
const stderr = e.stderr?.trim() || e.message;
|
|
41
|
+
if (okPatterns.some(p => stderr.includes(p))) return { ok: true, idempotent: true };
|
|
42
|
+
return { ok: false, error: stderr };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function copyScript(src, dest) {
|
|
47
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
48
|
+
fs.copyFileSync(src, dest);
|
|
49
|
+
try { fs.chmodSync(dest, 0o755); } catch {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function copyDirSync(src, dest) {
|
|
53
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
54
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
55
|
+
const srcPath = path.join(src, entry.name);
|
|
56
|
+
const destPath = path.join(dest, entry.name);
|
|
57
|
+
if (entry.isDirectory()) {
|
|
58
|
+
copyDirSync(srcPath, destPath);
|
|
58
59
|
} else {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
console.log(` ${dim('Hook scripts use #!/bin/bash and require a bash environment')}`);
|
|
62
|
-
if (!(await prompt(` Continue anyway? [Y/n] `))) {
|
|
63
|
-
console.log(`\n ${dim('Install cancelled.')}\n`);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
60
|
+
fs.copyFileSync(srcPath, destPath);
|
|
61
|
+
try { fs.chmodSync(destPath, 0o755); } catch {}
|
|
66
62
|
}
|
|
67
63
|
}
|
|
64
|
+
}
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
async function runInstall() {
|
|
67
|
+
console.log(`\n ${bold('claude-code-kanban')} — Plugin & StatusLine installer\n`);
|
|
68
|
+
|
|
69
|
+
// 1. Check prerequisites
|
|
70
|
+
process.stdout.write(' Checking claude CLI... ');
|
|
71
|
+
const claude = runCLI('claude --version');
|
|
72
|
+
if (claude.ok) {
|
|
73
|
+
console.log(green(`✓ found (${claude.output})`));
|
|
74
|
+
} else {
|
|
75
|
+
console.log(red('✗ claude CLI not found'));
|
|
76
|
+
console.log(` ${dim('Install Claude Code CLI first: https://docs.anthropic.com/en/docs/claude-code')}`);
|
|
77
|
+
return;
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
console.log(` ${green('✓')} Up to date`);
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
if (await prompt(` Different version found. Update? [Y/n] `)) {
|
|
88
|
-
fs.mkdirSync(HOOKS_DIR, { recursive: true });
|
|
89
|
-
fs.copyFileSync(src, dest);
|
|
90
|
-
try { fs.chmodSync(dest, 0o755); } catch {}
|
|
91
|
-
console.log(` ${green('✓')} Updated`);
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
console.log(` ${dim('Skipped')}`);
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
if (await prompt(` Not found. Install? [Y/n] `)) {
|
|
98
|
-
fs.mkdirSync(HOOKS_DIR, { recursive: true });
|
|
99
|
-
fs.copyFileSync(src, dest);
|
|
100
|
-
try { fs.chmodSync(dest, 0o755); } catch {}
|
|
101
|
-
console.log(` ${green('✓')} Installed and set executable`);
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
console.log(` ${dim('Skipped')}`);
|
|
105
|
-
return false;
|
|
80
|
+
process.stdout.write(' Checking jq... ');
|
|
81
|
+
const jq = runCLI('jq --version');
|
|
82
|
+
if (jq.ok) {
|
|
83
|
+
console.log(green(`✓ found (${jq.output})`));
|
|
84
|
+
} else {
|
|
85
|
+
console.log(yellow('⚠ not found — hook scripts require jq for JSON parsing'));
|
|
106
86
|
}
|
|
107
87
|
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
88
|
+
// 2. Copy plugin to stable location & register marketplace
|
|
89
|
+
console.log(`\n Plugin: ${dim(PLUGIN_DEST)}`);
|
|
90
|
+
if (await prompt(` Install claude-code-kanban plugin? [Y/n] `)) {
|
|
91
|
+
process.stdout.write(' Copying plugin to ~/.claude/.cck/plugin... ');
|
|
92
|
+
try {
|
|
93
|
+
if (fs.existsSync(PLUGIN_DEST)) fs.rmSync(PLUGIN_DEST, { recursive: true, force: true });
|
|
94
|
+
copyDirSync(PLUGIN_SRC, PLUGIN_DEST);
|
|
95
|
+
console.log(green('✓'));
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.log(red(`✗ ${e.message}`));
|
|
98
|
+
}
|
|
111
99
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (fs.existsSync(SETTINGS_PATH)) {
|
|
117
|
-
settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf8'));
|
|
100
|
+
process.stdout.write(' Registering marketplace... ');
|
|
101
|
+
const mkt = runCLI(`claude plugin marketplace add "${PLUGIN_DEST}"`, ['already', 'exists']);
|
|
102
|
+
if (mkt.ok) {
|
|
103
|
+
console.log(green(mkt.idempotent ? '✓ already registered' : '✓'));
|
|
118
104
|
} else {
|
|
119
|
-
|
|
105
|
+
console.log(yellow(`⚠ ${mkt.error}`));
|
|
120
106
|
}
|
|
121
|
-
} catch (e) {
|
|
122
|
-
console.log(` ${red('✗')} Malformed JSON in settings.json — aborting settings update`);
|
|
123
|
-
printSummary(hookInstalled, false);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
107
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
);
|
|
136
|
-
if (!exists) needed.push({ event, matcher: matcherStr });
|
|
108
|
+
const inst = runCLI('claude plugin install claude-code-kanban@claude-code-kanban', ['already installed', 'already exists']);
|
|
109
|
+
if (inst.ok) {
|
|
110
|
+
console.log(` ${green('✓')} ${inst.idempotent ? 'Already installed' : 'Plugin installed'}`);
|
|
111
|
+
} else {
|
|
112
|
+
console.log(` ${red('✗')} Plugin install failed: ${inst.error}`);
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
console.log(` ${dim('Skipped')}`);
|
|
137
116
|
}
|
|
138
117
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
fs.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
153
|
-
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\n');
|
|
154
|
-
console.log(` ${green('✓')} ${needed.length} hook entries added`);
|
|
155
|
-
settingsUpdated = true;
|
|
118
|
+
// 3. StatusLine setup (context-status.sh must be copied globally since statusLine is not plugin-scoped)
|
|
119
|
+
console.log(`\n Context spy: ${dim(CTX_SCRIPT_DEST)}`);
|
|
120
|
+
let ctxInstalled = false;
|
|
121
|
+
if (fs.existsSync(CTX_SCRIPT_DEST)) {
|
|
122
|
+
const existing = fs.readFileSync(CTX_SCRIPT_DEST, 'utf8');
|
|
123
|
+
const bundled = fs.readFileSync(CTX_SCRIPT_SRC, 'utf8');
|
|
124
|
+
if (existing === bundled) {
|
|
125
|
+
console.log(` ${green('✓')} Up to date`);
|
|
126
|
+
ctxInstalled = true;
|
|
127
|
+
} else if (await prompt(` Different version found. Update? [Y/n] `)) {
|
|
128
|
+
copyScript(CTX_SCRIPT_SRC, CTX_SCRIPT_DEST);
|
|
129
|
+
console.log(` ${green('✓')} Updated`);
|
|
130
|
+
ctxInstalled = true;
|
|
156
131
|
} else {
|
|
157
132
|
console.log(` ${dim('Skipped')}`);
|
|
158
133
|
}
|
|
134
|
+
} else if (await prompt(` Not found. Install? [Y/n] `)) {
|
|
135
|
+
copyScript(CTX_SCRIPT_SRC, CTX_SCRIPT_DEST);
|
|
136
|
+
console.log(` ${green('✓')} Installed`);
|
|
137
|
+
ctxInstalled = true;
|
|
138
|
+
} else {
|
|
139
|
+
console.log(` ${dim('Skipped')}`);
|
|
159
140
|
}
|
|
160
141
|
|
|
161
|
-
//
|
|
162
|
-
const CTX_COMMAND = '~/.claude/hooks/context-status.sh';
|
|
163
|
-
let statusLineUpdated = false;
|
|
142
|
+
// 4. StatusLine config in settings.json
|
|
164
143
|
if (ctxInstalled) {
|
|
144
|
+
let settings;
|
|
145
|
+
try {
|
|
146
|
+
settings = fs.existsSync(SETTINGS_PATH)
|
|
147
|
+
? JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf8'))
|
|
148
|
+
: {};
|
|
149
|
+
} catch {
|
|
150
|
+
console.log(` ${red('✗')} Malformed JSON in settings.json — skipping statusline config`);
|
|
151
|
+
printSummary();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const CTX_COMMAND = '~/.claude/hooks/context-status.sh';
|
|
165
156
|
const hasCtx = settings.statusLine?.command?.includes('context-status.sh');
|
|
166
157
|
if (hasCtx) {
|
|
167
158
|
console.log(`\n StatusLine: ${green('✓')} Already configured`);
|
|
168
|
-
statusLineUpdated = true;
|
|
169
159
|
} else if (!settings.statusLine) {
|
|
170
160
|
console.log(`\n StatusLine: ${dim('not configured')}`);
|
|
171
161
|
if (await prompt(` Set up context tracking statusline? [Y/n] `)) {
|
|
172
162
|
settings.statusLine = { command: CTX_COMMAND };
|
|
173
163
|
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\n');
|
|
174
164
|
console.log(` ${green('✓')} StatusLine configured`);
|
|
175
|
-
statusLineUpdated = true;
|
|
176
165
|
} else {
|
|
177
166
|
console.log(` ${dim('Skipped')}`);
|
|
178
167
|
}
|
|
@@ -183,51 +172,63 @@ async function runInstall() {
|
|
|
183
172
|
settings.statusLine.command = `${CTX_COMMAND} | ${existing}`;
|
|
184
173
|
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\n');
|
|
185
174
|
console.log(` ${green('✓')} StatusLine updated`);
|
|
186
|
-
statusLineUpdated = true;
|
|
187
175
|
} else {
|
|
188
176
|
console.log(` ${dim('Skipped')}`);
|
|
189
177
|
}
|
|
190
178
|
}
|
|
191
179
|
}
|
|
192
180
|
|
|
193
|
-
printSummary(
|
|
181
|
+
printSummary();
|
|
194
182
|
}
|
|
195
183
|
|
|
196
|
-
function printSummary(
|
|
197
|
-
console.log('');
|
|
198
|
-
if (hookOk && settingsOk) {
|
|
199
|
-
console.log(` ${green('Agent Log will appear in the Kanban footer when subagents are active.')}`);
|
|
200
|
-
} else {
|
|
201
|
-
console.log(` ${yellow('Partial install — re-run --install to complete setup.')}`);
|
|
202
|
-
}
|
|
203
|
-
console.log('');
|
|
184
|
+
function printSummary() {
|
|
185
|
+
console.log(`\n ${green('Setup complete. Agent activity will appear in the Kanban dashboard.')}\n`);
|
|
204
186
|
}
|
|
205
187
|
|
|
206
188
|
async function runUninstall() {
|
|
207
|
-
console.log(`\n ${bold('claude-code-kanban')} —
|
|
189
|
+
console.log(`\n ${bold('claude-code-kanban')} — Uninstaller\n`);
|
|
190
|
+
|
|
191
|
+
// 1. Uninstall plugin via Claude CLI
|
|
192
|
+
process.stdout.write(' Removing plugin... ');
|
|
193
|
+
const uninst = runCLI('claude plugin uninstall claude-code-kanban', ['not found', 'not installed']);
|
|
194
|
+
if (uninst.ok) {
|
|
195
|
+
console.log(uninst.idempotent ? dim('Not installed') : green('✓ Removed'));
|
|
196
|
+
} else {
|
|
197
|
+
console.log(yellow(`⚠ ${uninst.error}`));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 2. Remove marketplace
|
|
201
|
+
process.stdout.write(' Removing marketplace... ');
|
|
202
|
+
const rmMkt = runCLI('claude plugin marketplace remove claude-code-kanban', ['not found', 'not configured']);
|
|
203
|
+
if (rmMkt.ok) {
|
|
204
|
+
console.log(rmMkt.idempotent ? dim('Not configured') : green('✓ Removed'));
|
|
205
|
+
} else {
|
|
206
|
+
console.log(yellow(`⚠ ${rmMkt.error}`));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 3. Remove plugin copy
|
|
210
|
+
if (fs.existsSync(PLUGIN_DEST)) {
|
|
211
|
+
fs.rmSync(PLUGIN_DEST, { recursive: true, force: true });
|
|
212
|
+
console.log(` Plugin copy: ${green('✓')} Removed`);
|
|
213
|
+
} else {
|
|
214
|
+
console.log(` Plugin copy: ${dim('Not found')}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 4. Remove context-status.sh copy
|
|
218
|
+
if (fs.existsSync(CTX_SCRIPT_DEST)) {
|
|
219
|
+
fs.unlinkSync(CTX_SCRIPT_DEST);
|
|
220
|
+
console.log(` Context spy: ${green('✓')} Removed`);
|
|
221
|
+
} else {
|
|
222
|
+
console.log(` Context spy: ${dim('Not found')}`);
|
|
223
|
+
}
|
|
208
224
|
|
|
209
|
-
//
|
|
225
|
+
// 5. Clean up settings.json (statusLine)
|
|
210
226
|
if (fs.existsSync(SETTINGS_PATH)) {
|
|
211
227
|
try {
|
|
212
228
|
const settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf8'));
|
|
213
|
-
let
|
|
214
|
-
if (settings.hooks) {
|
|
215
|
-
const eventNames = [...new Set(HOOK_EVENTS.map(e => e.event))];
|
|
216
|
-
for (const event of eventNames) {
|
|
217
|
-
if (!Array.isArray(settings.hooks[event])) continue;
|
|
218
|
-
const before = settings.hooks[event].length;
|
|
219
|
-
settings.hooks[event] = settings.hooks[event].map(g => {
|
|
220
|
-
if (!g.hooks?.some(h => h.command === HOOK_COMMAND)) return g;
|
|
221
|
-
const filtered = g.hooks.filter(h => h.command !== HOOK_COMMAND);
|
|
222
|
-
return filtered.length > 0 ? { ...g, hooks: filtered } : null;
|
|
223
|
-
}).filter(Boolean);
|
|
224
|
-
removed += before - settings.hooks[event].length;
|
|
225
|
-
if (settings.hooks[event].length === 0) delete settings.hooks[event];
|
|
226
|
-
}
|
|
227
|
-
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
228
|
-
}
|
|
229
|
+
let changed = false;
|
|
229
230
|
|
|
230
|
-
// Strip context-status.sh from statusLine
|
|
231
|
+
// Strip context-status.sh from statusLine
|
|
231
232
|
if (settings.statusLine?.command?.includes('context-status.sh')) {
|
|
232
233
|
const cmd = settings.statusLine.command;
|
|
233
234
|
const stripped = cmd.replace(/~\/\.claude\/hooks\/context-status\.sh\s*\|\s*/, '').trim();
|
|
@@ -238,43 +239,15 @@ async function runUninstall() {
|
|
|
238
239
|
delete settings.statusLine;
|
|
239
240
|
console.log(` StatusLine: ${green('✓')} Removed`);
|
|
240
241
|
}
|
|
242
|
+
changed = true;
|
|
241
243
|
}
|
|
242
244
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
console.log(` Settings: ${green('✓')} Removed ${removed} hook entries`);
|
|
246
|
-
} else {
|
|
247
|
-
console.log(` Settings: ${dim('No hook entries found')}`);
|
|
245
|
+
if (changed) {
|
|
246
|
+
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\n');
|
|
248
247
|
}
|
|
249
248
|
} catch {
|
|
250
249
|
console.log(` Settings: ${red('✗')} Could not parse settings.json`);
|
|
251
250
|
}
|
|
252
|
-
} else {
|
|
253
|
-
console.log(` Settings: ${dim('No settings.json found')}`);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// 2. Remove hook scripts
|
|
257
|
-
if (fs.existsSync(HOOK_SCRIPT_DEST)) {
|
|
258
|
-
fs.unlinkSync(HOOK_SCRIPT_DEST);
|
|
259
|
-
console.log(` Hook script: ${green('✓')} Removed`);
|
|
260
|
-
} else {
|
|
261
|
-
console.log(` Hook script: ${dim('Not found')}`);
|
|
262
|
-
}
|
|
263
|
-
if (fs.existsSync(CTX_SCRIPT_DEST)) {
|
|
264
|
-
fs.unlinkSync(CTX_SCRIPT_DEST);
|
|
265
|
-
console.log(` Context spy: ${green('✓')} Removed`);
|
|
266
|
-
} else {
|
|
267
|
-
console.log(` Context spy: ${dim('Not found')}`);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// 3. Optionally remove agent-activity data
|
|
271
|
-
if (fs.existsSync(AGENT_ACTIVITY_DIR)) {
|
|
272
|
-
if (await prompt(`\n Remove agent activity data (${AGENT_ACTIVITY_DIR})? [y/N] `)) {
|
|
273
|
-
fs.rmSync(AGENT_ACTIVITY_DIR, { recursive: true, force: true });
|
|
274
|
-
console.log(` ${green('✓')} Agent activity data removed`);
|
|
275
|
-
} else {
|
|
276
|
-
console.log(` ${dim('Kept agent activity data')}`);
|
|
277
|
-
}
|
|
278
251
|
}
|
|
279
252
|
|
|
280
253
|
console.log(`\n ${green('Uninstall complete.')}\n`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-kanban",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-rc.2",
|
|
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": {
|
|
@@ -45,8 +45,7 @@
|
|
|
45
45
|
"files": [
|
|
46
46
|
"server.js",
|
|
47
47
|
"install.js",
|
|
48
|
-
"
|
|
49
|
-
"hooks/context-status.sh",
|
|
48
|
+
"plugin/**/*",
|
|
50
49
|
"lib/**/*",
|
|
51
50
|
"public/**/*"
|
|
52
51
|
],
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-code-kanban",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "NikiforovAll"
|
|
5
|
+
},
|
|
6
|
+
"plugins": [
|
|
7
|
+
{
|
|
8
|
+
"name": "claude-code-kanban",
|
|
9
|
+
"source": "./plugins/claude-code-kanban",
|
|
10
|
+
"description": "Agent activity tracking for claude-code-kanban dashboard"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh",
|
|
10
|
+
"timeout": 5
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"SubagentStart": [
|
|
16
|
+
{
|
|
17
|
+
"matcher": "",
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh",
|
|
22
|
+
"timeout": 5
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"SubagentStop": [
|
|
28
|
+
{
|
|
29
|
+
"matcher": "",
|
|
30
|
+
"hooks": [
|
|
31
|
+
{
|
|
32
|
+
"type": "command",
|
|
33
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh",
|
|
34
|
+
"timeout": 5
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"TeammateIdle": [
|
|
40
|
+
{
|
|
41
|
+
"matcher": "",
|
|
42
|
+
"hooks": [
|
|
43
|
+
{
|
|
44
|
+
"type": "command",
|
|
45
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh",
|
|
46
|
+
"timeout": 5
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"PermissionRequest": [
|
|
52
|
+
{
|
|
53
|
+
"matcher": "",
|
|
54
|
+
"hooks": [
|
|
55
|
+
{
|
|
56
|
+
"type": "command",
|
|
57
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh",
|
|
58
|
+
"timeout": 5
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"PreToolUse": [
|
|
64
|
+
{
|
|
65
|
+
"matcher": "AskUserQuestion",
|
|
66
|
+
"hooks": [
|
|
67
|
+
{
|
|
68
|
+
"type": "command",
|
|
69
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh",
|
|
70
|
+
"timeout": 5
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"PostToolUse": [
|
|
76
|
+
{
|
|
77
|
+
"matcher": "",
|
|
78
|
+
"hooks": [
|
|
79
|
+
{
|
|
80
|
+
"type": "command",
|
|
81
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh",
|
|
82
|
+
"timeout": 5
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Tracks subagent lifecycle: one JSON file per agent, grouped by session
|
|
3
|
-
# Layout: ~/.claude/agent-activity/{sessionId}/{agentId}.json
|
|
3
|
+
# Layout: ~/.claude/.cck/agent-activity/{sessionId}/{agentId}.json
|
|
4
4
|
|
|
5
5
|
INPUT=$(cat)
|
|
6
6
|
|
|
@@ -16,12 +16,14 @@ eval "$(echo "$INPUT" | jq -r '
|
|
|
16
16
|
|
|
17
17
|
[ -z "$SESSION_ID" ] && exit 0
|
|
18
18
|
|
|
19
|
+
CCK_ACTIVITY="$HOME/.claude/.cck/agent-activity"
|
|
20
|
+
|
|
19
21
|
# Map session to custom task list on session start
|
|
20
22
|
if [ "$EVENT" = "SessionStart" ]; then
|
|
21
23
|
TASK_LIST_ID="${CLAUDE_CODE_TASK_LIST_ID:-}"
|
|
22
24
|
if [ -n "$TASK_LIST_ID" ]; then
|
|
23
25
|
CWD=$(echo "$INPUT" | jq -r '.cwd // ""')
|
|
24
|
-
MAPS_DIR="$
|
|
26
|
+
MAPS_DIR="$CCK_ACTIVITY/_task-maps"
|
|
25
27
|
mkdir -p "$MAPS_DIR"
|
|
26
28
|
MAP_FILE="$MAPS_DIR/$TASK_LIST_ID.json"
|
|
27
29
|
TMP_FILE="$MAP_FILE.$$"
|
|
@@ -36,7 +38,7 @@ fi
|
|
|
36
38
|
|
|
37
39
|
# PostToolUse / non-waiting PreToolUse: clear waiting state
|
|
38
40
|
if [ "$EVENT" = "PostToolUse" ] || { [ "$EVENT" = "PreToolUse" ] && [ "$TOOL_NAME" != "AskUserQuestion" ]; }; then
|
|
39
|
-
WFILE="$
|
|
41
|
+
WFILE="$CCK_ACTIVITY/$SESSION_ID/_waiting.json"
|
|
40
42
|
rm -f "$WFILE"
|
|
41
43
|
[ "$EVENT" = "PostToolUse" ] && exit 0
|
|
42
44
|
fi
|
|
@@ -46,7 +48,7 @@ fi
|
|
|
46
48
|
|
|
47
49
|
# Waiting-for-user events → write _waiting.json marker
|
|
48
50
|
if [ "$EVENT" = "PermissionRequest" ] || { [ "$EVENT" = "PreToolUse" ] && [ "$TOOL_NAME" = "AskUserQuestion" ]; }; then
|
|
49
|
-
DIR="$
|
|
51
|
+
DIR="$CCK_ACTIVITY/$SESSION_ID"
|
|
50
52
|
mkdir -p "$DIR"
|
|
51
53
|
KIND="permission"
|
|
52
54
|
[ "$EVENT" = "PreToolUse" ] && KIND="question"
|
|
@@ -63,7 +65,7 @@ fi
|
|
|
63
65
|
|
|
64
66
|
# TeammateIdle has no agent_id — resolve via name→id mapping file
|
|
65
67
|
if [ "$EVENT" = "TeammateIdle" ] && [ -z "$AGENT_ID" ] && [ -n "$TEAMMATE_NAME" ]; then
|
|
66
|
-
DIR="$
|
|
68
|
+
DIR="$CCK_ACTIVITY/$SESSION_ID"
|
|
67
69
|
MAP_FILE="$DIR/_name-${TEAMMATE_NAME}.id"
|
|
68
70
|
[ ! -f "$MAP_FILE" ] && exit 0
|
|
69
71
|
AGENT_ID=$(cat "$MAP_FILE")
|
|
@@ -83,7 +85,7 @@ fi
|
|
|
83
85
|
|
|
84
86
|
[ -z "$AGENT_ID" ] && exit 0
|
|
85
87
|
|
|
86
|
-
DIR="$
|
|
88
|
+
DIR="$CCK_ACTIVITY/$SESSION_ID"
|
|
87
89
|
FILE="$DIR/$AGENT_ID.json"
|
|
88
90
|
|
|
89
91
|
# On Start: skip if no type (internal agents like AskUserQuestion)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Statusline spy: writes raw context data for kanban dashboard, passes input through
|
|
3
|
-
# Layout: ~/.claude/context-status/{sessionId}.json
|
|
3
|
+
# Layout: ~/.claude/.cck/context-status/{sessionId}.json
|
|
4
4
|
#
|
|
5
5
|
# Usage: pipe before your statusline command:
|
|
6
6
|
# "command": "~/.claude/hooks/context-status.sh | npx -y ccstatusline@latest"
|
|
@@ -9,7 +9,7 @@ INPUT=$(cat)
|
|
|
9
9
|
|
|
10
10
|
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""')
|
|
11
11
|
if [ -n "$SESSION_ID" ]; then
|
|
12
|
-
DIR="$HOME/.claude/context-status"
|
|
12
|
+
DIR="$HOME/.claude/.cck/context-status"
|
|
13
13
|
mkdir -p "$DIR"
|
|
14
14
|
echo "$INPUT" > "$DIR/$SESSION_ID.json"
|
|
15
15
|
fi
|
package/server.js
CHANGED
|
@@ -64,8 +64,9 @@ const TASKS_DIR = path.join(CLAUDE_DIR, 'tasks');
|
|
|
64
64
|
const PROJECTS_DIR = path.join(CLAUDE_DIR, 'projects');
|
|
65
65
|
const TEAMS_DIR = path.join(CLAUDE_DIR, 'teams');
|
|
66
66
|
const PLANS_DIR = path.join(CLAUDE_DIR, 'plans');
|
|
67
|
-
const
|
|
68
|
-
const
|
|
67
|
+
const CCK_DIR = path.join(CLAUDE_DIR, '.cck');
|
|
68
|
+
const AGENT_ACTIVITY_DIR = path.join(CCK_DIR, 'agent-activity');
|
|
69
|
+
const CONTEXT_STATUS_DIR = path.join(CCK_DIR, 'context-status');
|
|
69
70
|
|
|
70
71
|
const PERMISSION_TTL_MS = 1800000;
|
|
71
72
|
const AGENT_TTL_MS = 3600000;
|