@yemi33/minions 0.1.10 → 0.1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.12 (2026-03-26)
4
+
5
+ ### Engine
6
+ - engine.js
7
+ - engine/ado.js
8
+ - engine/cli.js
9
+ - engine/github.js
10
+ - engine/lifecycle.js
11
+
12
+ ### Dashboard
13
+ - dashboard.html
14
+ - dashboard.js
15
+
16
+ ### Other
17
+ - TODO.md
18
+ - routing.md
19
+ - test/unit.test.js
20
+
21
+ ## 0.1.11 (2026-03-26)
22
+
23
+ ### Engine
24
+ - engine.js
25
+ - engine/lifecycle.js
26
+ - engine/scheduler.js
27
+
28
+ ### Dashboard
29
+ - dashboard.html
30
+ - dashboard.js
31
+
32
+ ### Other
33
+ - test/unit.test.js
34
+
3
35
  ## 0.1.9 (2026-03-26)
4
36
 
5
37
  ### Engine
package/dashboard.html CHANGED
@@ -45,9 +45,19 @@
45
45
  @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.3} }
46
46
  .timestamp { color: var(--muted); font-size: var(--text-md); font-variant-numeric: tabular-nums; }
47
47
 
48
- .layout { display: grid; grid-template-columns: 1fr 1fr; gap: 0; max-width: 100vw; overflow-x: hidden; }
48
+ .layout { display: none; } /* Replaced by page-layout */
49
49
  section { padding: var(--space-8) var(--space-9); border-bottom: 1px solid var(--border); overflow: hidden; min-width: 0; }
50
- section:nth-child(odd) { border-right: 1px solid var(--border); }
50
+
51
+ /* Sidebar navigation */
52
+ .page-layout { display: flex; height: calc(100vh - 44px); overflow: hidden; }
53
+ .sidebar { width: 150px; min-width: 150px; background: var(--surface); border-right: 1px solid var(--border); padding: var(--space-4) 0; overflow-y: auto; position: sticky; top: 0; }
54
+ .sidebar-link { display: flex; align-items: center; justify-content: space-between; padding: 8px 14px; color: var(--muted); text-decoration: none; font-size: var(--text-sm); border-left: 3px solid transparent; transition: all var(--transition-fast); cursor: pointer; }
55
+ .sidebar-link:hover { color: var(--text); background: var(--surface2); }
56
+ .sidebar-link.active { color: var(--blue); border-left-color: var(--blue); background: var(--surface2); font-weight: 600; }
57
+ .sidebar-count { font-size: 9px; color: var(--muted); background: var(--surface2); padding: 1px 5px; border-radius: 8px; min-width: 16px; text-align: center; }
58
+ .page-content { flex: 1; overflow-y: auto; min-width: 0; }
59
+ .page { display: none; }
60
+ .page.active { display: block; }
51
61
  section h2 { font-size: var(--text-base); font-weight: 600; text-transform: uppercase; letter-spacing: 1px; color: var(--muted); margin-bottom: 14px; display: flex; align-items: center; gap: var(--space-4); }
52
62
  section h2 .count { background: var(--surface2); border: 1px solid var(--border); border-radius: var(--radius-xl); padding: var(--space-1) 7px; font-size: var(--text-base); color: var(--text); }
53
63
 
@@ -189,7 +199,7 @@
189
199
  .inbox-name { font-weight: 500; font-size: var(--text-md); color: var(--purple); margin-bottom: var(--space-2); display: flex; justify-content: space-between; }
190
200
  .inbox-preview { font-size: var(--text-base); color: var(--muted); line-height: 1.5; max-height: 60px; overflow: hidden; }
191
201
 
192
- .prd-panel, .pr-panel { grid-column: 1 / -1; border-bottom: 1px solid var(--border); overflow: visible; min-width: 0; }
202
+ .prd-panel, .pr-panel { border-bottom: 1px solid var(--border); overflow: visible; min-width: 0; }
193
203
  .prd-inner { display: flex; gap: 16px; align-items: flex-start; }
194
204
  .prd-stats { display: flex; gap: 16px; }
195
205
  .prd-stat { text-align: center; background: var(--surface2); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-6) var(--space-8); }
