claude-task-viewer 1.5.0 → 1.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 +6 -0
- package/package.json +1 -1
- package/public/index.html +19 -48
- package/server.js +47 -32
package/README.md
CHANGED
|
@@ -26,6 +26,9 @@ Tasks can block other tasks. The viewer shows these relationships clearly — bl
|
|
|
26
26
|
### Notes
|
|
27
27
|
Add context to any task. Your notes are appended to the task description, so Claude sees them when it reads the task. Use this to clarify requirements, add constraints, or redirect work — all without interrupting Claude's flow.
|
|
28
28
|
|
|
29
|
+
### Search
|
|
30
|
+
Find tasks instantly. Type in the search box to filter across all columns by subject or description. Case-insensitive, real-time results.
|
|
31
|
+
|
|
29
32
|
### Project Filtering
|
|
30
33
|
Filter tasks by project using the dropdown. Working on multiple codebases? See only what's relevant. Combine with the session filter to show just active sessions for a specific project.
|
|
31
34
|
|
|
@@ -90,6 +93,9 @@ PORT=8080 npx claude-task-viewer
|
|
|
90
93
|
|
|
91
94
|
# Open browser automatically
|
|
92
95
|
npx claude-task-viewer --open
|
|
96
|
+
|
|
97
|
+
# Use a different Claude config directory (for multiple accounts)
|
|
98
|
+
npx claude-task-viewer --dir=~/.claude-work
|
|
93
99
|
```
|
|
94
100
|
|
|
95
101
|
## API
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -327,6 +327,19 @@
|
|
|
327
327
|
margin-top: 2px;
|
|
328
328
|
white-space: nowrap;
|
|
329
329
|
overflow: hidden;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.session-branch {
|
|
333
|
+
font-size: 10px;
|
|
334
|
+
color: var(--accent);
|
|
335
|
+
margin-top: 3px;
|
|
336
|
+
padding: 2px 6px;
|
|
337
|
+
background: var(--border);
|
|
338
|
+
border-radius: 3px;
|
|
339
|
+
display: inline-block;
|
|
340
|
+
font-family: var(--mono);
|
|
341
|
+
white-space: nowrap;
|
|
342
|
+
overflow: hidden;
|
|
330
343
|
text-overflow: ellipsis;
|
|
331
344
|
}
|
|
332
345
|
|
|
@@ -469,26 +482,6 @@
|
|
|
469
482
|
color: var(--accent);
|
|
470
483
|
}
|
|
471
484
|
|
|
472
|
-
.search-input {
|
|
473
|
-
background: var(--bg-elevated);
|
|
474
|
-
border: 1px solid var(--border);
|
|
475
|
-
border-radius: 6px;
|
|
476
|
-
padding: 8px 12px;
|
|
477
|
-
font-family: var(--mono);
|
|
478
|
-
font-size: 12px;
|
|
479
|
-
color: var(--text-primary);
|
|
480
|
-
width: 200px;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
.search-input:focus {
|
|
484
|
-
outline: none;
|
|
485
|
-
border-color: var(--accent);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
.search-input::placeholder {
|
|
489
|
-
color: var(--text-muted);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
485
|
.icon-btn {
|
|
493
486
|
width: 32px;
|
|
494
487
|
height: 32px;
|
|
@@ -1039,8 +1032,6 @@
|
|
|
1039
1032
|
<p id="session-meta" class="view-meta"></p>
|
|
1040
1033
|
</div>
|
|
1041
1034
|
<div class="view-actions">
|
|
1042
|
-
<input type="text" id="task-search" class="search-input"
|
|
1043
|
-
placeholder="Search tasks..." oninput="searchTasks(this.value)">
|
|
1044
1035
|
<div class="view-progress">
|
|
1045
1036
|
<div class="progress-bar">
|
|
1046
1037
|
<div id="progress-bar" class="progress-fill" style="width: 0%"></div>
|
|
@@ -1112,7 +1103,6 @@
|
|
|
1112
1103
|
let viewMode = 'session';
|
|
1113
1104
|
let sessionFilter = localStorage.getItem('sessionFilter') || 'all'; // 'all' or 'active'
|
|
1114
1105
|
let filterProject = null; // null = all projects, or project path to filter
|
|
1115
|
-
let searchQuery = '';
|
|
1116
1106
|
|
|
1117
1107
|
// DOM
|
|
1118
1108
|
const sessionsList = document.getElementById('sessions-list');
|
|
@@ -1184,7 +1174,6 @@
|
|
|
1184
1174
|
async function fetchTasks(sessionId) {
|
|
1185
1175
|
try {
|
|
1186
1176
|
viewMode = 'session';
|
|
1187
|
-
clearSearch();
|
|
1188
1177
|
const res = await fetch(`/api/sessions/${sessionId}`);
|
|
1189
1178
|
currentTasks = await res.json();
|
|
1190
1179
|
currentSessionId = sessionId;
|
|
@@ -1198,7 +1187,6 @@
|
|
|
1198
1187
|
try {
|
|
1199
1188
|
viewMode = 'all';
|
|
1200
1189
|
currentSessionId = null;
|
|
1201
|
-
clearSearch();
|
|
1202
1190
|
const res = await fetch('/api/tasks/all');
|
|
1203
1191
|
let tasks = await res.json();
|
|
1204
1192
|
if (filterProject) {
|
|
@@ -1282,6 +1270,7 @@
|
|
|
1282
1270
|
${hasInProgress ? '<span class="pulse"></span>' : ''}
|
|
1283
1271
|
</div>
|
|
1284
1272
|
${secondaryName ? `<div class="session-secondary">${escapeHtml(secondaryName)}</div>` : ''}
|
|
1273
|
+
${session.gitBranch ? `<div class="session-branch">${escapeHtml(session.gitBranch)}</div>` : ''}
|
|
1285
1274
|
<div class="session-progress">
|
|
1286
1275
|
<div class="progress-bar"><div class="progress-fill" style="width: ${percent}%"></div></div>
|
|
1287
1276
|
<span class="progress-text">${session.completed}/${total}</span>
|
|
@@ -1302,7 +1291,8 @@
|
|
|
1302
1291
|
const displayName = session.name || currentSessionId;
|
|
1303
1292
|
sessionTitle.textContent = displayName;
|
|
1304
1293
|
const projectName = session.project ? session.project.split('/').pop() : null;
|
|
1305
|
-
|
|
1294
|
+
const branchInfo = session.gitBranch ? ` · ${session.gitBranch}` : '';
|
|
1295
|
+
sessionMeta.textContent = `${currentTasks.length} tasks${projectName ? ' · ' + projectName : ''}${branchInfo} · ${formatDate(session.modifiedAt)}`;
|
|
1306
1296
|
|
|
1307
1297
|
const completed = currentTasks.filter(t => t.status === 'completed').length;
|
|
1308
1298
|
const percent = currentTasks.length > 0 ? Math.round((completed / currentTasks.length) * 100) : 0;
|
|
@@ -1336,17 +1326,9 @@
|
|
|
1336
1326
|
}
|
|
1337
1327
|
|
|
1338
1328
|
function renderKanban() {
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
t.subject.toLowerCase().includes(searchQuery) ||
|
|
1343
|
-
(t.description && t.description.toLowerCase().includes(searchQuery))
|
|
1344
|
-
);
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
const pending = filteredTasks.filter(t => t.status === 'pending');
|
|
1348
|
-
const inProgress = filteredTasks.filter(t => t.status === 'in_progress');
|
|
1349
|
-
const completed = filteredTasks.filter(t => t.status === 'completed');
|
|
1329
|
+
const pending = currentTasks.filter(t => t.status === 'pending');
|
|
1330
|
+
const inProgress = currentTasks.filter(t => t.status === 'in_progress');
|
|
1331
|
+
const completed = currentTasks.filter(t => t.status === 'completed');
|
|
1350
1332
|
|
|
1351
1333
|
pendingCount.textContent = pending.length;
|
|
1352
1334
|
inProgressCount.textContent = inProgress.length;
|
|
@@ -1538,17 +1520,6 @@
|
|
|
1538
1520
|
showAllTasks();
|
|
1539
1521
|
}
|
|
1540
1522
|
|
|
1541
|
-
function searchTasks(query) {
|
|
1542
|
-
searchQuery = query.toLowerCase();
|
|
1543
|
-
renderKanban();
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
function clearSearch() {
|
|
1547
|
-
searchQuery = '';
|
|
1548
|
-
const searchInput = document.getElementById('task-search');
|
|
1549
|
-
if (searchInput) searchInput.value = '';
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
1523
|
function updateProjectDropdown() {
|
|
1553
1524
|
const dropdown = document.getElementById('project-filter');
|
|
1554
1525
|
const projects = [...new Set(sessions.map(s => s.project).filter(Boolean))].sort();
|
package/server.js
CHANGED
|
@@ -10,7 +10,24 @@ const os = require('os');
|
|
|
10
10
|
|
|
11
11
|
const app = express();
|
|
12
12
|
const PORT = process.env.PORT || 3456;
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
// Parse --dir flag for custom Claude directory
|
|
15
|
+
function getClaudeDir() {
|
|
16
|
+
const dirIndex = process.argv.findIndex(arg => arg.startsWith('--dir'));
|
|
17
|
+
if (dirIndex !== -1) {
|
|
18
|
+
const arg = process.argv[dirIndex];
|
|
19
|
+
if (arg.includes('=')) {
|
|
20
|
+
const dir = arg.split('=')[1];
|
|
21
|
+
return dir.startsWith('~') ? dir.replace('~', os.homedir()) : dir;
|
|
22
|
+
} else if (process.argv[dirIndex + 1]) {
|
|
23
|
+
const dir = process.argv[dirIndex + 1];
|
|
24
|
+
return dir.startsWith('~') ? dir.replace('~', os.homedir()) : dir;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return process.env.CLAUDE_DIR || path.join(os.homedir(), '.claude');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const CLAUDE_DIR = getClaudeDir();
|
|
14
31
|
const TASKS_DIR = path.join(CLAUDE_DIR, 'tasks');
|
|
15
32
|
const PROJECTS_DIR = path.join(CLAUDE_DIR, 'projects');
|
|
16
33
|
|
|
@@ -352,45 +369,43 @@ function broadcast(data) {
|
|
|
352
369
|
}
|
|
353
370
|
}
|
|
354
371
|
|
|
355
|
-
// Watch for file changes
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
});
|
|
372
|
+
// Watch for file changes (chokidar handles non-existent paths)
|
|
373
|
+
const watcher = chokidar.watch(TASKS_DIR, {
|
|
374
|
+
persistent: true,
|
|
375
|
+
ignoreInitial: true,
|
|
376
|
+
depth: 2
|
|
377
|
+
});
|
|
362
378
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
379
|
+
watcher.on('all', (event, filePath) => {
|
|
380
|
+
if (filePath.endsWith('.json')) {
|
|
381
|
+
const relativePath = path.relative(TASKS_DIR, filePath);
|
|
382
|
+
const sessionId = relativePath.split(path.sep)[0];
|
|
383
|
+
|
|
384
|
+
broadcast({
|
|
385
|
+
type: 'update',
|
|
386
|
+
event,
|
|
387
|
+
sessionId,
|
|
388
|
+
file: path.basename(filePath)
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
});
|
|
376
392
|
|
|
377
|
-
|
|
378
|
-
}
|
|
393
|
+
console.log(`Watching for changes in: ${TASKS_DIR}`);
|
|
379
394
|
|
|
380
395
|
// Also watch projects dir for metadata changes
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
});
|
|
396
|
+
const projectsWatcher = chokidar.watch(PROJECTS_DIR, {
|
|
397
|
+
persistent: true,
|
|
398
|
+
ignoreInitial: true,
|
|
399
|
+
depth: 2
|
|
400
|
+
});
|
|
387
401
|
|
|
388
|
-
|
|
402
|
+
projectsWatcher.on('all', (event, filePath) => {
|
|
403
|
+
if (filePath.endsWith('.jsonl')) {
|
|
389
404
|
// Invalidate cache on any change
|
|
390
405
|
lastMetadataRefresh = 0;
|
|
391
406
|
broadcast({ type: 'metadata-update' });
|
|
392
|
-
}
|
|
393
|
-
}
|
|
407
|
+
}
|
|
408
|
+
});
|
|
394
409
|
|
|
395
410
|
// Start server
|
|
396
411
|
app.listen(PORT, () => {
|