saga-mcp 1.7.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 +2 -0
- package/dist/web/ui.js +79 -2
- package/dist/web/ui.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/saga-mcp)
|
|
4
4
|
[](https://www.npmjs.com/package/saga-mcp)
|
|
5
|
+
[](https://github.com/spranab/saga-mcp/blob/master/LICENSE)
|
|
5
6
|
[](https://ideacred.com/profile/spranab)
|
|
6
7
|
|
|
7
8
|
Your coding agent loses the plan between sessions. You come back tomorrow and
|
|
@@ -386,6 +387,7 @@ What you get:
|
|
|
386
387
|
- **Notes** and **Activity** — decisions and the complete change history
|
|
387
388
|
- **Task drawer** — edit any field, tick subtasks, comment, remove or restore a comment
|
|
388
389
|
- **Project switcher** — every project in the database, so one central `.tracker.db` covers all your repos; every tab, including Activity, is scoped to the selected project
|
|
390
|
+
- **Shareable, refreshable URLs** — the open project, tab and task live in the address bar, so a browser refresh puts you back where you were and back/forward move between tasks. A ⟳ button in the task drawer re-reads that task without a page reload, for picking up what an agent just wrote
|
|
389
391
|
|
|
390
392
|
Writes from the UI call the *same handlers* the MCP tools do, so edits you make by hand are
|
|
391
393
|
validated identically and land in the same activity log as the agent's — an agent calling
|
package/dist/web/ui.js
CHANGED
|
@@ -400,6 +400,39 @@ function modal(title, fields, submitLabel, onSubmit) {
|
|
|
400
400
|
|
|
401
401
|
/* ---------- shell ---------- */
|
|
402
402
|
|
|
403
|
+
/* ---------- url state ---------- */
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Project, tab and the open task live in the location hash. A browser refresh
|
|
407
|
+
* then puts you back exactly where you were instead of dumping you on the
|
|
408
|
+
* overview with the task drawer closed, and back/forward work.
|
|
409
|
+
*/
|
|
410
|
+
var applyingHash = false;
|
|
411
|
+
|
|
412
|
+
function syncHash() {
|
|
413
|
+
if (applyingHash) return;
|
|
414
|
+
var parts = [];
|
|
415
|
+
if (S.projectId) parts.push('p=' + S.projectId);
|
|
416
|
+
if (S.tab && S.tab !== 'overview') parts.push('tab=' + S.tab);
|
|
417
|
+
if (S.task) parts.push('task=' + S.task.id);
|
|
418
|
+
var next = parts.length ? '#' + parts.join('&') : '#';
|
|
419
|
+
if (next !== location.hash) history.replaceState(null, '', next);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function readHash() {
|
|
423
|
+
var out = {};
|
|
424
|
+
var raw = location.hash.replace(/^#/, '');
|
|
425
|
+
if (!raw) return out;
|
|
426
|
+
raw.split('&').forEach(function (pair) {
|
|
427
|
+
var i = pair.indexOf('=');
|
|
428
|
+
if (i < 0) return;
|
|
429
|
+
var k = pair.slice(0, i), v = pair.slice(i + 1);
|
|
430
|
+
if (k === 'p' || k === 'task') { var n = Number(v); if (n > 0) out[k] = n; }
|
|
431
|
+
if (k === 'tab' && TABS.some(function (t) { return t[0] === v; })) out.tab = v;
|
|
432
|
+
});
|
|
433
|
+
return out;
|
|
434
|
+
}
|
|
435
|
+
|
|
403
436
|
function renderTabs() {
|
|
404
437
|
el('tabs').innerHTML = TABS.map(function (t) {
|
|
405
438
|
return '<button data-tab="' + t[0] + '" class="' + (S.tab === t[0] ? 'active' : '') + '">' + t[1] + '</button>';
|
|
@@ -447,6 +480,7 @@ function refresh() {
|
|
|
447
480
|
|
|
448
481
|
function render() {
|
|
449
482
|
renderTabs();
|
|
483
|
+
syncHash();
|
|
450
484
|
el('roBadge').hidden = !S.readOnly;
|
|
451
485
|
if (S.query) return renderSearch();
|
|
452
486
|
var fns = { overview: viewOverview, board: viewBoard, epics: viewEpics,
|
|
@@ -680,11 +714,13 @@ function openTask(id) {
|
|
|
680
714
|
return get('/api/tasks/' + id + (S.showDeleted ? '?include_deleted=1' : '')).then(function (t) {
|
|
681
715
|
S.task = t;
|
|
682
716
|
drawTask();
|
|
717
|
+
syncHash();
|
|
683
718
|
}).catch(oops);
|
|
684
719
|
}
|
|
685
720
|
|
|
686
721
|
function closeDrawer() {
|
|
687
722
|
S.task = null;
|
|
723
|
+
syncHash();
|
|
688
724
|
var d = document.querySelector('.drawer');
|
|
689
725
|
var b = document.querySelector('.drawer-backdrop');
|
|
690
726
|
if (d) d.remove();
|
|
@@ -710,6 +746,7 @@ function drawTask() {
|
|
|
710
746
|
d.className = 'drawer';
|
|
711
747
|
|
|
712
748
|
var h = '<div class="row"><span class="grow"></span>' +
|
|
749
|
+
'<button class="btn" id="refreshTask" title="Re-read this task from the database">⟳ Refresh</button>' +
|
|
713
750
|
(ed ? '<button class="btn" id="editTask">Edit task</button>' : '') +
|
|
714
751
|
'<button class="btn" id="closeDrawer">Close ✕</button></div>';
|
|
715
752
|
h += '<h2 style="margin:6px 0 8px;font-size:18px">' + esc(t.title) + '</h2>';
|
|
@@ -951,6 +988,12 @@ document.addEventListener('click', function (ev) {
|
|
|
951
988
|
if (tabBtn) { S.tab = tabBtn.dataset.tab; S.query = ''; el('q').value = ''; render(); return; }
|
|
952
989
|
|
|
953
990
|
if (target.id === 'closeDrawer') return closeDrawer();
|
|
991
|
+
if (target.id === 'refreshTask') {
|
|
992
|
+
var btn = target;
|
|
993
|
+
btn.disabled = true;
|
|
994
|
+
btn.textContent = '⟳ …';
|
|
995
|
+
return refresh().then(function () { toast('Refreshed'); }).catch(oops);
|
|
996
|
+
}
|
|
954
997
|
if (target.id === 'clearSearch') { S.query = ''; el('q').value = ''; render(); return; }
|
|
955
998
|
if (target.id === 'reload') { toast('Reloaded'); refresh(); return; }
|
|
956
999
|
if (target.id === 'expandAll') { S.overview.epics.forEach(function (e) { S.epicOpen[e.id] = true; }); render(); return; }
|
|
@@ -1177,16 +1220,50 @@ document.addEventListener('drop', function (ev) {
|
|
|
1177
1220
|
|
|
1178
1221
|
/* ---------- boot ---------- */
|
|
1179
1222
|
|
|
1223
|
+
// Back and forward move between tasks and tabs rather than leaving the page.
|
|
1224
|
+
// Registered before boot so it survives a failed first fetch.
|
|
1225
|
+
window.addEventListener('hashchange', function () {
|
|
1226
|
+
var want = readHash();
|
|
1227
|
+
var openId = S.task ? S.task.id : null;
|
|
1228
|
+
if (want.task === openId && (want.tab || 'overview') === S.tab &&
|
|
1229
|
+
(!want.p || want.p === S.projectId)) return;
|
|
1230
|
+
|
|
1231
|
+
applyingHash = true;
|
|
1232
|
+
try {
|
|
1233
|
+
if (want.p && want.p !== S.projectId && S.projects.some(function (p) { return p.id === want.p; })) {
|
|
1234
|
+
S.projectId = want.p;
|
|
1235
|
+
el('projectSel').value = String(want.p);
|
|
1236
|
+
S.epicOpen = {};
|
|
1237
|
+
loadProject();
|
|
1238
|
+
}
|
|
1239
|
+
S.tab = want.tab || 'overview';
|
|
1240
|
+
render();
|
|
1241
|
+
if (want.task) openTask(want.task);
|
|
1242
|
+
else if (S.task) closeDrawer();
|
|
1243
|
+
} finally {
|
|
1244
|
+
applyingHash = false;
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1180
1248
|
get('/api/projects').then(function (r) {
|
|
1181
1249
|
S.projects = r.projects;
|
|
1182
1250
|
S.readOnly = !!r.read_only;
|
|
1183
1251
|
el('dbpath').textContent = r.db_path;
|
|
1184
1252
|
el('roBadge').hidden = !S.readOnly;
|
|
1185
1253
|
if (S.readOnly) el('newProject').hidden = true;
|
|
1186
|
-
|
|
1254
|
+
|
|
1255
|
+
var want = readHash();
|
|
1256
|
+
var known = S.projects.some(function (p) { return p.id === want.p; });
|
|
1257
|
+
S.projectId = known ? want.p : (S.projects.length ? S.projects[0].id : null);
|
|
1258
|
+
if (want.tab) S.tab = want.tab;
|
|
1259
|
+
|
|
1187
1260
|
renderProjects();
|
|
1188
|
-
loadProject()
|
|
1261
|
+
loadProject().then(function () {
|
|
1262
|
+
// Reopen whatever task the URL names, so a browser refresh keeps your place.
|
|
1263
|
+
if (want.task) return openTask(want.task);
|
|
1264
|
+
});
|
|
1189
1265
|
}).catch(fail);
|
|
1266
|
+
|
|
1190
1267
|
</script>
|
|
1191
1268
|
</body>
|
|
1192
1269
|
</html>`;
|
package/dist/web/ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,IAAI,GAAW
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,IAAI,GAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA2uCpB,CAAC"}
|
package/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"manifest_version": "0.3",
|
|
3
3
|
"name": "saga-mcp",
|
|
4
4
|
"display_name": "Saga — Project Tracker for AI Agents",
|
|
5
|
-
"version": "1.7.
|
|
5
|
+
"version": "1.7.1",
|
|
6
6
|
"description": "A Jira-like project tracker MCP server for AI agents. SQLite-backed, per-project scoped, with full hierarchy and activity logging — so LLMs never lose track.",
|
|
7
7
|
"long_description": "Saga gives your AI assistant a structured SQLite database to track projects, epics, tasks, subtasks, notes, and decisions across sessions. No more scattered markdown files — one `tracker_dashboard` call gives full project context to resume work.\n\n**Key features:**\n- Full hierarchy: Projects > Epics > Tasks > Subtasks\n- Task dependencies: Express sequencing with auto-block/unblock\n- Comments: Threaded discussions on tasks for decision trails, with reversible soft-delete\n- Templates: Reusable task sets with variable substitution\n- Dashboard: One tool call gives full overview with natural language summary\n- SQLite: Self-contained `.tracker.db` file per project — zero setup\n- Activity log: Every mutation is automatically tracked\n- Notes system: Decisions, context, meeting notes, blockers\n- Batch operations: Create multiple subtasks or update multiple tasks in one call\n- 33 focused tools with safety annotations\n- Web UI: `saga-web` serves a local dashboard for browsing and editing the same database\n- Import/export: Full project backup and migration as JSON",
|
|
8
8
|
"author": {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saga-mcp",
|
|
3
3
|
"mcpName": "io.github.spranab/saga-mcp",
|
|
4
|
-
"version": "1.7.
|
|
4
|
+
"version": "1.7.1",
|
|
5
5
|
"description": "A Jira-like project tracker MCP server for AI agents. SQLite-backed, per-project scoped, with full hierarchy (Projects > Epics > Tasks > Subtasks), activity logging, and a dashboard \u2014 so LLMs never lose track.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|