@vheins/local-memory-mcp 0.2.0 → 0.3.12

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 (41) hide show
  1. package/dist/dashboard/public/app.js +358 -31
  2. package/dist/dashboard/public/index.html +131 -7
  3. package/dist/dashboard/server.js +109 -5
  4. package/dist/dashboard/server.js.map +1 -1
  5. package/dist/prompts/registry.d.ts +112 -0
  6. package/dist/prompts/registry.d.ts.map +1 -1
  7. package/dist/prompts/registry.js +172 -0
  8. package/dist/prompts/registry.js.map +1 -1
  9. package/dist/resources/index.test.js +2 -0
  10. package/dist/resources/index.test.js.map +1 -1
  11. package/dist/server.js +10 -10
  12. package/dist/server.js.map +1 -1
  13. package/dist/storage/sqlite.d.ts +6 -1
  14. package/dist/storage/sqlite.d.ts.map +1 -1
  15. package/dist/storage/sqlite.js +91 -13
  16. package/dist/storage/sqlite.js.map +1 -1
  17. package/dist/storage/sqlite.test.js +2 -0
  18. package/dist/storage/sqlite.test.js.map +1 -1
  19. package/dist/storage/vectors.d.ts.map +1 -1
  20. package/dist/storage/vectors.js +0 -2
  21. package/dist/storage/vectors.js.map +1 -1
  22. package/dist/tools/memory.store.d.ts.map +1 -1
  23. package/dist/tools/memory.store.js +2 -0
  24. package/dist/tools/memory.store.js.map +1 -1
  25. package/dist/tools/memory.update.d.ts.map +1 -1
  26. package/dist/tools/memory.update.js +6 -0
  27. package/dist/tools/memory.update.js.map +1 -1
  28. package/dist/tools/schemas.d.ts +35 -1
  29. package/dist/tools/schemas.d.ts.map +1 -1
  30. package/dist/tools/schemas.js +21 -2
  31. package/dist/tools/schemas.js.map +1 -1
  32. package/dist/tools/task.manage.d.ts.map +1 -1
  33. package/dist/tools/task.manage.js +15 -2
  34. package/dist/tools/task.manage.js.map +1 -1
  35. package/dist/types.d.ts +5 -0
  36. package/dist/types.d.ts.map +1 -1
  37. package/dist/utils/logger.test.js +19 -27
  38. package/dist/utils/logger.test.js.map +1 -1
  39. package/dist/v2-features.test.js +2 -0
  40. package/dist/v2-features.test.js.map +1 -1
  41. package/package.json +1 -1
@@ -6,6 +6,11 @@ let totalPages = 1;
6
6
  let totalItems = 0;
7
7
  let selectedIds = new Set();
8
8
  let currentPaginatedData = [];
9
+ let taskPagination = {
10
+ todo: { page: 1, pageSize: 20, hasMore: true, loading: false },
11
+ in_progress: { page: 1, pageSize: 20, hasMore: true, loading: false },
12
+ completed: { page: 1, pageSize: 20, hasMore: true, loading: false }
13
+ };
9
14
  let charts = {};
10
15
  let lastSyncTime = Date.now();
11
16
  let countdownSeconds = 30;
@@ -600,6 +605,21 @@ async function loadStats() {
600
605
  document.getElementById('mistakeCount').textContent = data.byType?.mistake || 0;
601
606
  document.getElementById('patternCount').textContent = data.byType?.pattern || 0;
602
607
 
608
+ // Fill Task stats
609
+ if (data.taskStats) {
610
+ document.getElementById('totalTasks').textContent = data.taskStats.total || 0;
611
+ document.getElementById('todoTasksCount').textContent = data.taskStats.todo || 0;
612
+ document.getElementById('inProgressTasksCount').textContent = data.taskStats.inProgress || 0;
613
+ document.getElementById('completedTasksCount').textContent = data.taskStats.completed || 0;
614
+
615
+ document.getElementById('todoStatCount').textContent = data.taskStats.todo || 0;
616
+ document.getElementById('inProgressStatCount').textContent = data.taskStats.inProgress || 0;
617
+ document.getElementById('completedStatCount').textContent = data.taskStats.completed || 0;
618
+ document.getElementById('blockedStatCount').textContent = data.taskStats.blocked || 0;
619
+
620
+ updateTaskStatusChart(data.taskStats);
621
+ }
622
+
603
623
  updateTypeChart(data.byType);
604
624
  updateTimeSeriesChart(data.timeSeries || {});
605
625
  updateScatterChart(data.scatterData || []);
@@ -655,6 +675,51 @@ function updateTypeChart(byType) {
655
675
  });
