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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # backlog-mcp
2
2
 
3
- Minimal task backlog MCP server for Claude and AI agents.
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 Claude Desktop config):
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 {};
@@ -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 { type Backlog, type StorageOptions, loadBacklog, saveBacklog, getTask, listTasks, addTask, saveTask, deleteTask, taskExists, getTaskCounts, getTaskMarkdown, } from './storage.js';
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 { loadBacklog, saveBacklog, getTask, listTasks, addTask, saveTask, deleteTask, taskExists, getTaskCounts, getTaskMarkdown, } from './storage.js';
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,EAGL,WAAW,EACX,WAAW,EACX,OAAO,EACP,SAAS,EACT,OAAO,EACP,QAAQ,EACR,UAAU,EACV,UAAU,EACV,aAAa,EACb,eAAe,GAChB,MAAM,cAAc,CAAC;AAEtB,SAAS;AACT,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
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
- export {};
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 { countTasks } from './summary.js';
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: 'Manage tasks. Actions: list, get, create, update',
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). Include "done" or "cancelled" to see archived tasks'),
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 when status includes done/cancelled (list). Default: 10'),
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 = listTasks(filter, storageOptions);
51
+ const tasks = storage.list(filter);
46
52
  if (summary) {
47
- const counts = status ? countTasks(tasks) : getTaskCounts(storageOptions);
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
- if (!id) {
55
- return { content: [{ type: 'text', text: 'Missing required: id' }], isError: true };
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
- return { content: [{ type: 'text', text: markdown }] };
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 backlog = loadBacklog(storageOptions);
68
- const task = createTask({ title, description }, backlog.tasks);
69
- addTask(task, storageOptions);
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 = getTask(id, storageOptions);
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
- saveTask(updated, storageOptions);
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(dataDir, viewerPort);
113
+ startViewer(viewerPort);
99
114
  const transport = new StdioServerTransport();
100
115
  await server.connect(transport);
101
116
  }
102
- main().catch((error) => {
103
- console.error('Fatal error:', error);
104
- process.exit(1);
105
- });
117
+ main().catch(console.error);
106
118
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
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 === 'done' ? 'active' : ''}" data-filter="done">Completed</button>
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>`;
@@ -1,15 +1,6 @@
1
1
  export const API_URL = 'http://localhost:3030';
2
2
  export async function fetchTasks(filter = 'active') {
3
- let url = `${API_URL}/tasks`;
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(dataDir: string, port?: number): Promise<void>;
1
+ export declare function startViewer(port?: number): Promise<void>;