claude-autopm 3.22.0 → 3.22.2
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.
|
@@ -132,8 +132,7 @@ function generateEpicFlowDiagram() {
|
|
|
132
132
|
}
|
|
133
133
|
} catch {}
|
|
134
134
|
if (epics.length === 0 && prds.length === 0) {
|
|
135
|
-
|
|
136
|
-
return lines.join('\n');
|
|
135
|
+
return { mermaid: '', hasData: false };
|
|
137
136
|
}
|
|
138
137
|
let nodeId = 0;
|
|
139
138
|
for (const prd of prds) {
|
|
@@ -159,7 +158,7 @@ function generateEpicFlowDiagram() {
|
|
|
159
158
|
lines.push(` ${eid} --> ${tid}["${t.name} ${t.icon}"]`);
|
|
160
159
|
}
|
|
161
160
|
}
|
|
162
|
-
return lines.join('\n');
|
|
161
|
+
return { mermaid: lines.join('\n'), hasData: true };
|
|
163
162
|
}
|
|
164
163
|
|
|
165
164
|
function generatePluginGraph() {
|
|
@@ -253,18 +252,22 @@ function getProjectDiagrams() {
|
|
|
253
252
|
for (const f of entries) {
|
|
254
253
|
if (!f.endsWith('.mmd')) continue;
|
|
255
254
|
try {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
255
|
+
const name = f.replace(/\.mmd$/, '');
|
|
256
|
+
const d = { name, content: fs.readFileSync(path.join(diagramsDir, f), 'utf8') };
|
|
257
|
+
// Load metadata if available
|
|
258
|
+
const metaPath = path.join(diagramsDir, name + '.meta.json');
|
|
259
|
+
try { const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8')); d.type = meta.type || 'custom'; } catch { d.type = 'custom'; }
|
|
260
|
+
diagrams.push(d);
|
|
260
261
|
} catch { /* skip unreadable */ }
|
|
261
262
|
}
|
|
262
263
|
return diagrams;
|
|
263
264
|
}
|
|
264
265
|
|
|
265
266
|
function getDiagramsData() {
|
|
267
|
+
const epic = generateEpicFlowDiagram();
|
|
266
268
|
return {
|
|
267
|
-
epicFlow:
|
|
269
|
+
epicFlow: epic.mermaid,
|
|
270
|
+
epicFlowHasData: epic.hasData,
|
|
268
271
|
projectDiagrams: getProjectDiagrams(),
|
|
269
272
|
pluginGraph: generatePluginGraph(),
|
|
270
273
|
agentTree: generateAgentTree()
|
|
@@ -287,6 +290,7 @@ function renderHTML() {
|
|
|
287
290
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
288
291
|
<title>AutoPM Config Dashboard</title>
|
|
289
292
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"><\/script>
|
|
293
|
+
<script src="https://cdn.jsdelivr.net/npm/panzoom@9.4.3/dist/panzoom.min.js"><\/script>
|
|
290
294
|
<style>
|
|
291
295
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
292
296
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0d1117; color: #c9d1d9; padding: 24px; }
|
|
@@ -364,6 +368,67 @@ function renderHTML() {
|
|
|
364
368
|
.toast.ok { background: #238636; }
|
|
365
369
|
.toast.err { background: #da3633; }
|
|
366
370
|
footer { margin-top: 24px; text-align: center; color: #484f58; font-size: 12px; }
|
|
371
|
+
/* Diagram list */
|
|
372
|
+
.diagram-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
|
|
373
|
+
.diagram-grid-item {
|
|
374
|
+
background: #0d1117; border: 1px solid #30363d; border-radius: 8px; padding: 16px; cursor: pointer; transition: border-color 0.15s;
|
|
375
|
+
}
|
|
376
|
+
.diagram-grid-item:hover { border-color: #58a6ff; }
|
|
377
|
+
.diagram-grid-item h4 { color: #58a6ff; margin-bottom: 4px; }
|
|
378
|
+
.diagram-grid-item .diagram-meta { color: #8b949e; font-size: 11px; }
|
|
379
|
+
.diagram-grid-item .diagram-preview { height: 100px; overflow: hidden; opacity: 0.6; margin-top: 8px; }
|
|
380
|
+
.diagram-grid-item .diagram-preview svg { max-height: 100px; width: 100%; }
|
|
381
|
+
/* Diagram split editor */
|
|
382
|
+
.diagram-editor { display: none; }
|
|
383
|
+
.diagram-editor.active { display: flex; flex-direction: column; }
|
|
384
|
+
.diagram-editor-toolbar {
|
|
385
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
386
|
+
padding: 8px 0; margin-bottom: 8px;
|
|
387
|
+
}
|
|
388
|
+
.diagram-editor-toolbar .left { display: flex; align-items: center; gap: 12px; }
|
|
389
|
+
.diagram-editor-toolbar .right { display: flex; gap: 6px; }
|
|
390
|
+
.diagram-editor-toolbar button {
|
|
391
|
+
background: #21262d; color: #8b949e; border: 1px solid #30363d;
|
|
392
|
+
padding: 5px 12px; border-radius: 4px; cursor: pointer; font-size: 12px;
|
|
393
|
+
}
|
|
394
|
+
.diagram-editor-toolbar button:hover { background: #30363d; color: #c9d1d9; }
|
|
395
|
+
.diagram-editor-toolbar .btn-save { background: #238636; color: #fff; border-color: #238636; }
|
|
396
|
+
.diagram-editor-toolbar .btn-save:hover { background: #2ea043; }
|
|
397
|
+
.diagram-editor-toolbar .btn-back { color: #58a6ff; }
|
|
398
|
+
.diagram-split {
|
|
399
|
+
display: flex; gap: 0; flex: 1; min-height: 500px;
|
|
400
|
+
border: 1px solid #30363d; border-radius: 8px; overflow: hidden;
|
|
401
|
+
}
|
|
402
|
+
.diagram-split .code-pane {
|
|
403
|
+
flex: 1; display: flex; flex-direction: column; border-right: 1px solid #30363d; min-width: 0;
|
|
404
|
+
}
|
|
405
|
+
.diagram-split .code-pane .pane-header {
|
|
406
|
+
background: #161b22; padding: 6px 12px; font-size: 11px; color: #8b949e;
|
|
407
|
+
border-bottom: 1px solid #30363d; display: flex; justify-content: space-between; align-items: center;
|
|
408
|
+
}
|
|
409
|
+
.diagram-split .code-pane textarea {
|
|
410
|
+
flex: 1; background: #0d1117; color: #c9d1d9; border: none; padding: 12px;
|
|
411
|
+
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace; font-size: 13px;
|
|
412
|
+
line-height: 1.5; resize: none; outline: none; tab-size: 2;
|
|
413
|
+
}
|
|
414
|
+
.diagram-split .preview-pane {
|
|
415
|
+
flex: 1; display: flex; flex-direction: column; background: #161b22; min-width: 0;
|
|
416
|
+
}
|
|
417
|
+
.diagram-split .preview-pane .pane-header {
|
|
418
|
+
background: #161b22; padding: 6px 12px; font-size: 11px; color: #8b949e;
|
|
419
|
+
border-bottom: 1px solid #30363d; display: flex; justify-content: space-between; align-items: center;
|
|
420
|
+
}
|
|
421
|
+
.diagram-split .preview-pane .pane-header button {
|
|
422
|
+
background: none; border: none; color: #8b949e; cursor: pointer; font-size: 11px; padding: 2px 6px;
|
|
423
|
+
}
|
|
424
|
+
.diagram-split .preview-pane .pane-header button:hover { color: #c9d1d9; }
|
|
425
|
+
.diagram-split .preview-container {
|
|
426
|
+
flex: 1; overflow: hidden; position: relative; background: #0d1117;
|
|
427
|
+
}
|
|
428
|
+
.diagram-split .preview-container svg { width: 100%; height: 100%; }
|
|
429
|
+
.panzoom-hint {
|
|
430
|
+
position: absolute; bottom: 8px; right: 12px; color: #484f58; font-size: 10px; pointer-events: none; z-index: 1;
|
|
431
|
+
}
|
|
367
432
|
</style>
|
|
368
433
|
</head>
|
|
369
434
|
<body>
|
|
@@ -494,13 +559,44 @@ function renderHTML() {
|
|
|
494
559
|
|
|
495
560
|
<!-- Diagrams Tab -->
|
|
496
561
|
<div id="tab-diagrams" class="tab-content">
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
<
|
|
562
|
+
<!-- Diagram list view -->
|
|
563
|
+
<div id="diagram-list-view">
|
|
564
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
|
565
|
+
<p class="desc" style="margin:0;">Click a diagram to view and edit. Create new: <code>/pm:diagram-new <name></code></p>
|
|
566
|
+
</div>
|
|
567
|
+
<div id="diagram-grid" class="diagram-grid"></div>
|
|
568
|
+
</div>
|
|
569
|
+
<!-- Diagram editor view (split: code | preview) -->
|
|
570
|
+
<div id="diagram-editor-view" class="diagram-editor">
|
|
571
|
+
<div class="diagram-editor-toolbar">
|
|
572
|
+
<div class="left">
|
|
573
|
+
<button class="btn-back" onclick="closeDiagramEditor()">← Back</button>
|
|
574
|
+
<strong id="editor-diagram-name" style="color:#c9d1d9;font-size:14px;"></strong>
|
|
575
|
+
<span id="editor-diagram-meta" style="color:#8b949e;font-size:11px;"></span>
|
|
576
|
+
<span id="editor-unsaved" style="color:#d29922;font-size:11px;display:none;">● unsaved</span>
|
|
577
|
+
</div>
|
|
578
|
+
<div class="right">
|
|
579
|
+
<button onclick="downloadCurrentDiagram()">↓ .mmd</button>
|
|
580
|
+
<button class="btn-save" onclick="saveDiagram()">Save</button>
|
|
581
|
+
</div>
|
|
582
|
+
</div>
|
|
583
|
+
<div class="diagram-split">
|
|
584
|
+
<div class="code-pane">
|
|
585
|
+
<div class="pane-header"><span>Mermaid Source</span></div>
|
|
586
|
+
<textarea id="diagram-code" spellcheck="false"></textarea>
|
|
587
|
+
</div>
|
|
588
|
+
<div class="preview-pane">
|
|
589
|
+
<div class="pane-header">
|
|
590
|
+
<span>Preview</span>
|
|
591
|
+
<button onclick="resetPanZoom()">Reset zoom</button>
|
|
592
|
+
</div>
|
|
593
|
+
<div class="preview-container" id="diagram-preview-container">
|
|
594
|
+
<div id="diagram-preview-render"></div>
|
|
595
|
+
<div class="panzoom-hint">Scroll to zoom · Drag to pan</div>
|
|
596
|
+
</div>
|
|
597
|
+
</div>
|
|
501
598
|
</div>
|
|
502
599
|
</div>
|
|
503
|
-
<div id="project-diagrams"></div>
|
|
504
600
|
</div>
|
|
505
601
|
|
|
506
602
|
<!-- Tests Tab -->
|
|
@@ -534,56 +630,182 @@ const headers = { 'Authorization': 'Bearer ' + TOKEN, 'Content-Type': 'applicati
|
|
|
534
630
|
function esc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
|
535
631
|
|
|
536
632
|
function showTab(name, btn) {
|
|
633
|
+
// Close diagram editor if leaving diagrams tab
|
|
634
|
+
if (currentDiagramName && name !== 'diagrams') closeDiagramEditor();
|
|
537
635
|
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
|
|
538
636
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
539
637
|
document.getElementById('tab-' + name).classList.add('active');
|
|
540
638
|
btn.classList.add('active');
|
|
541
|
-
if (name === 'diagrams')
|
|
639
|
+
if (name === 'diagrams') renderDiagramsTab();
|
|
640
|
+
// Pause auto-refresh on tabs with editable forms
|
|
641
|
+
editingLock = (name === 'mcp' || name === 'diagrams');
|
|
542
642
|
}
|
|
543
643
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
644
|
+
// --- Diagram editor state ---
|
|
645
|
+
let diagramsLoaded = false;
|
|
646
|
+
let allDiagrams = [];
|
|
647
|
+
let currentDiagramName = null;
|
|
648
|
+
let currentDiagramSaved = '';
|
|
649
|
+
let panzoomInstance = null;
|
|
650
|
+
let previewDebounce = null;
|
|
651
|
+
|
|
652
|
+
function renderDiagramsTab() {
|
|
653
|
+
if (diagramsLoaded) return;
|
|
547
654
|
fetch('/api/diagrams', { headers: { 'Authorization': 'Bearer ' + TOKEN } })
|
|
548
655
|
.then(r => r.json())
|
|
549
656
|
.then(data => {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
657
|
+
// Merge system-generated + project diagrams into one list
|
|
658
|
+
allDiagrams = [];
|
|
659
|
+
if (data.epicFlowHasData) {
|
|
660
|
+
allDiagrams.push({ name: 'epic-flow', content: data.epicFlow, type: 'system', meta: 'PRD → Epic → Tasks' });
|
|
661
|
+
}
|
|
662
|
+
allDiagrams.push({ name: 'plugins', content: data.pluginGraph, type: 'system', meta: 'Plugin dependencies' });
|
|
663
|
+
allDiagrams.push({ name: 'agents', content: data.agentTree, type: 'system', meta: 'Agent selection tree' });
|
|
664
|
+
if (data.projectDiagrams) {
|
|
557
665
|
data.projectDiagrams.forEach(d => {
|
|
558
|
-
|
|
559
|
-
card.className = 'card';
|
|
560
|
-
card.innerHTML = '<h3>' + esc(d.name) + '</h3>';
|
|
561
|
-
const pre = document.createElement('pre');
|
|
562
|
-
pre.className = 'mermaid';
|
|
563
|
-
pre.textContent = d.content;
|
|
564
|
-
card.appendChild(pre);
|
|
565
|
-
container.appendChild(card);
|
|
666
|
+
allDiagrams.push({ name: d.name, content: d.content, type: 'project', meta: d.type || 'custom' });
|
|
566
667
|
});
|
|
567
|
-
} else {
|
|
568
|
-
const msg = document.createElement('div');
|
|
569
|
-
msg.className = 'card';
|
|
570
|
-
msg.innerHTML = '<p class="desc">No project diagrams yet. Run <code>/pm:diagram-new</code> to create your first diagram.</p>';
|
|
571
|
-
container.appendChild(msg);
|
|
572
668
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
669
|
+
renderDiagramGrid();
|
|
670
|
+
diagramsLoaded = true;
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function renderDiagramGrid() {
|
|
675
|
+
const grid = document.getElementById('diagram-grid');
|
|
676
|
+
grid.textContent = '';
|
|
677
|
+
if (allDiagrams.length === 0) {
|
|
678
|
+
grid.innerHTML = '<p class="desc">No diagrams yet. Run <code>/pm:diagram-new <name></code> to create one.</p>';
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
allDiagrams.forEach(d => {
|
|
682
|
+
const item = document.createElement('div');
|
|
683
|
+
item.className = 'diagram-grid-item';
|
|
684
|
+
item.setAttribute('role', 'button');
|
|
685
|
+
item.setAttribute('tabindex', '0');
|
|
686
|
+
item.onclick = function() { openDiagramEditor(d.name); };
|
|
687
|
+
item.onkeydown = function(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openDiagramEditor(d.name); } };
|
|
688
|
+
const h4 = document.createElement('h4');
|
|
689
|
+
h4.textContent = d.name;
|
|
690
|
+
const meta = document.createElement('div');
|
|
691
|
+
meta.className = 'diagram-meta';
|
|
692
|
+
meta.textContent = d.type === 'system' ? '⚙ ' + d.meta : '📄 ' + d.meta;
|
|
693
|
+
const preview = document.createElement('div');
|
|
694
|
+
preview.className = 'diagram-preview';
|
|
695
|
+
const pre = document.createElement('pre');
|
|
696
|
+
pre.className = 'mermaid';
|
|
697
|
+
pre.textContent = d.content;
|
|
698
|
+
preview.appendChild(pre);
|
|
699
|
+
item.appendChild(h4);
|
|
700
|
+
item.appendChild(meta);
|
|
701
|
+
item.appendChild(preview);
|
|
702
|
+
grid.appendChild(item);
|
|
703
|
+
});
|
|
704
|
+
// Render mini previews
|
|
705
|
+
if (typeof mermaid !== 'undefined' && mermaid.run) {
|
|
706
|
+
mermaid.run({ nodes: grid.querySelectorAll('.mermaid') });
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function openDiagramEditor(name) {
|
|
711
|
+
const d = allDiagrams.find(x => x.name === name);
|
|
712
|
+
if (!d) return;
|
|
713
|
+
currentDiagramName = name;
|
|
714
|
+
currentDiagramSaved = d.content;
|
|
715
|
+
document.getElementById('editor-diagram-name').textContent = name;
|
|
716
|
+
document.getElementById('editor-diagram-meta').textContent = d.type === 'system' ? '(system — read-only)' : '';
|
|
717
|
+
document.getElementById('editor-unsaved').style.display = 'none';
|
|
718
|
+
const codeEl = document.getElementById('diagram-code');
|
|
719
|
+
codeEl.value = d.content;
|
|
720
|
+
codeEl.readOnly = d.type === 'system';
|
|
721
|
+
document.getElementById('diagram-list-view').style.display = 'none';
|
|
722
|
+
document.getElementById('diagram-editor-view').classList.add('active');
|
|
723
|
+
renderPreview(d.content);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function closeDiagramEditor() {
|
|
727
|
+
clearTimeout(previewDebounce); previewDebounce = null;
|
|
728
|
+
if (panzoomInstance) { panzoomInstance.dispose(); panzoomInstance = null; }
|
|
729
|
+
document.getElementById('diagram-editor-view').classList.remove('active');
|
|
730
|
+
document.getElementById('diagram-list-view').style.display = '';
|
|
731
|
+
currentDiagramName = null;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function renderPreview(source) {
|
|
735
|
+
const container = document.getElementById('diagram-preview-render');
|
|
736
|
+
container.textContent = '';
|
|
737
|
+
if (panzoomInstance) { panzoomInstance.dispose(); panzoomInstance = null; }
|
|
738
|
+
if (!source || !source.trim()) { container.textContent = 'Empty diagram'; return; }
|
|
739
|
+
const pre = document.createElement('pre');
|
|
740
|
+
pre.className = 'mermaid';
|
|
741
|
+
pre.textContent = source;
|
|
742
|
+
container.appendChild(pre);
|
|
743
|
+
if (typeof mermaid !== 'undefined' && mermaid.run) {
|
|
744
|
+
mermaid.run({ nodes: [pre] }).then(function() {
|
|
745
|
+
const svg = container.querySelector('svg');
|
|
746
|
+
if (svg && typeof panzoom !== 'undefined') {
|
|
747
|
+
svg.style.width = '100%';
|
|
748
|
+
svg.style.height = '100%';
|
|
749
|
+
panzoomInstance = panzoom(svg, { maxZoom: 10, minZoom: 0.1, smoothScroll: false });
|
|
584
750
|
}
|
|
585
|
-
|
|
751
|
+
}).catch(function() {
|
|
752
|
+
container.textContent = 'Mermaid syntax error — check your code';
|
|
586
753
|
});
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function resetPanZoom() {
|
|
758
|
+
if (panzoomInstance) {
|
|
759
|
+
panzoomInstance.moveTo(0, 0);
|
|
760
|
+
panzoomInstance.zoomAbs(0, 0, 1);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Live preview on code change
|
|
765
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
766
|
+
const codeEl = document.getElementById('diagram-code');
|
|
767
|
+
if (codeEl) {
|
|
768
|
+
codeEl.addEventListener('input', function() {
|
|
769
|
+
document.getElementById('editor-unsaved').style.display = codeEl.value !== currentDiagramSaved ? '' : 'none';
|
|
770
|
+
clearTimeout(previewDebounce);
|
|
771
|
+
previewDebounce = setTimeout(function() { renderPreview(codeEl.value); }, 600);
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
async function saveDiagram() {
|
|
777
|
+
if (!currentDiagramName) return;
|
|
778
|
+
const d = allDiagrams.find(x => x.name === currentDiagramName);
|
|
779
|
+
if (d && d.type === 'system') { toast('System diagrams are read-only', false); return; }
|
|
780
|
+
const content = document.getElementById('diagram-code').value;
|
|
781
|
+
try {
|
|
782
|
+
await api('POST', '/api/diagrams/' + encodeURIComponent(currentDiagramName), { content: content });
|
|
783
|
+
currentDiagramSaved = content;
|
|
784
|
+
if (d) d.content = content;
|
|
785
|
+
document.getElementById('editor-unsaved').style.display = 'none';
|
|
786
|
+
// Re-render grid so mini previews reflect saved content
|
|
787
|
+
diagramsLoaded = false;
|
|
788
|
+
toast('Diagram saved', true);
|
|
789
|
+
} catch (e) {
|
|
790
|
+
toast('Save failed: ' + e.message, false);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function downloadCurrentDiagram() {
|
|
795
|
+
const src = document.getElementById('diagram-code').value;
|
|
796
|
+
if (!src || !currentDiagramName) return;
|
|
797
|
+
const safeName = currentDiagramName.replace(/[^a-zA-Z0-9_-]/g, '_') + '.mmd';
|
|
798
|
+
const blob = new Blob([src], { type: 'text/plain' });
|
|
799
|
+
const a = document.createElement('a');
|
|
800
|
+
const url = URL.createObjectURL(blob);
|
|
801
|
+
a.href = url;
|
|
802
|
+
a.download = safeName;
|
|
803
|
+
a.style.display = 'none';
|
|
804
|
+
document.body.appendChild(a);
|
|
805
|
+
try { a.click(); } finally {
|
|
806
|
+
document.body.removeChild(a);
|
|
807
|
+
setTimeout(function() { URL.revokeObjectURL(url); }, 100);
|
|
808
|
+
}
|
|
587
809
|
}
|
|
588
810
|
|
|
589
811
|
function renderTestPlan(md) {
|
|
@@ -963,7 +1185,11 @@ function addEnvSuggestion(key, val, hint) {
|
|
|
963
1185
|
addEnvRow(key, val);
|
|
964
1186
|
}
|
|
965
1187
|
|
|
1188
|
+
// Pause auto-refresh while MCP & Keys tab is active (editing forms)
|
|
1189
|
+
let editingLock = false;
|
|
1190
|
+
|
|
966
1191
|
async function refresh() {
|
|
1192
|
+
if (editingLock) return;
|
|
967
1193
|
try {
|
|
968
1194
|
const data = await api('GET', '/api/status');
|
|
969
1195
|
loadStatus(data);
|
|
@@ -1030,6 +1256,41 @@ const server = http.createServer(async (req, res) => {
|
|
|
1030
1256
|
return json(res, 200, getDiagramsData());
|
|
1031
1257
|
}
|
|
1032
1258
|
|
|
1259
|
+
// Save diagram content
|
|
1260
|
+
const diagramMatch = parsedUrl.pathname.match(/^\/api\/diagrams\/([a-zA-Z0-9_-]+)$/);
|
|
1261
|
+
if (req.method === 'POST' && diagramMatch) {
|
|
1262
|
+
const name = decodeURIComponent(diagramMatch[1]);
|
|
1263
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) return json(res, 400, { error: 'Invalid diagram name' });
|
|
1264
|
+
const { body, err } = await parseJSONBody(req, res);
|
|
1265
|
+
if (err) return;
|
|
1266
|
+
if (typeof body.content !== 'string') return json(res, 400, { error: 'content must be a string' });
|
|
1267
|
+
if (body.content.length > 500000) return json(res, 400, { error: 'content too large (max 500KB)' });
|
|
1268
|
+
const diagramsDir = path.join(basePath, '.claude', 'pm', 'diagrams');
|
|
1269
|
+
if (!fs.existsSync(diagramsDir)) fs.mkdirSync(diagramsDir, { recursive: true });
|
|
1270
|
+
fs.writeFileSync(path.join(diagramsDir, name + '.mmd'), body.content, 'utf8');
|
|
1271
|
+
// Update metadata timestamp if exists, or create
|
|
1272
|
+
const metaPath = path.join(diagramsDir, name + '.meta.json');
|
|
1273
|
+
let meta = {};
|
|
1274
|
+
try { meta = JSON.parse(fs.readFileSync(metaPath, 'utf8')); } catch {}
|
|
1275
|
+
meta.name = meta.name || name;
|
|
1276
|
+
meta.updated = new Date().toISOString().replace(/\\.\\d{3}Z$/, 'Z');
|
|
1277
|
+
if (!meta.created) meta.created = meta.updated;
|
|
1278
|
+
fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2), 'utf8');
|
|
1279
|
+
return json(res, 200, { ok: true });
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
const diagramDeleteMatch = parsedUrl.pathname.match(/^\/api\/diagrams\/([a-zA-Z0-9_-]+)$/);
|
|
1283
|
+
if (req.method === 'DELETE' && diagramDeleteMatch) {
|
|
1284
|
+
const name = decodeURIComponent(diagramDeleteMatch[1]);
|
|
1285
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) return json(res, 400, { error: 'Invalid diagram name' });
|
|
1286
|
+
const diagramsDir = path.join(basePath, '.claude', 'pm', 'diagrams');
|
|
1287
|
+
const mmdPath = path.join(diagramsDir, name + '.mmd');
|
|
1288
|
+
const metaPath = path.join(diagramsDir, name + '.meta.json');
|
|
1289
|
+
try { fs.unlinkSync(mmdPath); } catch {}
|
|
1290
|
+
try { fs.unlinkSync(metaPath); } catch {}
|
|
1291
|
+
return json(res, 200, { ok: true });
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1033
1294
|
if (req.method === 'GET' && parsedUrl.pathname === '/api/tests') {
|
|
1034
1295
|
return json(res, 200, getTestsData());
|
|
1035
1296
|
}
|
package/package.json
CHANGED