656
676
  }
657
677
 
678
+ function updateTaskStatusChart(taskStats) {
679
+ const ctx = document.getElementById('taskStatusChart');
680
+ if (!window.Chart || !ctx) return;
681
+ if (charts.taskStatusChart) charts.taskStatusChart.destroy();
682
+
683
+ const isDark = document.documentElement.classList.contains('dark');
684
+ const counts = [
685
+ taskStats?.todo || 0,
686
+ taskStats?.inProgress || 0,
687
+ taskStats?.completed || 0,
688
+ taskStats?.blocked || 0
689
+ ];
690
+
691
+ charts.taskStatusChart = new Chart(ctx, {
692
+ type: 'doughnut',
693
+ data: {
694
+ labels: ['To Do', 'In Progress', 'Completed', 'Blocked'],
695
+ datasets: [{
696
+ data: counts,
697
+ backgroundColor: ['#94a3b8', '#38bdf8', '#10b981', '#fb7185'],
698
+ borderWidth: 2,
699
+ borderColor: isDark ? '#1e293b' : '#ffffff',
700
+ hoverOffset: 12
701
+ }]
702
+ },
703
+ options: {
704
+ responsive: true,
705
+ maintainAspectRatio: false,
706
+ cutout: '68%',
707
+ plugins: {
708
+ legend: { display: false },
709
+ tooltip: {
710
+ backgroundColor: isDark ? '#1e293b' : '#ffffff',
711
+ titleColor: isDark ? '#f8fafc' : '#1e293b',
712
+ bodyColor: isDark ? '#94a3b8' : '#64748b',
713
+ borderColor: isDark ? '#334155' : '#e2e8f0',
714
+ borderWidth: 1,
715
+ padding: 10,
716
+ cornerRadius: 10
717
+ }
718
+ }
719
+ }
720
+ });
721
+ }
722
+
658
723
  function updateTopMemoriesChart(memories = []) {
659
724
  const ctx = document.getElementById('topMemoriesChart');
660
725
  if (!window.Chart) {
@@ -1089,6 +1154,7 @@ function renderTable(memories) {
1089
1154
  <td class="p-3">
1090
1155
  <div class="flex flex-col">
1091
1156
  <span class="text-[10px] font-bold text-sky-600 dark:text-sky-400 truncate max-w-[100px]" title="${m.agent || 'unknown'}">${m.agent || 'unknown'}</span>
1157
+ <span class="text-[9px] font-medium text-slate-500 dark:text-slate-400 truncate max-w-[100px]" title="${m.role || 'unknown'}">${m.role || 'unknown'}</span>
1092
1158
  <span class="text-[9px] text-gray-400 dark:text-gray-500 truncate max-w-[100px]" title="${m.model || 'unknown'}">${m.model || 'unknown'}</span>
1093
1159
  </div>
1094
1160
  </td>
@@ -1589,47 +1655,101 @@ let currentTasks = [];
1589
1655
 
1590
1656
  async function loadTasks() {
1591
1657
  if (!currentRepo) return;
1658
+
1659
+ // Reset pagination
1660
+ taskPagination.todo = { page: 1, pageSize: 20, hasMore: true, loading: false };
1661
+ taskPagination.in_progress = { page: 1, pageSize: 20, hasMore: true, loading: false };
1662
+ taskPagination.completed = { page: 1, pageSize: 20, hasMore: true, loading: false };
1663
+
1664
+ // Clear containers
1665
+ document.getElementById('todoTasks').innerHTML = '';
1666
+ document.getElementById('inProgressTasks').innerHTML = '';
1667
+ document.getElementById('completedTasks').innerHTML = '';
1668
+
1669
+ await Promise.all([
1670
+ loadTaskCategory('pending,blocked,canceled'),
1671
+ loadTaskCategory('in_progress'),
1672
+ loadTaskCategory('completed')
1673
+ ]);
1674
+
1675
+ setupTaskScrollListeners();
1676
+ }
1677
+
1678
+ async function loadTaskCategory(status) {
1679
+ const category = (status.includes('pending') || status.includes('blocked') || status.includes('canceled')) ? 'todo' : (status === 'in_progress' ? 'in_progress' : 'completed');
1680
+ const pag = taskPagination[category];
1681
+
1682
+ if (!pag.hasMore || pag.loading) return;
1683
+
1684
+ pag.loading = true;
1685
+ const containerId = { todo: 'todoTasks', in_progress: 'inProgressTasks', completed: 'completedTasks' }[category];
1686
+ const container = document.getElementById(containerId);
1687
+
1688
+ // Show loading indicator
1689
+ const loadingId = `loading-${category}`;
1690
+ if (!document.getElementById(loadingId)) {
1691
+ const loader = document.createElement('div');
1692
+ loader.id = loadingId;
1693
+ loader.className = 'py-4 text-center text-gray-400 text-[10px] animate-pulse w-full';
1694
+ loader.textContent = 'Loading more...';
1695
+ container.appendChild(loader);
1696
+ }
1697
+
1592
1698
  try {
1593
- const response = await fetch(`/api/tasks?repo=${encodeURIComponent(currentRepo)}`);
1699
+ const response = await fetch(`/api/tasks?repo=${encodeURIComponent(currentRepo)}&status=${status}&page=${pag.page}&pageSize=${pag.pageSize}`);
1594
1700
  const data = await response.json();
1595
- currentTasks = data.tasks || [];
1596
- renderTasks();
1701
+
1702
+ const tasks = data.tasks || [];
1703
+
1704
+ // Remove loader
1705
+ const loader = document.getElementById(loadingId);
1706
+ if (loader) loader.remove();
1707
+
1708
+ if (tasks.length < pag.pageSize) {
1709
+ pag.hasMore = false;
1710
+ }
1711
+
1712
+ renderTaskCards(containerId, tasks, pag.page === 1);
1713
+ pag.page++;
1597
1714
  } catch (err) {
1598
- console.error('Failed to load tasks:', err);
1715
+ console.error(`Failed to load ${category} tasks:`, err);
1716
+ } finally {
1717
+ pag.loading = false;
1599
1718
  }
1600
1719
  }
1601
1720
 
1602
- function renderTasks() {
1603
- if (!document.getElementById('todoTasks')) return;
1604
-
1605
- const todoTasks = currentTasks.filter(t => t.status === 'pending' || t.status === 'blocked');
1606
- const inProgressTasks = currentTasks.filter(t => t.status === 'in_progress');
1607
- const completedTasks = currentTasks.filter(t => t.status === 'completed');
1608
-
1609
- document.getElementById('todoCount').textContent = todoTasks.length;
1610
- document.getElementById('inProgressCount').textContent = inProgressTasks.length;
1611
- document.getElementById('completedCount').textContent = completedTasks.length;
1612
-
1613
- renderTaskColumn('todoTasks', todoTasks);
1614
- renderTaskColumn('inProgressTasks', inProgressTasks);
1615
- renderTaskColumn('completedTasks', completedTasks);
1721
+ function setupTaskScrollListeners() {
1722
+ ['todoTasks', 'inProgressTasks', 'completedTasks'].forEach(id => {
1723
+ const el = document.getElementById(id);
1724
+ if (!el) return;
1725
+
1726
+ el.onscroll = () => {
1727
+ if (el.scrollTop + el.clientHeight >= el.scrollHeight - 50) {
1728
+ const category = id === 'todoTasks' ? 'todo' : (id === 'inProgressTasks' ? 'in_progress' : 'completed');
1729
+ const status = category === 'todo' ? 'pending,blocked,canceled' : (category === 'in_progress' ? 'in_progress' : 'completed');
1730
+ loadTaskCategory(status);
1731
+ }
1732
+ };
1733
+ });
1616
1734
  }
1617
1735
 
1618
- function renderTaskColumn(id, tasks) {
1619
- const container = document.getElementById(id);
1736
+ function renderTaskCards(containerId, tasks, clear = false) {
1737
+ const container = document.getElementById(containerId);
1620
1738
  if (!container) return;
1621
-
1622
- if (!tasks || tasks.length === 0) {
1739
+
1740
+ if (clear && (!tasks || tasks.length === 0)) {
1623
1741
  container.innerHTML = '<div class="text-center py-8 text-gray-400 text-xs italic bg-gray-50/50 dark:bg-gray-900/20 rounded-xl border border-dashed border-gray-200 dark:border-gray-700">No tasks</div>';
1624
1742
  return;
1625
1743
  }
1626
1744
 
1627
- container.innerHTML = tasks.map(t => `
1745
+ const html = tasks.map(t => `
1628
1746
  <div class="bg-white dark:bg-gray-700 p-4 rounded-xl shadow-sm border border-gray-100 dark:border-gray-600 hover:shadow-md transition-all group">
1629
1747
  <div class="flex items-center justify-between mb-2">
1630
1748
  <div class="flex items-center gap-2">
1631
1749
  <span class="px-1.5 py-0.5 rounded bg-gray-100 dark:bg-gray-800 text-[10px] font-bold text-gray-600 dark:text-gray-400 font-mono border border-gray-200 dark:border-gray-700">${t.task_code}</span>
1632
1750
  <span class="text-[10px] font-bold uppercase tracking-wider text-gray-400">${t.phase}</span>
1751
+ ${t.status === 'blocked' ? '<span class="px-1 py-0.5 rounded bg-red-500 text-white text-[8px] font-bold uppercase">Blocked</span>' : ''}
1752
+ ${t.status === 'canceled' ? '<span class="px-1 py-0.5 rounded bg-slate-500 text-white text-[8px] font-bold uppercase">Canceled</span>' : ''}
1633
1753
  </div>
1634
1754
  <div class="flex items-center gap-1">
1635
1755
  ${t.priority >= 4 ? '<span class="w-2 h-2 rounded-full bg-red-500 animate-pulse"></span>' : ''}
@@ -1637,7 +1757,25 @@ function renderTaskColumn(id, tasks) {
1637
1757
  </div>
1638
1758
  </div>
1639
1759
  <h4 class="font-bold text-sm text-gray-900 dark:text-gray-100 mb-1">${escapeHtml(t.title)}</h4>
1640
- <p class="text-xs text-gray-500 dark:text-gray-400 line-clamp-2 mb-3">${escapeHtml(t.description || '')}</p>
1760
+ <p class="text-xs text-gray-500 dark:text-gray-400 line-clamp-2 mb-2">${escapeHtml(t.description || '')}</p>
1761
+
1762
+ ${t.doc_path ? `
1763
+ <div class="mb-3">
1764
+ <a href="${t.doc_path.startsWith('http') ? t.doc_path : '#'}" target="_blank" class="inline-flex items-center gap-1.5 px-2 py-1 rounded bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 text-[10px] text-slate-500 dark:text-gray-400 hover:text-sky-600 transition-colors">
1765
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path></svg>
1766
+ <span class="truncate max-w-[150px]">${escapeHtml(t.doc_path)}</span>
1767
+ </a>
1768
+ </div>
1769
+ ` : ''}
1770
+
1771
+ <div class="flex items-center gap-2 mb-3">
1772
+ <div class="px-1.5 py-0.5 rounded bg-sky-50 dark:bg-sky-900/30 border border-sky-100 dark:border-sky-800 flex items-center gap-1">
1773
+ <span class="text-[9px] font-bold text-sky-600 dark:text-sky-400">${escapeHtml(t.agent || 'unknown')}</span>
1774
+ <span class="text-[8px] text-sky-400 dark:text-sky-600">|</span>
1775
+ <span class="text-[9px] font-medium text-sky-500 dark:text-sky-500">${escapeHtml(t.role || 'unknown')}</span>
1776
+ </div>
1777
+ </div>
1778
+
1641
1779
  ${t.depends_on ? `
1642
1780
  <div class="mt-2 pt-2 border-t border-gray-50 dark:border-gray-600 flex items-center gap-1.5">
1643
1781
  <svg class="w-3 h-3 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"></path></svg>
@@ -1646,10 +1784,16 @@ function renderTaskColumn(id, tasks) {
1646
1784
  ` : ''}
1647
1785
  <div class="mt-3 flex items-center justify-between">
1648
1786
  <span class="text-[10px] font-mono text-gray-400">${t.id.substring(0, 8)}</span>
1649
- <span class="text-[10px] text-gray-400">${formatDate(t.created_at)}</span>
1787
+ <span class="text-[10px] text-gray-400">${t.status === 'completed' && t.finished_at ? 'Done ' + formatDate(t.finished_at) : formatDate(t.created_at)}</span>
1650
1788
  </div>
1651
1789
  </div>
1652
1790
  `).join('');
1791
+
1792
+ if (clear) {
1793
+ container.innerHTML = html;
1794
+ } else {
1795
+ container.insertAdjacentHTML('beforeend', html);
1796
+ }
1653
1797
  }
1654
1798
 
1655
1799
  function getPriorityColor(p) {
@@ -1665,25 +1809,27 @@ function switchTab(tab) {
1665
1809
  const dashTab = document.getElementById('dashboardTabBtn');
1666
1810
  const memTab = document.getElementById('memoriesTabBtn');
1667
1811
  const taskTab = document.getElementById('tasksTabBtn');
1812
+ const refTab = document.getElementById('referenceTabBtn');
1668
1813
  const indicator = document.getElementById('tabIndicator');
1669
-
1814
+
1670
1815
  const dashContent = document.getElementById('dashboardContent');
1671
1816
  const memContent = document.getElementById('memoriesContent');
1672
1817
  const taskContent = document.getElementById('tasksContent');
1818
+ const refContent = document.getElementById('referenceContent');
1673
1819
 
1674
1820
  currentTab = tab;
1675
1821
  localStorage.setItem('activeTab', tab);
1676
1822
 
1677
- const tabs = [dashTab, memTab, taskTab];
1678
- const contents = [dashContent, memContent, taskContent];
1679
- const targetId = tab + 'TabBtn';
1823
+ const tabs = [dashTab, memTab, taskTab, refTab];
1824
+ const contents = [dashContent, memContent, taskContent, refContent]; const targetId = tab + 'TabBtn';
1680
1825
  const targetContentId = tab + 'Content';
1681
1826
 
1682
1827
  // Update indicator
1683
1828
  if (indicator) {
1684
1829
  if (tab === 'dashboard') indicator.style.transform = 'translateX(0)';
1685
- else if (tab === 'memories') indicator.style.transform = 'translateX(100%)';
1686
- else if (tab === 'tasks') indicator.style.transform = 'translateX(200%)';
1830
+ if (tab === 'memories') indicator.style.transform = 'translateX(100%)';
1831
+ if (tab === 'tasks') indicator.style.transform = 'translateX(200%)';
1832
+ if (tab === 'reference') indicator.style.transform = 'translateX(300%)';
1687
1833
  }
1688
1834
 
1689
1835
  // Update button states
@@ -1714,6 +1860,8 @@ function switchTab(tab) {
1714
1860
  if (tab === 'tasks') loadTasks();
1715
1861
  if (tab === 'dashboard') loadStats();
1716
1862
  if (tab === 'memories') loadMemories();
1863
+ if (tab === 'reference') loadCapabilities();
1864
+ syncStickyOffsets();
1717
1865
  }
1718
1866
 
1719
1867
  window.loadTasks = loadTasks;
@@ -1767,6 +1915,185 @@ safeAddEventListener('repoCollapsedSummaryButton', 'click', () => {
1767
1915
 
1768
1916
  window.addEventListener('resize', syncStickyOffsets);
1769
1917
 
1918
+ let currentCapabilities = { tools: [], resources: [], prompts: [] };
1919
+
1920
+ async function loadCapabilities() {
1921
+ const toolsList = document.getElementById('toolsList');
1922
+ const resourcesList = document.getElementById('resourcesList');
1923
+ const promptsList = document.getElementById('promptsList');
1924
+
1925
+ if (!toolsList) return;
1926
+
1927
+ toolsList.innerHTML = '<div class="text-center py-4 text-gray-400 text-xs">Loading capabilities...</div>';
1928
+
1929
+ try {
1930
+ const response = await fetch('/api/capabilities');
1931
+ const data = await response.json();
1932
+
1933
+ currentCapabilities.tools = Array.isArray(data.tools) ? data.tools : [];
1934
+ currentCapabilities.resources = Array.isArray(data.resources) ? data.resources : [];
1935
+ currentCapabilities.prompts = Array.isArray(data.prompts) ? data.prompts : [];
1936
+
1937
+ toolsList.innerHTML = currentCapabilities.tools.map(t => `
1938
+ <div onclick="showCapabilityDetail('tools', '${t.name}')" class="p-3 bg-slate-50 dark:bg-slate-900/50 border border-slate-100 dark:border-slate-800 rounded-lg cursor-pointer hover:border-sky-500/50 transition-all group">
1939
+ <div class="font-bold text-xs text-sky-600 dark:text-sky-400 mb-1 group-hover:text-sky-500">${escapeHtml(t.name)}</div>
1940
+ <p class="text-[10px] text-gray-500 dark:text-gray-400 line-clamp-2">${escapeHtml(t.description)}</p>
1941
+ </div>
1942
+ `).join('') || '<div class="text-center py-4 text-gray-400 text-xs">No tools available</div>';
1943
+
1944
+ resourcesList.innerHTML = currentCapabilities.resources.map(r => `
1945
+ <div onclick="showCapabilityDetail('resources', '${r.name}')" class="p-3 bg-slate-50 dark:bg-slate-900/50 border border-slate-100 dark:border-slate-800 rounded-lg cursor-pointer hover:border-indigo-500/50 transition-all group">
1946
+ <div class="font-bold text-xs text-indigo-600 dark:text-indigo-400 mb-1 group-hover:text-indigo-500">${escapeHtml(r.name)}</div>
1947
+ <p class="text-[10px] text-gray-500 dark:text-gray-400 line-clamp-2">${escapeHtml(r.description || 'No description')}</p>
1948
+ <div class="text-[8px] font-mono text-gray-400 mt-1 truncate">${escapeHtml(r.uri)}</div>
1949
+ </div>
1950
+ `).join('') || '<div class="text-center py-4 text-gray-400 text-xs">No resources available</div>';
1951
+
1952
+ promptsList.innerHTML = currentCapabilities.prompts.map(p => `
1953
+ <div onclick="showCapabilityDetail('prompts', '${p.name}')" class="p-3 bg-slate-50 dark:bg-slate-900/50 border border-slate-100 dark:border-slate-800 rounded-lg cursor-pointer hover:border-emerald-500/50 transition-all group">
1954
+ <div class="font-bold text-xs text-emerald-600 dark:text-emerald-400 mb-1 group-hover:text-emerald-500">${escapeHtml(p.name)}</div>
1955
+ <p class="text-[10px] text-gray-500 dark:text-gray-400 line-clamp-2">${escapeHtml(p.description || 'No description')}</p>
1956
+ </div>
1957
+ `).join('') || '<div class="text-center py-4 text-gray-400 text-xs">No prompts available</div>';
1958
+
1959
+ } catch (err) {
1960
+ console.error('Failed to load capabilities:', err);
1961
+ toolsList.innerHTML = `<div class="text-center py-4 text-red-400 text-xs">Error: ${err.message}</div>`;
1962
+ }
1963
+ }
1964
+
1965
+ async function handleCsvImport(event) {
1966
+ const file = event.target.files[0];
1967
+ if (!file || !currentRepo) return;
1968
+
1969
+ const reader = new FileReader();
1970
+ reader.onload = async (e) => {
1971
+ const csvData = e.target.result;
1972
+ try {
1973
+ const response = await fetch('/api/tasks/import-csv', {
1974
+ method: 'POST',
1975
+ headers: { 'Content-Type': 'application/json' },
1976
+ body: JSON.stringify({ repo: currentRepo, csvData })
1977
+ });
1978
+ const result = await response.json();
1979
+ if (result.success) {
1980
+ showToast(`Successfully imported ${result.count} tasks`, 'success');
1981
+ loadTasks();
1982
+ loadStats();
1983
+ } else {
1984
+ showToast(result.error || 'Failed to import CSV', 'error');
1985
+ }
1986
+ } catch (err) {
1987
+ showToast('Import failed: ' + err.message, 'error');
1988
+ }
1989
+ };
1990
+ reader.readAsText(file);
1991
+ event.target.value = '';
1992
+ }
1993
+
1994
+ function downloadCsvTemplate() {
1995
+ const headers = "task_code,phase,title,description,priority,status,agent,role,doc_path";
1996
+ const example = "TASK-001,research,Integrate CSV,Add import feature to dashboard,4,pending,Gemini CLI,expert,docs/tasks.md";
1997
+ const csv = `${headers}\n${example}`;
1998
+
1999
+ const blob = new Blob([csv], { type: 'text/csv' });
2000
+ const url = window.URL.createObjectURL(blob);
2001
+ const a = document.createElement('a');
2002
+ a.setAttribute('hidden', '');
2003
+ a.setAttribute('href', url);
2004
+ a.setAttribute('download', 'task_template.csv');
2005
+ document.body.appendChild(a);
2006
+ a.click();
2007
+ document.body.removeChild(a);
2008
+ }
2009
+
2010
+ function showCapabilityDetail(type, name) {
2011
+ const item = currentCapabilities[type].find(i => i.name === name);
2012
+ if (!item) return;
2013
+
2014
+ const drawer = document.getElementById('memoryDrawer');
2015
+ const title = document.getElementById('drawerTitle');
2016
+ const body = document.getElementById('drawerBody');
2017
+
2018
+ title.textContent = `${type.charAt(0).toUpperCase() + type.slice(1)}: ${name}`;
2019
+
2020
+ let contentHtml = `
2021
+ <div class="space-y-6">
2022
+ <div class="p-4 bg-slate-50 dark:bg-slate-900/50 rounded-xl border border-slate-100 dark:border-slate-800">
2023
+ <h4 class="text-xs font-bold text-slate-400 uppercase mb-2">Description</h4>
2024
+ <p class="text-sm text-slate-600 dark:text-slate-300">${escapeHtml(item.description || 'No description')}</p>
2025
+ </div>
2026
+ `;
2027
+
2028
+ if (type === 'tools' && item.inputSchema) {
2029
+ contentHtml += `
2030
+ <div class="p-4 bg-slate-50 dark:bg-slate-900/50 rounded-xl border border-slate-100 dark:border-slate-800">
2031
+ <h4 class="text-xs font-bold text-slate-400 uppercase mb-2">Input Schema</h4>
2032
+ <pre class="text-[10px] font-mono text-sky-600 dark:text-sky-400 overflow-x-auto">${JSON.stringify(item.inputSchema, null, 2)}</pre>
2033
+ </div>
2034
+ `;
2035
+ }
2036
+
2037
+ if (type === 'resources' && item.uri) {
2038
+ contentHtml += `
2039
+ <div class="p-4 bg-slate-50 dark:bg-slate-900/50 rounded-xl border border-slate-100 dark:border-slate-800">
2040
+ <h4 class="text-xs font-bold text-slate-400 uppercase mb-2">URI</h4>
2041
+ <code class="text-xs font-mono text-indigo-600 dark:text-indigo-400">${escapeHtml(item.uri)}</code>
2042
+ </div>
2043
+ `;
2044
+ }
2045
+
2046
+ if (type === 'prompts' && item.arguments) {
2047
+ contentHtml += `
2048
+ <div class="p-4 bg-slate-50 dark:bg-slate-900/50 rounded-xl border border-slate-100 dark:border-slate-800">
2049
+ <h4 class="text-xs font-bold text-slate-400 uppercase mb-2">Arguments</h4>
2050
+ <div class="space-y-3">
2051
+ ${item.arguments.map(arg => `
2052
+ <div class="border-l-2 border-emerald-500 pl-3 py-1">
2053
+ <div class="text-xs font-bold text-emerald-600 dark:text-emerald-400">${escapeHtml(arg.name)} ${arg.required ? '<span class="text-[8px] bg-emerald-500 text-white px-1 rounded">REQUIRED</span>' : ''}</div>
2054
+ <div class="text-[10px] text-gray-500 dark:text-gray-400">${escapeHtml(arg.description || '')}</div>
2055
+ </div>
2056
+ `).join('')}
2057
+ </div>
2058
+ </div>
2059
+ `;
2060
+ }
2061
+
2062
+ if (type === 'prompts' && item.messages) {
2063
+ contentHtml += `
2064
+ <div class="p-4 bg-slate-50 dark:bg-slate-900/50 rounded-xl border border-slate-100 dark:border-slate-800">
2065
+ <h4 class="text-xs font-bold text-slate-400 uppercase mb-2">Instructions</h4>
2066
+ <div class="space-y-4">
2067
+ ${item.messages.map(msg => {
2068
+ const rawContent = typeof msg.content === 'string' ? msg.content : (msg.content?.text || '');
2069
+ const renderedMarkdown = window.marked ? window.marked.parse(rawContent) : escapeHtml(rawContent);
2070
+ return `
2071
+ <div class="space-y-1">
2072
+ <div class="text-[10px] font-bold uppercase tracking-wider text-slate-400">${escapeHtml(msg.role)}</div>
2073
+ <div class="p-4 bg-white dark:bg-slate-900 border border-slate-100 dark:border-slate-800 rounded-lg text-sm markdown-body">
2074
+ ${renderedMarkdown}
2075
+ </div>
2076
+ </div>
2077
+ `;
2078
+ }).join('')}
2079
+ </div>
2080
+ </div>
2081
+ `;
2082
+ }
2083
+
2084
+ contentHtml += `</div>`;
2085
+ body.innerHTML = contentHtml;
2086
+
2087
+ drawer.classList.remove('hidden');
2088
+ document.body.classList.add('drawer-open');
2089
+ setTimeout(() => document.getElementById('drawerAside').classList.remove('translate-x-full'), 10);
2090
+ }
2091
+
2092
+ window.handleCsvImport = handleCsvImport;
2093
+ window.downloadCsvTemplate = downloadCsvTemplate;
2094
+ window.loadCapabilities = loadCapabilities;
2095
+ window.showCapabilityDetail = showCapabilityDetail;
2096
+
1770
2097
  initTheme();
1771
2098
  initRepoSidebarState();
1772
2099
  initPinnedRepos();