backlog-mcp 0.6.5 → 0.7.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 +6 -2
- package/dist/backlog.d.ts +26 -0
- package/dist/backlog.js +159 -0
- package/dist/backlog.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +47 -35
- package/dist/server.js.map +1 -1
- package/dist/viewer/components/task-detail.js +27 -11
- package/dist/viewer/components/task-filter-bar.js +1 -1
- package/dist/viewer/icons/index.js +1 -0
- package/dist/viewer/utils/api.js +1 -10
- package/dist/viewer.d.ts +1 -1
- package/dist/viewer.js +38 -116
- package/dist/viewer.js.map +1 -1
- package/package.json +2 -1
- package/viewer/index.html +1 -1
- package/viewer/md-block.js +25 -0
- package/viewer/styles.css +100 -9
- package/dist/storage.d.ts +0 -58
- package/dist/storage.js +0 -279
- package/dist/storage.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# backlog-mcp
|
|
2
2
|
|
|
3
|
-
Minimal task backlog MCP server
|
|
3
|
+
Minimal task backlog MCP server. Works with any LLM agent or CLI editor that supports MCP integration (Claude, Kiro, Cursor, Codex, etc).
|
|
4
4
|
|
|
5
5
|
> **Quick start**: Tell your LLM: `Add backlog-mcp to .mcp.json and use it to track tasks`
|
|
6
6
|
|
|
@@ -64,7 +64,7 @@ backlog action="update" id="TASK-0001" set_status="done" # Update task
|
|
|
64
64
|
|
|
65
65
|
## Installation
|
|
66
66
|
|
|
67
|
-
Add to your MCP config (`.mcp.json` or
|
|
67
|
+
Add to your MCP config (`.mcp.json` or your MCP client config):
|
|
68
68
|
|
|
69
69
|
```json
|
|
70
70
|
{
|
|
@@ -95,3 +95,7 @@ npm start
|
|
|
95
95
|
## License
|
|
96
96
|
|
|
97
97
|
MIT
|
|
98
|
+
|
|
99
|
+
<a href="https://glama.ai/mcp/servers/@gkoreli/backlog-mcp">
|
|
100
|
+
<img width="380" height="200" src="https://glama.ai/mcp/servers/@gkoreli/backlog-mcp/badge" alt="backlog-mcp MCP server" />
|
|
101
|
+
</a>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Task, Status } from './schema.js';
|
|
2
|
+
declare class BacklogStorage {
|
|
3
|
+
private dataDir;
|
|
4
|
+
private static instance;
|
|
5
|
+
static getInstance(): BacklogStorage;
|
|
6
|
+
init(dataDir: string): void;
|
|
7
|
+
private get tasksPath();
|
|
8
|
+
private get archivePath();
|
|
9
|
+
private ensureDir;
|
|
10
|
+
private taskFilePath;
|
|
11
|
+
private taskToMarkdown;
|
|
12
|
+
private markdownToTask;
|
|
13
|
+
getFilePath(id: string): string | null;
|
|
14
|
+
get(id: string): Task | undefined;
|
|
15
|
+
getMarkdown(id: string): string | null;
|
|
16
|
+
list(filter?: {
|
|
17
|
+
status?: Status[];
|
|
18
|
+
archivedLimit?: number;
|
|
19
|
+
}): Task[];
|
|
20
|
+
add(task: Task): void;
|
|
21
|
+
save(task: Task): void;
|
|
22
|
+
delete(id: string): boolean;
|
|
23
|
+
counts(): Record<Status, number>;
|
|
24
|
+
}
|
|
25
|
+
export declare const storage: BacklogStorage;
|
|
26
|
+
export {};
|
package/dist/backlog.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import matter from 'gray-matter';
|
|
4
|
+
const TASKS_DIR = 'tasks';
|
|
5
|
+
const ARCHIVE_DIR = 'archive';
|
|
6
|
+
const TERMINAL_STATUSES = ['done', 'cancelled'];
|
|
7
|
+
class BacklogStorage {
|
|
8
|
+
dataDir = 'data';
|
|
9
|
+
static instance;
|
|
10
|
+
static getInstance() {
|
|
11
|
+
if (!BacklogStorage.instance) {
|
|
12
|
+
BacklogStorage.instance = new BacklogStorage();
|
|
13
|
+
}
|
|
14
|
+
return BacklogStorage.instance;
|
|
15
|
+
}
|
|
16
|
+
init(dataDir) {
|
|
17
|
+
this.dataDir = dataDir;
|
|
18
|
+
}
|
|
19
|
+
get tasksPath() {
|
|
20
|
+
return join(this.dataDir, TASKS_DIR);
|
|
21
|
+
}
|
|
22
|
+
get archivePath() {
|
|
23
|
+
return join(this.dataDir, ARCHIVE_DIR);
|
|
24
|
+
}
|
|
25
|
+
ensureDir(dir) {
|
|
26
|
+
if (!existsSync(dir)) {
|
|
27
|
+
mkdirSync(dir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
taskFilePath(id, archived = false) {
|
|
31
|
+
return join(archived ? this.archivePath : this.tasksPath, `${id}.md`);
|
|
32
|
+
}
|
|
33
|
+
taskToMarkdown(task) {
|
|
34
|
+
const { description, ...frontmatter } = task;
|
|
35
|
+
return matter.stringify(description || '', frontmatter);
|
|
36
|
+
}
|
|
37
|
+
markdownToTask(content) {
|
|
38
|
+
const { data, content: description } = matter(content);
|
|
39
|
+
return { ...data, description: description.trim() };
|
|
40
|
+
}
|
|
41
|
+
getFilePath(id) {
|
|
42
|
+
const activePath = this.taskFilePath(id, false);
|
|
43
|
+
if (existsSync(activePath))
|
|
44
|
+
return activePath;
|
|
45
|
+
const archivePath = this.taskFilePath(id, true);
|
|
46
|
+
if (existsSync(archivePath))
|
|
47
|
+
return archivePath;
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
get(id) {
|
|
51
|
+
const activePath = this.taskFilePath(id, false);
|
|
52
|
+
if (existsSync(activePath)) {
|
|
53
|
+
return this.markdownToTask(readFileSync(activePath, 'utf-8'));
|
|
54
|
+
}
|
|
55
|
+
const archivePath = this.taskFilePath(id, true);
|
|
56
|
+
if (existsSync(archivePath)) {
|
|
57
|
+
return this.markdownToTask(readFileSync(archivePath, 'utf-8'));
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
getMarkdown(id) {
|
|
62
|
+
const activePath = this.taskFilePath(id, false);
|
|
63
|
+
if (existsSync(activePath)) {
|
|
64
|
+
return readFileSync(activePath, 'utf-8');
|
|
65
|
+
}
|
|
66
|
+
const archivePath = this.taskFilePath(id, true);
|
|
67
|
+
if (existsSync(archivePath)) {
|
|
68
|
+
return readFileSync(archivePath, 'utf-8');
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
list(filter) {
|
|
73
|
+
const tasks = [];
|
|
74
|
+
const statusFilter = filter?.status;
|
|
75
|
+
const archivedLimit = filter?.archivedLimit ?? 10;
|
|
76
|
+
// Load active tasks
|
|
77
|
+
if (existsSync(this.tasksPath)) {
|
|
78
|
+
const files = readdirSync(this.tasksPath).filter(f => f.endsWith('.md'));
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
const task = this.markdownToTask(readFileSync(join(this.tasksPath, file), 'utf-8'));
|
|
81
|
+
if (!statusFilter || statusFilter.includes(task.status)) {
|
|
82
|
+
tasks.push(task);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Load archived tasks if needed
|
|
87
|
+
const needsArchived = !statusFilter || statusFilter.some(s => TERMINAL_STATUSES.includes(s));
|
|
88
|
+
if (needsArchived && existsSync(this.archivePath)) {
|
|
89
|
+
const files = readdirSync(this.archivePath).filter(f => f.endsWith('.md'));
|
|
90
|
+
const archived = files
|
|
91
|
+
.map(file => this.markdownToTask(readFileSync(join(this.archivePath, file), 'utf-8')))
|
|
92
|
+
.filter(t => !statusFilter || statusFilter.includes(t.status))
|
|
93
|
+
.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())
|
|
94
|
+
.slice(0, archivedLimit);
|
|
95
|
+
tasks.push(...archived);
|
|
96
|
+
}
|
|
97
|
+
return tasks;
|
|
98
|
+
}
|
|
99
|
+
add(task) {
|
|
100
|
+
this.ensureDir(this.tasksPath);
|
|
101
|
+
const filePath = this.taskFilePath(task.id, false);
|
|
102
|
+
writeFileSync(filePath, this.taskToMarkdown(task));
|
|
103
|
+
}
|
|
104
|
+
save(task) {
|
|
105
|
+
const isTerminal = TERMINAL_STATUSES.includes(task.status);
|
|
106
|
+
const activePath = this.taskFilePath(task.id, false);
|
|
107
|
+
const archivePath = this.taskFilePath(task.id, true);
|
|
108
|
+
// Move to archive if terminal status
|
|
109
|
+
if (isTerminal) {
|
|
110
|
+
this.ensureDir(this.archivePath);
|
|
111
|
+
writeFileSync(archivePath, this.taskToMarkdown(task));
|
|
112
|
+
if (existsSync(activePath))
|
|
113
|
+
unlinkSync(activePath);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
this.ensureDir(this.tasksPath);
|
|
117
|
+
writeFileSync(activePath, this.taskToMarkdown(task));
|
|
118
|
+
if (existsSync(archivePath))
|
|
119
|
+
unlinkSync(archivePath);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
delete(id) {
|
|
123
|
+
const activePath = this.taskFilePath(id, false);
|
|
124
|
+
if (existsSync(activePath)) {
|
|
125
|
+
unlinkSync(activePath);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
const archivePath = this.taskFilePath(id, true);
|
|
129
|
+
if (existsSync(archivePath)) {
|
|
130
|
+
unlinkSync(archivePath);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
counts() {
|
|
136
|
+
const counts = {
|
|
137
|
+
open: 0,
|
|
138
|
+
in_progress: 0,
|
|
139
|
+
blocked: 0,
|
|
140
|
+
done: 0,
|
|
141
|
+
cancelled: 0,
|
|
142
|
+
};
|
|
143
|
+
if (existsSync(this.tasksPath)) {
|
|
144
|
+
for (const file of readdirSync(this.tasksPath).filter(f => f.endsWith('.md'))) {
|
|
145
|
+
const task = this.markdownToTask(readFileSync(join(this.tasksPath, file), 'utf-8'));
|
|
146
|
+
counts[task.status]++;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (existsSync(this.archivePath)) {
|
|
150
|
+
for (const file of readdirSync(this.archivePath).filter(f => f.endsWith('.md'))) {
|
|
151
|
+
const task = this.markdownToTask(readFileSync(join(this.archivePath, file), 'utf-8'));
|
|
152
|
+
counts[task.status]++;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return counts;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
export const storage = BacklogStorage.getInstance();
|
|
159
|
+
//# sourceMappingURL=backlog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backlog.js","sourceRoot":"","sources":["../src/backlog.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { isValidTaskId, parseTaskId, formatTaskId, nextTaskId, STATUSES, type Status, type Task, type CreateTaskInput, createTask, } from './schema.js';
|
|
2
|
-
export {
|
|
2
|
+
export { storage } from './backlog.js';
|
|
3
3
|
export { startViewer } from './viewer.js';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Schema
|
|
2
2
|
export { isValidTaskId, parseTaskId, formatTaskId, nextTaskId, STATUSES, createTask, } from './schema.js';
|
|
3
3
|
// Storage
|
|
4
|
-
export {
|
|
4
|
+
export { storage } from './backlog.js';
|
|
5
5
|
// Viewer
|
|
6
6
|
export { startViewer } from './viewer.js';
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,SAAS;AACT,OAAO,EACL,aAAa,EACb,WAAW,EACX,YAAY,EACZ,UAAU,EACV,QAAQ,EAIR,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,UAAU;AACV,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,SAAS;AACT,OAAO,EACL,aAAa,EACb,WAAW,EACX,YAAY,EACZ,UAAU,EACV,QAAQ,EAIR,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,UAAU;AACV,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,SAAS;AACT,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import 'dotenv/config';
|
package/dist/server.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import 'dotenv/config';
|
|
2
3
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
4
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
5
|
import { z } from 'zod';
|
|
5
6
|
import { createTask, STATUSES } from './schema.js';
|
|
6
|
-
import {
|
|
7
|
-
import { loadBacklog, getTask, getTaskMarkdown, listTasks, addTask, saveTask, getTaskCounts } from './storage.js';
|
|
7
|
+
import { storage } from './backlog.js';
|
|
8
8
|
import { startViewer } from './viewer.js';
|
|
9
|
+
// Init storage
|
|
10
|
+
const dataDir = process.env.BACKLOG_DATA_DIR ?? 'data';
|
|
11
|
+
storage.init(dataDir);
|
|
9
12
|
// ============================================================================
|
|
10
13
|
// Server
|
|
11
14
|
// ============================================================================
|
|
@@ -13,67 +16,71 @@ const server = new McpServer({
|
|
|
13
16
|
name: 'backlog-mcp',
|
|
14
17
|
version: '0.1.0',
|
|
15
18
|
});
|
|
16
|
-
const storageOptions = {
|
|
17
|
-
dataDir: process.env.BACKLOG_DATA_DIR ?? 'data',
|
|
18
|
-
};
|
|
19
19
|
// ============================================================================
|
|
20
20
|
// Tools
|
|
21
21
|
// ============================================================================
|
|
22
|
-
const ACTIONS = ['list', 'get', 'create', 'update'];
|
|
22
|
+
const ACTIONS = ['list', 'get', 'create', 'update', 'delete'];
|
|
23
23
|
server.registerTool('backlog', {
|
|
24
|
-
description:
|
|
24
|
+
description: `Local task backlog. Actions:
|
|
25
|
+
- list: Show tasks (active by default, add status=["done"] for completed)
|
|
26
|
+
- get: Fetch task by ID (works for ANY status - active or archived)
|
|
27
|
+
- create: New task
|
|
28
|
+
- update: Modify task
|
|
29
|
+
- delete: Permanently remove task`,
|
|
25
30
|
inputSchema: {
|
|
26
31
|
action: z.enum(ACTIONS).describe('Action to perform'),
|
|
27
32
|
// list options
|
|
28
|
-
status: z.array(z.enum(STATUSES)).optional().describe('Filter by status (list).
|
|
33
|
+
status: z.array(z.enum(STATUSES)).optional().describe('Filter by status (list only). Default: active tasks'),
|
|
29
34
|
summary: z.boolean().optional().describe('Return counts instead of list (list)'),
|
|
30
|
-
archived_limit: z.number().optional().describe('Max archived tasks
|
|
31
|
-
// get/update options
|
|
32
|
-
id: z.string().optional().describe('Task ID (get, update)'),
|
|
35
|
+
archived_limit: z.number().optional().describe('Max archived tasks to return (list). Default: 10'),
|
|
36
|
+
// get/update/delete options
|
|
37
|
+
id: z.string().optional().describe('Task ID like TASK-0001 (get, update, delete). Get works on any task regardless of status'),
|
|
38
|
+
ids: z.array(z.string()).optional().describe('Multiple task IDs (get only). Returns all in one call'),
|
|
33
39
|
// create/update options
|
|
34
40
|
title: z.string().optional().describe('Task title (create, update)'),
|
|
35
|
-
description: z.string().optional().describe('Task description (create, update)'),
|
|
41
|
+
description: z.string().optional().describe('Task description in markdown (create, update)'),
|
|
36
42
|
// update-only options
|
|
37
43
|
set_status: z.enum(STATUSES).optional().describe('New status (update)'),
|
|
38
44
|
blocked_reason: z.string().optional().describe('Reason for blocked status (update)'),
|
|
39
|
-
evidence: z.array(z.string()).optional().describe('Evidence of completion (update)'),
|
|
45
|
+
evidence: z.array(z.string()).optional().describe('Evidence of completion - links, notes (update)'),
|
|
40
46
|
},
|
|
41
|
-
}, async ({ action, status, summary, archived_limit, id, title, description, set_status, blocked_reason, evidence }) => {
|
|
47
|
+
}, async ({ action, status, summary, archived_limit, id, ids, title, description, set_status, blocked_reason, evidence }) => {
|
|
42
48
|
switch (action) {
|
|
43
49
|
case 'list': {
|
|
44
50
|
const filter = status || archived_limit ? { status, archivedLimit: archived_limit } : undefined;
|
|
45
|
-
const tasks =
|
|
51
|
+
const tasks = storage.list(filter);
|
|
46
52
|
if (summary) {
|
|
47
|
-
const counts =
|
|
53
|
+
const counts = storage.counts();
|
|
48
54
|
return { content: [{ type: 'text', text: JSON.stringify(counts, null, 2) }] };
|
|
49
55
|
}
|
|
50
56
|
const list = tasks.map((t) => ({ id: t.id, title: t.title, status: t.status }));
|
|
51
57
|
return { content: [{ type: 'text', text: JSON.stringify(list, null, 2) }] };
|
|
52
58
|
}
|
|
53
59
|
case 'get': {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const markdown = getTaskMarkdown(id, storageOptions);
|
|
58
|
-
if (!markdown) {
|
|
59
|
-
return { content: [{ type: 'text', text: `Not found: ${id}` }], isError: true };
|
|
60
|
+
const taskIds = ids || (id ? [id] : []);
|
|
61
|
+
if (taskIds.length === 0) {
|
|
62
|
+
return { content: [{ type: 'text', text: 'Missing required: id or ids' }], isError: true };
|
|
60
63
|
}
|
|
61
|
-
|
|
64
|
+
const results = taskIds.map(tid => {
|
|
65
|
+
const markdown = storage.getMarkdown(tid);
|
|
66
|
+
return markdown || `Not found: ${tid}`;
|
|
67
|
+
});
|
|
68
|
+
return { content: [{ type: 'text', text: results.join('\n\n---\n\n') }] };
|
|
62
69
|
}
|
|
63
70
|
case 'create': {
|
|
64
71
|
if (!title) {
|
|
65
72
|
return { content: [{ type: 'text', text: 'Missing required: title' }], isError: true };
|
|
66
73
|
}
|
|
67
|
-
const
|
|
68
|
-
const task = createTask({ title, description },
|
|
69
|
-
|
|
74
|
+
const existing = storage.list();
|
|
75
|
+
const task = createTask({ title, description }, existing);
|
|
76
|
+
storage.add(task);
|
|
70
77
|
return { content: [{ type: 'text', text: `Created ${task.id}` }] };
|
|
71
78
|
}
|
|
72
79
|
case 'update': {
|
|
73
80
|
if (!id) {
|
|
74
81
|
return { content: [{ type: 'text', text: 'Missing required: id' }], isError: true };
|
|
75
82
|
}
|
|
76
|
-
const task =
|
|
83
|
+
const task = storage.get(id);
|
|
77
84
|
if (!task) {
|
|
78
85
|
return { content: [{ type: 'text', text: `Not found: ${id}` }], isError: true };
|
|
79
86
|
}
|
|
@@ -83,24 +90,29 @@ server.registerTool('backlog', {
|
|
|
83
90
|
...Object.fromEntries(Object.entries(updates).filter(([_, v]) => v !== undefined)),
|
|
84
91
|
updated_at: new Date().toISOString(),
|
|
85
92
|
};
|
|
86
|
-
|
|
93
|
+
storage.save(updated);
|
|
87
94
|
return { content: [{ type: 'text', text: `Updated ${id}` }] };
|
|
88
95
|
}
|
|
96
|
+
case 'delete': {
|
|
97
|
+
if (!id) {
|
|
98
|
+
return { content: [{ type: 'text', text: 'Missing required: id' }], isError: true };
|
|
99
|
+
}
|
|
100
|
+
const deleted = storage.delete(id);
|
|
101
|
+
if (!deleted) {
|
|
102
|
+
return { content: [{ type: 'text', text: `Not found: ${id}` }], isError: true };
|
|
103
|
+
}
|
|
104
|
+
return { content: [{ type: 'text', text: `Deleted ${id}` }] };
|
|
105
|
+
}
|
|
89
106
|
}
|
|
90
107
|
});
|
|
91
108
|
// ============================================================================
|
|
92
109
|
// Main
|
|
93
110
|
// ============================================================================
|
|
94
111
|
async function main() {
|
|
95
|
-
// Start HTTP viewer in background
|
|
96
|
-
const dataDir = storageOptions.dataDir ?? 'data';
|
|
97
112
|
const viewerPort = parseInt(process.env.BACKLOG_VIEWER_PORT || '3030');
|
|
98
|
-
startViewer(
|
|
113
|
+
startViewer(viewerPort);
|
|
99
114
|
const transport = new StdioServerTransport();
|
|
100
115
|
await server.connect(transport);
|
|
101
116
|
}
|
|
102
|
-
main().catch(
|
|
103
|
-
console.error('Fatal error:', error);
|
|
104
|
-
process.exit(1);
|
|
105
|
-
});
|
|
117
|
+
main().catch(console.error);
|
|
106
118
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAa,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { fetchTask } from '../utils/api.js';
|
|
2
|
+
import { copyIcon } from '../icons/index.js';
|
|
3
|
+
function linkify(text) {
|
|
4
|
+
const urlRegex = /(https?:\/\/[^\s<>"']+)/g;
|
|
5
|
+
return text.replace(urlRegex, '<a href="$1" target="_blank" rel="noopener">$1</a>');
|
|
6
|
+
}
|
|
2
7
|
export class TaskDetail extends HTMLElement {
|
|
3
8
|
connectedCallback() {
|
|
4
9
|
this.showEmpty();
|
|
@@ -14,18 +19,24 @@ export class TaskDetail extends HTMLElement {
|
|
|
14
19
|
async loadTask(taskId) {
|
|
15
20
|
try {
|
|
16
21
|
const task = await fetchTask(taskId);
|
|
22
|
+
const headerHtml = `
|
|
23
|
+
<div class="task-header-left">
|
|
24
|
+
<button class="btn-outline task-id-btn" onclick="navigator.clipboard.writeText('${task.id}')" title="Copy ID">${task.id} ${copyIcon}</button>
|
|
25
|
+
<span class="status-badge status-${task.status || 'open'}">${(task.status || 'open').replace('_', ' ')}</span>
|
|
26
|
+
${task.filePath ? `
|
|
27
|
+
<div class="task-meta-path">
|
|
28
|
+
<a href="#" class="open-link" onclick="fetch('http://localhost:3030/open/${task.id}');return false;" title="Open in editor">${task.filePath}</a>
|
|
29
|
+
</div>
|
|
30
|
+
` : ''}
|
|
31
|
+
</div>
|
|
32
|
+
<button class="copy-btn copy-raw btn-outline" title="Copy markdown">Copy Markdown ${copyIcon}</button>
|
|
33
|
+
`;
|
|
34
|
+
const paneHeader = document.getElementById('task-pane-header');
|
|
35
|
+
if (paneHeader) {
|
|
36
|
+
paneHeader.innerHTML = headerHtml;
|
|
37
|
+
}
|
|
17
38
|
const metaHtml = `
|
|
18
39
|
<div class="task-meta-card">
|
|
19
|
-
<div class="task-meta-header">
|
|
20
|
-
<span class="task-meta-id">${task.id || ''}</span>
|
|
21
|
-
<span class="status-badge status-${task.status || 'open'}">${(task.status || 'open').replace('_', ' ')}</span>
|
|
22
|
-
${task.filePath ? `
|
|
23
|
-
<div class="task-meta-path">
|
|
24
|
-
<a href="#" class="open-link" onclick="fetch('http://localhost:3030/open/${task.id}');return false;" title="Open in editor">${task.filePath}</a>
|
|
25
|
-
<button class="copy-btn" onclick="navigator.clipboard.writeText('${task.filePath}')">📋</button>
|
|
26
|
-
</div>
|
|
27
|
-
` : ''}
|
|
28
|
-
</div>
|
|
29
40
|
<h1 class="task-meta-title">${task.title || ''}</h1>
|
|
30
41
|
<div class="task-meta-dates">
|
|
31
42
|
<span>Created: ${task.created_at ? new Date(task.created_at).toLocaleDateString() : ''}</span>
|
|
@@ -34,7 +45,7 @@ export class TaskDetail extends HTMLElement {
|
|
|
34
45
|
${task.evidence?.length ? `
|
|
35
46
|
<div class="task-meta-evidence">
|
|
36
47
|
<div class="task-meta-evidence-label">Evidence:</div>
|
|
37
|
-
<ul>${task.evidence.map((e) => `<li>${e}</li>`).join('')}</ul>
|
|
48
|
+
<ul>${task.evidence.map((e) => `<li>${linkify(e)}</li>`).join('')}</ul>
|
|
38
49
|
</div>
|
|
39
50
|
` : ''}
|
|
40
51
|
</div>
|
|
@@ -47,6 +58,11 @@ export class TaskDetail extends HTMLElement {
|
|
|
47
58
|
article.appendChild(mdBlock);
|
|
48
59
|
this.innerHTML = '';
|
|
49
60
|
this.appendChild(article);
|
|
61
|
+
// Bind copy raw button (in pane header)
|
|
62
|
+
const copyRawBtn = paneHeader?.querySelector('.copy-raw');
|
|
63
|
+
if (copyRawBtn && task.raw) {
|
|
64
|
+
copyRawBtn.addEventListener('click', () => navigator.clipboard.writeText(task.raw));
|
|
65
|
+
}
|
|
50
66
|
}
|
|
51
67
|
catch (error) {
|
|
52
68
|
this.innerHTML = `<div class="error">Failed to load task: ${error.message}</div>`;
|
|
@@ -8,7 +8,7 @@ export class TaskFilterBar extends HTMLElement {
|
|
|
8
8
|
this.innerHTML = `
|
|
9
9
|
<div class="filter-bar">
|
|
10
10
|
<button class="filter-btn ${this.currentFilter === 'active' ? 'active' : ''}" data-filter="active">Active</button>
|
|
11
|
-
<button class="filter-btn ${this.currentFilter === '
|
|
11
|
+
<button class="filter-btn ${this.currentFilter === 'completed' ? 'active' : ''}" data-filter="completed">Completed</button>
|
|
12
12
|
<button class="filter-btn ${this.currentFilter === 'all' ? 'active' : ''}" data-filter="all">All</button>
|
|
13
13
|
</div>
|
|
14
14
|
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const copyIcon = `<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"/><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z"/></svg>`;
|
package/dist/viewer/utils/api.js
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
export const API_URL = 'http://localhost:3030';
|
|
2
2
|
export async function fetchTasks(filter = 'active') {
|
|
3
|
-
|
|
4
|
-
if (filter === 'active') {
|
|
5
|
-
url += '?status=open,in_progress,blocked';
|
|
6
|
-
}
|
|
7
|
-
else if (filter === 'done') {
|
|
8
|
-
url += '?status=done,cancelled&limit=20';
|
|
9
|
-
}
|
|
10
|
-
else if (filter === 'all') {
|
|
11
|
-
url += '?status=open,in_progress,blocked,done,cancelled&limit=20';
|
|
12
|
-
}
|
|
3
|
+
const url = `${API_URL}/tasks?filter=${filter}`;
|
|
13
4
|
const response = await fetch(url);
|
|
14
5
|
return response.json();
|
|
15
6
|
}
|
package/dist/viewer.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function startViewer(
|
|
1
|
+
export declare function startViewer(port?: number): Promise<void>;
|