@@ -331,7 +341,7 @@
331
341
  .empty { color: var(--muted); font-style: italic; font-size: var(--text-md); padding: var(--space-4) 0; }
332
342
 
333
343
  /* Command Center — Unified Input */
334
- .cmd-center { grid-column: 1 / -1; overflow: visible !important; }
344
+ .cmd-center { overflow: visible !important; }
335
345
  .cmd-input-wrap {
336
346
  position: relative; display: flex; align-items: flex-start; gap: 0;
337
347
  background: var(--bg); border: 2px solid var(--border); border-radius: var(--radius-xl);
@@ -642,104 +652,134 @@
642
652
  <code style="background:var(--bg);padding:6px 16px;border-radius:4px;font-size:14px;color:var(--blue);border:1px solid var(--border)">minions init</code>
643
653
  </div>
644
654
 
645
- <div class="layout">
646
- <section class="cmd-center">
647
- <h2>Command Center</h2>
648
- <div class="cmd-input-wrap" id="cmd-input-wrap">
649
- <div class="cmd-highlight-layer" id="cmd-highlight" aria-hidden="true"></div>
650
- <textarea id="cmd-input" rows="1" placeholder='What do you need? e.g. "Fix the auth bug @dallas", "explain the dispatch flow", or "/note always use feature flags"'
651
- oninput="cmdInputChanged()" onkeydown="cmdKeyDown(event)" onscroll="syncHighlightScroll()"></textarea>
652
- <button class="cmd-send-btn" id="cmd-send-btn" onclick="cmdSubmit()">Send <kbd>Ctrl+Enter</kbd></button>
655
+ <!-- Layout replaced by page-layout sidebar navigation -->
656
+
657
+ <div class="page-layout">
658
+ <nav class="sidebar" id="sidebar">
659
+ <a class="sidebar-link" data-page="home" href="/">Home</a>
660
+ <a class="sidebar-link" data-page="work" href="/work">Work Items <span class="sidebar-count" id="sidebar-wi"></span></a>
661
+ <a class="sidebar-link" data-page="prd" href="/prd">PRD</a>
662
+ <a class="sidebar-link" data-page="prs" href="/prs">Pull Requests <span class="sidebar-count" id="sidebar-pr"></span></a>
663
+ <a class="sidebar-link" data-page="plans" href="/plans">Plans</a>
664
+ <a class="sidebar-link" data-page="inbox" href="/inbox">Notes & KB</a>
665
+ <a class="sidebar-link" data-page="schedule" href="/schedule">Schedules</a>
666
+ <a class="sidebar-link" data-page="engine" href="/engine">Engine</a>
667
+ </nav>
668
+ <div class="page-content" id="page-content">
669
+
670
+ <div class="page active" id="page-home">
671
+ <section class="cmd-center">
672
+ <h2>Command Center</h2>
673
+ <div class="cmd-input-wrap" id="cmd-input-wrap">
674
+ <div class="cmd-highlight-layer" id="cmd-highlight" aria-hidden="true"></div>
675
+ <textarea id="cmd-input" rows="1" placeholder='What do you need? e.g. "Fix the auth bug @dallas", "explain the dispatch flow", or "/note always use feature flags"'
676
+ oninput="cmdInputChanged()" onkeydown="cmdKeyDown(event)" onscroll="syncHighlightScroll()"></textarea>
677
+ <button class="cmd-send-btn" id="cmd-send-btn" onclick="cmdSubmit()">Send <kbd>Ctrl+Enter</kbd></button>
678
+ </div>
679
+ <div class="cmd-mention-popup" id="cmd-mention-popup"></div>
680
+ <div class="cmd-meta" id="cmd-meta" style="display:none"></div>
681
+ <div class="cmd-hints">
682
+ <span style="color:var(--blue);font-weight:600">Command Center</span>
683
+ <span>Ask anything, dispatch work, manage plans — powered by Sonnet</span>
684
+ <button class="cmd-history-btn" onclick="cmdShowHistory()">Past Commands</button>
685
+ </div>
686
+ <div class="cmd-toast" id="cmd-toast"></div>
687
+ </section>
688
+ <section>
689
+ <h2>Minions Members <span style="font-size:10px;color:var(--border);font-weight:400;text-transform:none;letter-spacing:0">click for details</span></h2>
690
+ <div class="agents" id="agents-grid">Loading...</div>
691
+ </section>
692
+ <section>
693
+ <h2>Dispatch Queue</h2>
694
+ <div class="dispatch-stats" id="dispatch-stats"></div>
695
+ <div id="dispatch-active"></div>
696
+ <div id="dispatch-pending"></div>
697
+ </section>
698
+ <section class="pr-panel" id="completed-section">
699
+ <h2>Recent Completions <span class="count" id="completed-count">0</span></h2>
700
+ <div id="completed-content"><p class="empty">No completed dispatches yet.</p></div>
701
+ </section>
702
+ </div>
703
+
704
+ <div class="page" id="page-work">
705
+ <section id="work-items-section" style="overflow:visible">
706
+ <h2>Work Items <span class="count" id="wi-count">0</span> <button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;margin-left:8px" onclick="toggleWorkItemArchive()">See Archive</button></h2>
707
+ <div id="work-items-content"><p class="empty">No work items. Add tasks via Command Center above.</p></div>
708
+ <div id="work-items-archive" style="display:none;margin-top:12px"></div>
709
+ </section>
710
+ </div>
711
+
712
+ <div class="page" id="page-prd">
713
+ <section class="prd-panel" id="prd-section">
714
+ <h2>PRD <span class="count" id="prd-progress-count">0%</span> <span id="prd-badge"></span> <span id="archive-btns"></span></h2>
715
+ <div id="prd-content"><p class="prd-pending">No PRD found.</p></div>
716
+ <div id="prd-progress-content" style="margin-top:12px"></div>
717
+ </section>
718
+ </div>
719
+
720
+ <div class="page" id="page-prs">
721
+ <section class="pr-panel" id="pr-section">
722
+ <h2>Pull Requests <span class="count" id="pr-count">0</span></h2>
723
+ <div id="pr-content"><p class="pr-empty">No pull requests yet.</p></div>
724
+ </section>
725
+ </div>
726
+
727
+ <div class="page" id="page-plans">
728
+ <section>
729
+ <h2>Plans <span class="count" id="plans-count">0</span></h2>
730
+ <div id="plans-list"><p class="empty">No plans yet. Use /plan in the command center to create one.</p></div>
731
+ </section>
732
+ </div>
733
+
734
+ <div class="page" id="page-inbox">
735
+ <section>
736
+ <h2>Notes Inbox <span class="count" id="inbox-count">0</span> <span style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">auto-consolidates at 3 notes</span></h2>
737
+ <div class="inbox-list" id="inbox-list">Loading...</div>
738
+ </section>
739
+ <section>
740
+ <h2 data-file="notes.md" style="position:relative">Team Notes <span style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0" id="notes-updated"></span></h2>
741
+ <div id="notes-list">Loading...</div>
742
+ </section>
743
+ <section>
744
+ <h2>Knowledge Base <span class="count" id="kb-count">0</span> <button id="kb-sweep-btn" onclick="kbSweep()" style="font-size:9px;padding:2px 8px;background:var(--surface2);border:1px solid var(--border);color:var(--muted);border-radius:4px;cursor:pointer;margin-left:8px;vertical-align:middle">sweep</button><span id="kb-swept-time" style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0;margin-left:8px"></span></h2>
745
+ <div class="kb-tabs" id="kb-tabs"></div>
746
+ <div class="kb-list" id="kb-list"><p class="empty">No knowledge entries yet. Notes are classified here after consolidation.</p></div>
747
+ </section>
748
+ <section>
749
+ <h2>Minions Skills <span class="count" id="skills-count">0</span></h2>
750
+ <div id="skills-list"><p class="empty">No skills yet. Agents create these when they discover repeatable workflows.</p></div>
751
+ </section>
653
752
  </div>
654
- <div class="cmd-mention-popup" id="cmd-mention-popup"></div>
655
- <div class="cmd-meta" id="cmd-meta" style="display:none"></div>
656
- <div class="cmd-hints">
657
- <span style="color:var(--blue);font-weight:600">Command Center</span>
658
- <span>Ask anything, dispatch work, manage plans — powered by Sonnet</span>
659
- <button class="cmd-history-btn" onclick="cmdShowHistory()">Past Commands</button>
753
+
754
+ <div class="page" id="page-schedule">
755
+ <section id="scheduled-section">
756
+ <h2>Scheduled Tasks <span class="count" id="scheduled-count">0</span>
757
+ <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:8px" onclick="openCreateScheduleModal()">+ New</button>
758
+ </h2>
759
+ <div id="scheduled-content"><p class="empty">No scheduled tasks. Add one to automate recurring work.</p></div>
760
+ </section>
761
+ <section>
762
+ <h2>MCP Servers <span class="count" id="mcp-count">0</span></h2>
763
+ <div id="mcp-list"><p class="empty">No MCP servers synced.</p></div>
764
+ </section>
660
765
  </div>
661
- <div class="cmd-toast" id="cmd-toast"></div>
662
- </section>
663
-
664
- <section>
665
- <h2>Minions Members <span style="font-size:10px;color:var(--border);font-weight:400;text-transform:none;letter-spacing:0">click for details</span></h2>
666
- <div class="agents" id="agents-grid">Loading...</div>
667
- </section>
668
-
669
- <section id="work-items-section" style="overflow:visible">
670
- <h2>Work Items <span class="count" id="wi-count">0</span> <button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;margin-left:8px" onclick="toggleWorkItemArchive()">See Archive</button></h2>
671
- <div id="work-items-content"><p class="empty">No work items. Add tasks via Command Center above.</p></div>
672
- <div id="work-items-archive" style="display:none;margin-top:12px"></div>
673
- </section>
674
-
675
- <section class="prd-panel" id="prd-section">
676
- <h2>PRD <span class="count" id="prd-progress-count">0%</span> <span id="prd-badge"></span> <span id="archive-btns"></span></h2>
677
- <div id="prd-content"><p class="prd-pending">No PRD found.</p></div>
678
- <div id="prd-progress-content" style="margin-top:12px"></div>
679
- </section>
680
-
681
- <section class="pr-panel" id="pr-section">
682
- <h2>Pull Requests <span class="count" id="pr-count">0</span></h2>
683
- <div id="pr-content"><p class="pr-empty">No pull requests yet.</p></div>
684
- </section>
685
-
686
- <section>
687
- <h2>Plans <span class="count" id="plans-count">0</span></h2>
688
- <div id="plans-list"><p class="empty">No plans yet. Use /plan in the command center to create one.</p></div>
689
- </section>
690
-
691
- <section>
692
- <h2>Notes Inbox <span class="count" id="inbox-count">0</span> <span style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">auto-consolidates at 3 notes</span></h2>
693
- <div class="inbox-list" id="inbox-list">Loading...</div>
694
- </section>
695
-
696
- <section>
697
- <h2 data-file="notes.md" style="position:relative">Team Notes <span style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0" id="notes-updated"></span></h2>
698
- <div id="notes-list">Loading...</div>
699
- </section>
700
-
701
- <section>
702
- <h2>Knowledge Base <span class="count" id="kb-count">0</span> <button id="kb-sweep-btn" onclick="kbSweep()" style="font-size:9px;padding:2px 8px;background:var(--surface2);border:1px solid var(--border);color:var(--muted);border-radius:4px;cursor:pointer;margin-left:8px;vertical-align:middle">sweep</button><span id="kb-swept-time" style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0;margin-left:8px"></span></h2>
703
- <div class="kb-tabs" id="kb-tabs"></div>
704
- <div class="kb-list" id="kb-list"><p class="empty">No knowledge entries yet. Notes are classified here after consolidation.</p></div>
705
- </section>
706
-
707
- <section>
708
- <h2>Minions Skills <span class="count" id="skills-count">0</span></h2>
709
- <div id="skills-list"><p class="empty">No skills yet. Agents create these when they discover repeatable workflows.</p></div>
710
- </section>
711
-
712
- <section>
713
- <h2>MCP Servers <span class="count" id="mcp-count">0</span></h2>
714
- <div id="mcp-list"><p class="empty">No MCP servers synced.</p></div>
715
- </section>
716
-
717
- <section>
718
- <h2>Dispatch Queue</h2>
719
- <div class="dispatch-stats" id="dispatch-stats"></div>
720
- <div id="dispatch-active"></div>
721
- <div id="dispatch-pending"></div>
722
- </section>
723
-
724
- <section>
725
- <h2>Engine Log</h2>
726
- <div class="log-list" id="engine-log">No log entries yet.</div>
727
- </section>
728
-
729
- <section>
730
- <h2>Agent Metrics</h2>
731
- <div id="metrics-content"><p class="empty">No metrics yet. Metrics appear after agents complete tasks.</p></div>
732
- </section>
733
-
734
- <section>
735
- <h2>Token Usage</h2>
736
- <div id="token-usage-content"><p class="empty">No usage data yet.</p></div>
737
- </section>
738
-
739
- <section class="pr-panel" id="completed-section">
740
- <h2>Recent Completions <span class="count" id="completed-count">0</span></h2>
741
- <div id="completed-content"><p class="empty">No completed dispatches yet.</p></div>
742
- </section>
766
+
767
+ <div class="page" id="page-engine">
768
+ <section>
769
+ <h2>Engine Log</h2>
770
+ <div class="log-list" id="engine-log">No log entries yet.</div>
771
+ </section>
772
+ <section>
773
+ <h2>Agent Metrics</h2>
774
+ <div id="metrics-content"><p class="empty">No metrics yet. Metrics appear after agents complete tasks.</p></div>
775
+ </section>
776
+ <section>
777
+ <h2>Token Usage</h2>
778
+ <div id="token-usage-content"><p class="empty">No usage data yet.</p></div>
779
+ </section>
780
+ </div>
781
+
782
+ </div>
743
783
  </div>
744
784
 
745
785
  <!-- Agent Detail Panel -->
@@ -796,6 +836,34 @@ let inboxData = [];
796
836
  let agentData = [];
797
837
  let currentAgentId = null;
798
838
  let currentTab = 'thought-process';
839
+
840
+ // Sidebar page navigation — URL-routed
841
+ function getPageFromUrl() {
842
+ const path = window.location.pathname.replace(/^\//, '') || 'home';
843
+ if (document.querySelector('.sidebar-link[data-page="' + path + '"]')) return path;
844
+ return 'home';
845
+ }
846
+
847
+ let currentPage = getPageFromUrl();
848
+
849
+ function switchPage(page, pushState) {
850
+ currentPage = page;
851
+ document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
852
+ const target = document.getElementById('page-' + page);
853
+ if (target) target.classList.add('active');
854
+ document.querySelectorAll('.sidebar-link').forEach(l => l.classList.remove('active'));
855
+ const link = document.querySelector('.sidebar-link[data-page="' + page + '"]');
856
+ if (link) link.classList.add('active');
857
+ if (pushState !== false) {
858
+ const url = page === 'home' ? '/' : '/' + page;
859
+ history.pushState({ page }, '', url);
860
+ }
861
+ }
862
+
863
+ // Browser back/forward navigation
864
+ window.addEventListener('popstate', (e) => {
865
+ switchPage(e.state?.page || getPageFromUrl(), false);
866
+ });
799
867
  window._prdRequeueUi = window._prdRequeueUi || {};
800
868
 
801
869
  function getPrdRequeueState(workItemId) {
@@ -2380,6 +2448,12 @@ async function refresh() {
2380
2448
  renderWorkItems(data.workItems || []);
2381
2449
  renderSkills(data.skills || []);
2382
2450
  renderMcpServers(data.mcpServers || []);
2451
+ renderSchedules(data.schedules || []);
2452
+ // Update sidebar counts
2453
+ const swi = document.getElementById('sidebar-wi');
2454
+ if (swi) swi.textContent = (data.workItems || []).length || '';
2455
+ const spr = document.getElementById('sidebar-pr');
2456
+ if (spr) spr.textContent = (data.pullRequests || []).length || '';
2383
2457
  // Refresh KB and plans less frequently (every 3rd cycle = ~12s)
2384
2458
  if (!window._kbRefreshCount) window._kbRefreshCount = 0;
2385
2459
  if (window._kbRefreshCount++ % 3 === 0) { refreshKnowledgeBase(); refreshPlans(); }
@@ -2389,6 +2463,12 @@ async function refresh() {
2389
2463
  refresh();
2390
2464
  setInterval(refresh, 4000);
2391
2465
 
2466
+ // Wire sidebar navigation
2467
+ document.querySelectorAll('.sidebar-link').forEach(link => {
2468
+ link.addEventListener('click', e => { e.preventDefault(); switchPage(link.dataset.page); });
2469
+ });
2470
+ switchPage(currentPage);
2471
+
2392
2472
  // -- Projects --
2393
2473
  function renderProjects(projects) {
2394
2474
  const header = document.getElementById('header-projects');
@@ -2448,6 +2528,164 @@ function renderMcpServers(servers) {
2448
2528
  '<p style="font-size:10px;color:var(--muted);margin:0">Synced from <code style="color:var(--blue)">~/.claude.json</code> — add MCP servers there to make them available to all agents.</p>';
2449
2529
  }
2450
2530
 
2531
+ // -- Scheduled Tasks --
2532
+ function renderSchedules(schedules) {
2533
+ const el = document.getElementById('scheduled-content');
2534
+ const countEl = document.getElementById('scheduled-count');
2535
+ countEl.textContent = schedules.length;
2536
+ if (!schedules.length) {
2537
+ el.innerHTML = '<p class="empty">No scheduled tasks. Add one to automate recurring work.</p>';
2538
+ return;
2539
+ }
2540
+ let html = '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Cron</th><th>Type</th><th>Project</th><th>Agent</th><th>Enabled</th><th>Last Run</th><th></th></tr></thead><tbody>';
2541
+ for (const s of schedules) {
2542
+ const enabledBadge = s.enabled
2543
+ ? '<span class="pr-badge approved">enabled</span>'
2544
+ : '<span class="pr-badge rejected">disabled</span>';
2545
+ const lastRun = s._lastRun ? timeAgo(s._lastRun) : 'never';
2546
+ const typeBadge = '<span class="dispatch-type ' + escHtml(s.type || 'implement') + '">' + escHtml(s.type || 'implement') + '</span>';
2547
+ html += '<tr>' +
2548
+ '<td><span class="pr-id">' + escHtml(s.id || '') + '</span></td>' +
2549
+ '<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(s.title || '') + '">' + escHtml(s.title || '') + '</td>' +
2550
+ '<td><code style="font-size:10px;color:var(--blue)">' + escHtml(s.cron || '') + '</code></td>' +
2551
+ '<td>' + typeBadge + '</td>' +
2552
+ '<td><span style="font-size:10px;color:var(--muted)">' + escHtml(s.project || '') + '</span></td>' +
2553
+ '<td><span class="pr-agent">' + escHtml(s.agent || 'auto') + '</span></td>' +
2554
+ '<td>' + enabledBadge + '</td>' +
2555
+ '<td><span class="pr-date">' + escHtml(lastRun) + '</span></td>' +
2556
+ '<td style="white-space:nowrap">' +
2557
+ '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:' + (s.enabled ? 'var(--yellow)' : 'var(--green)') + ';border-color:' + (s.enabled ? 'var(--yellow)' : 'var(--green)') + ';margin-right:4px" onclick="event.stopPropagation();toggleScheduleEnabled(\'' + escHtml(s.id) + '\',' + !s.enabled + ')" title="' + (s.enabled ? 'Disable' : 'Enable') + '">' + (s.enabled ? '&#x23F8;' : '&#x25B6;') + '</button>' +
2558
+ '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--blue);border-color:var(--blue);margin-right:4px" onclick="event.stopPropagation();openEditScheduleModal(\'' + escHtml(s.id) + '\')" title="Edit">&#x270E;</button>' +
2559
+ '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--red);border-color:var(--red)" onclick="event.stopPropagation();deleteSchedule(\'' + escHtml(s.id) + '\')" title="Delete">&#x2715;</button>' +
2560
+ '</td>' +
2561
+ '</tr>';
2562
+ }
2563
+ html += '</tbody></table></div>';
2564
+ el.innerHTML = html;
2565
+ window._lastSchedules = schedules;
2566
+ }
2567
+
2568
+ function _scheduleFormHtml(sched, isEdit) {
2569
+ const types = ['implement', 'test', 'explore', 'ask', 'review', 'fix'];
2570
+ const priorities = ['high', 'medium', 'low'];
2571
+ const typeOpts = types.map(t => '<option value="' + t + '"' + ((sched.type || 'implement') === t ? ' selected' : '') + '>' + t + '</option>').join('');
2572
+ const priOpts = priorities.map(p => '<option value="' + p + '"' + ((sched.priority || 'medium') === p ? ' selected' : '') + '>' + p + '</option>').join('');
2573
+ const projOpts = '<option value="">Any</option>' + cmdProjects.map(p => '<option value="' + escHtml(p.name) + '"' + (sched.project === p.name ? ' selected' : '') + '>' + escHtml(p.name) + '</option>').join('');
2574
+ const agentOpts = '<option value="">Auto</option>' + cmdAgents.map(a => '<option value="' + escHtml(a.id) + '"' + (sched.agent === a.id ? ' selected' : '') + '>' + escHtml(a.name) + '</option>').join('');
2575
+
2576
+ const inputStyle = 'display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md);font-family:inherit';
2577
+
2578
+ return '<div style="display:flex;flex-direction:column;gap:12px;font-family:inherit">' +
2579
+ (isEdit ? '' :
2580
+ '<label style="color:var(--text);font-size:var(--text-md)">ID (unique slug)' +
2581
+ '<input id="sched-edit-id" value="' + escHtml(sched.id || '') + '" placeholder="e.g. nightly-tests" style="' + inputStyle + '">' +
2582
+ '</label>') +
2583
+ '<label style="color:var(--text);font-size:var(--text-md)">Title' +
2584
+ '<input id="sched-edit-title" value="' + escHtml(sched.title || '') + '" style="' + inputStyle + '">' +
2585
+ '</label>' +
2586
+ '<label style="color:var(--text);font-size:var(--text-md)">Cron <span style="font-size:10px;color:var(--muted)">(minute hour dayOfWeek)</span>' +
2587
+ '<input id="sched-edit-cron" value="' + escHtml(sched.cron || '') + '" placeholder="0 2 *" style="' + inputStyle + '">' +
2588
+ '</label>' +
2589
+ '<div style="display:flex;gap:12px">' +
2590
+ '<label style="color:var(--text);font-size:var(--text-md);flex:1">Type' +
2591
+ '<select id="sched-edit-type" style="' + inputStyle + '">' + typeOpts + '</select>' +
2592
+ '</label>' +
2593
+ '<label style="color:var(--text);font-size:var(--text-md);flex:1">Priority' +
2594
+ '<select id="sched-edit-priority" style="' + inputStyle + '">' + priOpts + '</select>' +
2595
+ '</label>' +
2596
+ '</div>' +
2597
+ '<div style="display:flex;gap:12px">' +
2598
+ '<label style="color:var(--text);font-size:var(--text-md);flex:1">Project' +
2599
+ '<select id="sched-edit-project" style="' + inputStyle + '">' + projOpts + '</select>' +
2600
+ '</label>' +
2601
+ '<label style="color:var(--text);font-size:var(--text-md);flex:1">Agent' +
2602
+ '<select id="sched-edit-agent" style="' + inputStyle + '">' + agentOpts + '</select>' +
2603
+ '</label>' +
2604
+ '</div>' +
2605
+ '<label style="color:var(--text);font-size:var(--text-md)">Description' +
2606
+ '<textarea id="sched-edit-desc" rows="3" style="' + inputStyle + ';resize:vertical">' + escHtml(sched.description || '') + '</textarea>' +
2607
+ '</label>' +
2608
+ '<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px">' +
2609
+ '<button onclick="closeModal()" class="pr-pager-btn" style="padding:6px 16px;font-size:var(--text-md)">Cancel</button>' +
2610
+ '<button onclick="submitSchedule(' + isEdit + ')" style="padding:6px 16px;font-size:var(--text-md);background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">' + (isEdit ? 'Save' : 'Create') + '</button>' +
2611
+ '</div>' +
2612
+ '</div>';
2613
+ }
2614
+
2615
+ function openCreateScheduleModal() {
2616
+ document.getElementById('modal-title').textContent = 'New Scheduled Task';
2617
+ document.getElementById('modal-body').style.whiteSpace = 'normal';
2618
+ document.getElementById('modal-body').style.fontFamily = '';
2619
+ document.getElementById('modal-body').innerHTML = _scheduleFormHtml({}, false);
2620
+ document.getElementById('modal').classList.add('open');
2621
+ }
2622
+
2623
+ function openEditScheduleModal(id) {
2624
+ const sched = (window._lastSchedules || []).find(s => s.id === id);
2625
+ if (!sched) return;
2626
+ document.getElementById('modal-title').textContent = 'Edit Schedule: ' + id;
2627
+ document.getElementById('modal-body').style.whiteSpace = 'normal';
2628
+ document.getElementById('modal-body').style.fontFamily = '';
2629
+ document.getElementById('modal-body').innerHTML = _scheduleFormHtml(sched, true);
2630
+ window._editScheduleId = id;
2631
+ document.getElementById('modal').classList.add('open');
2632
+ }
2633
+
2634
+ async function submitSchedule(isEdit) {
2635
+ const title = document.getElementById('sched-edit-title').value.trim();
2636
+ const cron = document.getElementById('sched-edit-cron').value.trim();
2637
+ const type = document.getElementById('sched-edit-type').value;
2638
+ const priority = document.getElementById('sched-edit-priority').value;
2639
+ const project = document.getElementById('sched-edit-project').value;
2640
+ const agent = document.getElementById('sched-edit-agent').value;
2641
+ const description = document.getElementById('sched-edit-desc').value;
2642
+ const id = isEdit ? window._editScheduleId : (document.getElementById('sched-edit-id') ? document.getElementById('sched-edit-id').value.trim() : '');
2643
+
2644
+ if (!id) { alert('ID is required'); return; }
2645
+ if (!title) { alert('Title is required'); return; }
2646
+ if (!cron) { alert('Cron expression is required'); return; }
2647
+
2648
+ const payload = { id, title, cron, type, priority, project: project || undefined, agent: agent || undefined, description: description || undefined, enabled: true };
2649
+ const url = isEdit ? '/api/schedules/update' : '/api/schedules';
2650
+ try {
2651
+ const res = await fetch(url, {
2652
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
2653
+ body: JSON.stringify(payload)
2654
+ });
2655
+ if (res.ok) { closeModal(); refresh(); showToast('cmd-toast', isEdit ? 'Schedule updated' : 'Schedule created', true); } else {
2656
+ const d = await res.json().catch(() => ({}));
2657
+ alert((isEdit ? 'Update' : 'Create') + ' failed: ' + (d.error || 'unknown'));
2658
+ }
2659
+ } catch (e) { alert('Error: ' + e.message); }
2660
+ }
2661
+
2662
+ async function toggleScheduleEnabled(id, enabled) {
2663
+ try {
2664
+ const res = await fetch('/api/schedules/update', {
2665
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
2666
+ body: JSON.stringify({ id, enabled })
2667
+ });
2668
+ if (res.ok) { refresh(); } else {
2669
+ const d = await res.json().catch(() => ({}));
2670
+ alert('Toggle failed: ' + (d.error || 'unknown'));
2671
+ }
2672
+ } catch (e) { alert('Toggle error: ' + e.message); }
2673
+ }
2674
+
2675
+ async function deleteSchedule(id) {
2676
+ if (!confirm('Delete scheduled task "' + id + '"?')) return;
2677
+ try {
2678
+ const res = await fetch('/api/schedules/delete', {
2679
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
2680
+ body: JSON.stringify({ id })
2681
+ });
2682
+ if (res.ok) { refresh(); showToast('cmd-toast', 'Schedule deleted', true); } else {
2683
+ const d = await res.json().catch(() => ({}));
2684
+ alert('Delete failed: ' + (d.error || 'unknown'));
2685
+ }
2686
+ } catch (e) { alert('Delete error: ' + e.message); }
2687
+ }
2688
+
2451
2689
  // -- Minions Skills --
2452
2690
  let _skillsTab = 'all';
2453
2691
  let _skillsPage = 0;