@yemi33/minions 0.1.11 → 0.1.13
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 +60 -0
- package/dashboard/js/command-center.js +377 -0
- package/dashboard/js/command-history.js +70 -0
- package/dashboard/js/command-input.js +268 -0
- package/dashboard/js/command-parser.js +129 -0
- package/dashboard/js/detail-panel.js +98 -0
- package/dashboard/js/live-stream.js +69 -0
- package/dashboard/js/modal-qa.js +268 -0
- package/dashboard/js/modal.js +131 -0
- package/dashboard/js/refresh.js +59 -0
- package/dashboard/js/render-agents.js +17 -0
- package/dashboard/js/render-dispatch.js +148 -0
- package/dashboard/js/render-inbox.js +126 -0
- package/dashboard/js/render-kb.js +107 -0
- package/dashboard/js/render-other.js +181 -0
- package/dashboard/js/render-plans.js +304 -0
- package/dashboard/js/render-prd.js +469 -0
- package/dashboard/js/render-prs.js +94 -0
- package/dashboard/js/render-schedules.js +158 -0
- package/dashboard/js/render-skills.js +89 -0
- package/dashboard/js/render-work-items.js +219 -0
- package/dashboard/js/settings.js +135 -0
- package/dashboard/js/state.js +84 -0
- package/dashboard/js/utils.js +39 -0
- package/dashboard/layout.html +123 -0
- package/dashboard/pages/engine.html +12 -0
- package/dashboard/pages/home.html +31 -0
- package/dashboard/pages/inbox.html +17 -0
- package/dashboard/pages/plans.html +4 -0
- package/dashboard/pages/prd.html +5 -0
- package/dashboard/pages/prs.html +4 -0
- package/dashboard/pages/schedule.html +10 -0
- package/dashboard/pages/work.html +5 -0
- package/dashboard/styles.css +598 -0
- package/dashboard-build.js +51 -0
- package/dashboard.html +179 -107
- package/dashboard.js +51 -1
- package/engine/ado.js +14 -0
- package/engine/cli.js +11 -0
- package/engine/github.js +14 -0
- package/engine/lifecycle.js +25 -29
- package/engine.js +106 -19
- package/package.json +1 -1
- package/routing.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,65 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.13 (2026-03-26)
|
|
4
|
+
|
|
5
|
+
### Dashboard
|
|
6
|
+
- dashboard-build.js
|
|
7
|
+
- dashboard.js
|
|
8
|
+
- dashboard/js/command-center.js
|
|
9
|
+
- dashboard/js/command-history.js
|
|
10
|
+
- dashboard/js/command-input.js
|
|
11
|
+
- dashboard/js/command-parser.js
|
|
12
|
+
- dashboard/js/detail-panel.js
|
|
13
|
+
- dashboard/js/live-stream.js
|
|
14
|
+
- dashboard/js/modal-qa.js
|
|
15
|
+
- dashboard/js/modal.js
|
|
16
|
+
- dashboard/js/refresh.js
|
|
17
|
+
- dashboard/js/render-agents.js
|
|
18
|
+
- dashboard/js/render-dispatch.js
|
|
19
|
+
- dashboard/js/render-inbox.js
|
|
20
|
+
- dashboard/js/render-kb.js
|
|
21
|
+
- dashboard/js/render-other.js
|
|
22
|
+
- dashboard/js/render-plans.js
|
|
23
|
+
- dashboard/js/render-prd.js
|
|
24
|
+
- dashboard/js/render-prs.js
|
|
25
|
+
- dashboard/js/render-schedules.js
|
|
26
|
+
- dashboard/js/render-skills.js
|
|
27
|
+
- dashboard/js/render-work-items.js
|
|
28
|
+
- dashboard/js/settings.js
|
|
29
|
+
- dashboard/js/state.js
|
|
30
|
+
- dashboard/js/utils.js
|
|
31
|
+
- dashboard/layout.html
|
|
32
|
+
- dashboard/pages/engine.html
|
|
33
|
+
- dashboard/pages/home.html
|
|
34
|
+
- dashboard/pages/inbox.html
|
|
35
|
+
- dashboard/pages/plans.html
|
|
36
|
+
- dashboard/pages/prd.html
|
|
37
|
+
- dashboard/pages/prs.html
|
|
38
|
+
- dashboard/pages/schedule.html
|
|
39
|
+
- dashboard/pages/work.html
|
|
40
|
+
- dashboard/styles.css
|
|
41
|
+
|
|
42
|
+
### Other
|
|
43
|
+
- test/unit.test.js
|
|
44
|
+
|
|
45
|
+
## 0.1.12 (2026-03-26)
|
|
46
|
+
|
|
47
|
+
### Engine
|
|
48
|
+
- engine.js
|
|
49
|
+
- engine/ado.js
|
|
50
|
+
- engine/cli.js
|
|
51
|
+
- engine/github.js
|
|
52
|
+
- engine/lifecycle.js
|
|
53
|
+
|
|
54
|
+
### Dashboard
|
|
55
|
+
- dashboard.html
|
|
56
|
+
- dashboard.js
|
|
57
|
+
|
|
58
|
+
### Other
|
|
59
|
+
- TODO.md
|
|
60
|
+
- routing.md
|
|
61
|
+
- test/unit.test.js
|
|
62
|
+
|
|
3
63
|
## 0.1.11 (2026-03-26)
|
|
4
64
|
|
|
5
65
|
### Engine
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// command-center.js — Command center panel functions extracted from dashboard.html
|
|
2
|
+
|
|
3
|
+
let _ccSessionId = localStorage.getItem('cc-session-id') || null;
|
|
4
|
+
let _ccMessages = JSON.parse(localStorage.getItem('cc-messages') || '[]');
|
|
5
|
+
let _ccOpen = false;
|
|
6
|
+
let _ccSending = false;
|
|
7
|
+
let _ccQueue = [];
|
|
8
|
+
|
|
9
|
+
function toggleCommandCenter() {
|
|
10
|
+
_ccOpen = !_ccOpen;
|
|
11
|
+
const drawer = document.getElementById('cc-drawer');
|
|
12
|
+
drawer.style.display = _ccOpen ? 'flex' : 'none';
|
|
13
|
+
if (_ccOpen) {
|
|
14
|
+
clearNotifBadge(document.getElementById('cc-toggle-btn'));
|
|
15
|
+
document.getElementById('cc-input').focus();
|
|
16
|
+
ccRestoreMessages();
|
|
17
|
+
} else if (_ccSending) {
|
|
18
|
+
// Closing drawer while CC is processing — show animated badge
|
|
19
|
+
showNotifBadge(document.getElementById('cc-toggle-btn'), 'processing');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function ccNewSession() {
|
|
24
|
+
fetch('/api/command-center/new-session', { method: 'POST' }).catch(() => {});
|
|
25
|
+
_ccSessionId = null;
|
|
26
|
+
_ccMessages = [];
|
|
27
|
+
localStorage.removeItem('cc-session-id');
|
|
28
|
+
localStorage.removeItem('cc-messages');
|
|
29
|
+
document.getElementById('cc-messages').innerHTML = '';
|
|
30
|
+
ccUpdateSessionIndicator();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function ccRestoreMessages() {
|
|
34
|
+
const el = document.getElementById('cc-messages');
|
|
35
|
+
if (el.children.length > 0 || _ccMessages.length === 0) return; // Already rendered or nothing to restore
|
|
36
|
+
for (const msg of _ccMessages) {
|
|
37
|
+
ccAddMessage(msg.role, msg.html, true);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function ccSaveState() {
|
|
42
|
+
try {
|
|
43
|
+
if (_ccSessionId) localStorage.setItem('cc-session-id', _ccSessionId);
|
|
44
|
+
// Keep last 30 messages for display
|
|
45
|
+
const toSave = _ccMessages.slice(-30);
|
|
46
|
+
localStorage.setItem('cc-messages', JSON.stringify(toSave));
|
|
47
|
+
} catch {} // localStorage might be full
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function ccUpdateSessionIndicator() {
|
|
51
|
+
const el = document.getElementById('cc-session-info');
|
|
52
|
+
if (!el) return;
|
|
53
|
+
if (_ccSessionId) {
|
|
54
|
+
const turns = _ccMessages.filter(m => m.role === 'user').length;
|
|
55
|
+
el.textContent = `Session: ${turns} turn${turns !== 1 ? 's' : ''}`;
|
|
56
|
+
el.style.color = 'var(--green)';
|
|
57
|
+
} else {
|
|
58
|
+
el.textContent = 'New session';
|
|
59
|
+
el.style.color = 'var(--muted)';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function ccAddMessage(role, html, skipSave) {
|
|
64
|
+
const el = document.getElementById('cc-messages');
|
|
65
|
+
const isUser = role === 'user';
|
|
66
|
+
const div = document.createElement('div');
|
|
67
|
+
const isAssistant = !isUser;
|
|
68
|
+
div.className = isAssistant ? 'cc-msg-assistant' : '';
|
|
69
|
+
div.style.cssText = 'padding:8px 12px;border-radius:8px;font-size:12px;line-height:1.6;max-width:95%;' +
|
|
70
|
+
(isUser ? 'background:var(--blue);color:#fff;align-self:flex-end' : 'background:var(--surface2);color:var(--text);align-self:flex-start;border:1px solid var(--border);position:relative');
|
|
71
|
+
div.innerHTML = (isAssistant && !html.includes('color:var(--red)') && !html.includes('cc-queued-pill') ? llmCopyBtn() : '') + html;
|
|
72
|
+
el.appendChild(div);
|
|
73
|
+
el.scrollTop = el.scrollHeight;
|
|
74
|
+
if (!skipSave) {
|
|
75
|
+
_ccMessages.push({ role, html });
|
|
76
|
+
ccSaveState();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function ccSend() {
|
|
81
|
+
const input = document.getElementById('cc-input');
|
|
82
|
+
const message = input.value.trim();
|
|
83
|
+
if (!message) return;
|
|
84
|
+
input.value = '';
|
|
85
|
+
|
|
86
|
+
// If already processing, queue the message and show it — it'll send when current finishes
|
|
87
|
+
if (_ccSending) {
|
|
88
|
+
_ccQueue.push(message);
|
|
89
|
+
ccAddMessage('user', escHtml(message));
|
|
90
|
+
const preview = message.split(/\s+/).slice(0, 6).join(' ') + (message.split(/\s+/).length > 6 ? '...' : '');
|
|
91
|
+
ccAddMessage('assistant', '<span class="cc-queued-pill" style="color:var(--muted);font-size:10px">Queued: "' + escHtml(preview) + '" — will send after current request</span>');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
await _ccDoSend(message);
|
|
95
|
+
|
|
96
|
+
// Drain queue
|
|
97
|
+
while (_ccQueue.length > 0) {
|
|
98
|
+
const next = _ccQueue.shift();
|
|
99
|
+
// Remove the "Queued" placeholder for this message
|
|
100
|
+
const msgs = document.getElementById('cc-messages');
|
|
101
|
+
const queuedPills = msgs.querySelectorAll('.cc-queued-pill');
|
|
102
|
+
for (const pill of queuedPills) {
|
|
103
|
+
if (pill.closest('div')) { pill.closest('div').remove(); break; }
|
|
104
|
+
}
|
|
105
|
+
await _ccDoSend(next, true); // skipUserMsg=true since already shown when queued
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function _ccDoSend(message, skipUserMsg) {
|
|
110
|
+
_ccSending = true;
|
|
111
|
+
|
|
112
|
+
if (!skipUserMsg) ccAddMessage('user', escHtml(message));
|
|
113
|
+
|
|
114
|
+
// Show thinking indicator with timer + queue count
|
|
115
|
+
const queueCount = _ccQueue.length;
|
|
116
|
+
const queueBadge = queueCount > 0 ? ' <span style="font-size:9px;background:var(--surface);padding:1px 5px;border-radius:8px;border:1px solid var(--border)">+' + queueCount + ' queued</span>' : '';
|
|
117
|
+
const thinking = document.createElement('div');
|
|
118
|
+
thinking.id = 'cc-thinking';
|
|
119
|
+
thinking.style.cssText = 'padding:8px 12px;border-radius:8px;font-size:11px;color:var(--muted);align-self:flex-start;display:flex;align-items:center;gap:8px';
|
|
120
|
+
thinking.innerHTML = '<span class="dot-pulse" style="display:inline-flex;gap:3px"><span style="width:4px;height:4px;background:var(--blue);border-radius:50%;animation:dotPulse 1.2s infinite"></span><span style="width:4px;height:4px;background:var(--blue);border-radius:50%;animation:dotPulse 1.2s infinite;animation-delay:0.2s"></span><span style="width:4px;height:4px;background:var(--blue);border-radius:50%;animation:dotPulse 1.2s infinite;animation-delay:0.4s"></span></span> <span id="cc-thinking-text">Thinking...</span> <span id="cc-thinking-time" style="font-size:10px;color:var(--border)"></span>' + queueBadge;
|
|
121
|
+
document.getElementById('cc-messages').appendChild(thinking);
|
|
122
|
+
const ccMsgs = document.getElementById('cc-messages');
|
|
123
|
+
ccMsgs.scrollTop = ccMsgs.scrollHeight;
|
|
124
|
+
|
|
125
|
+
const ccStartTime = Date.now();
|
|
126
|
+
const phases = [
|
|
127
|
+
[0, 'Thinking...'],
|
|
128
|
+
[3000, 'Reading minions context...'],
|
|
129
|
+
[8000, 'Analyzing...'],
|
|
130
|
+
[15000, 'Using tools to dig deeper...'],
|
|
131
|
+
[30000, 'Still working (multi-turn)...'],
|
|
132
|
+
[60000, 'Deep research in progress...'],
|
|
133
|
+
[180000, 'Still going (this is unusually long)...'],
|
|
134
|
+
[300000, 'Timing out soon...'],
|
|
135
|
+
];
|
|
136
|
+
const ccTimer = setInterval(() => {
|
|
137
|
+
const elapsed = Date.now() - ccStartTime;
|
|
138
|
+
const secs = Math.floor(elapsed / 1000);
|
|
139
|
+
const timeEl = document.getElementById('cc-thinking-time');
|
|
140
|
+
const textEl = document.getElementById('cc-thinking-text');
|
|
141
|
+
if (timeEl) timeEl.textContent = secs + 's';
|
|
142
|
+
if (textEl) {
|
|
143
|
+
for (let i = phases.length - 1; i >= 0; i--) {
|
|
144
|
+
if (elapsed >= phases[i][0]) { textEl.textContent = phases[i][1]; break; }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}, 500);
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const ccAbort = AbortSignal.timeout(960000);
|
|
151
|
+
const res = await fetch('/api/command-center', {
|
|
152
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
153
|
+
body: JSON.stringify({ message, sessionId: _ccSessionId }),
|
|
154
|
+
signal: ccAbort
|
|
155
|
+
});
|
|
156
|
+
const data = await res.json();
|
|
157
|
+
|
|
158
|
+
clearInterval(ccTimer);
|
|
159
|
+
thinking.remove();
|
|
160
|
+
|
|
161
|
+
if (data.error) {
|
|
162
|
+
ccAddMessage('assistant', '<span style="color:var(--red)">' + escHtml(data.error) + '</span>');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Track session — if server started a new session, clear stale frontend messages
|
|
167
|
+
if (data.sessionId) {
|
|
168
|
+
if (data.newSession && _ccSessionId && _ccSessionId !== data.sessionId) {
|
|
169
|
+
// Session was silently reset (expired/turn limit) — clear old messages except the current one
|
|
170
|
+
const el = document.getElementById('cc-messages');
|
|
171
|
+
// Keep only the user message we just added (last child)
|
|
172
|
+
const lastMsg = el.lastElementChild;
|
|
173
|
+
el.innerHTML = '';
|
|
174
|
+
if (lastMsg) el.appendChild(lastMsg);
|
|
175
|
+
_ccMessages = _ccMessages.slice(-1); // Keep only the message we just sent
|
|
176
|
+
}
|
|
177
|
+
_ccSessionId = data.sessionId;
|
|
178
|
+
ccSaveState();
|
|
179
|
+
ccUpdateSessionIndicator();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Render markdown-ish response
|
|
183
|
+
const ccElapsed = Math.round((Date.now() - ccStartTime) / 1000);
|
|
184
|
+
const rendered = (data.text || '').replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
|
185
|
+
.replace(/`([^`]+)`/g, '<code style="background:var(--surface);padding:1px 4px;border-radius:3px;font-size:11px">$1</code>')
|
|
186
|
+
.replace(/\n/g, '<br>');
|
|
187
|
+
ccAddMessage('assistant', rendered + '<div style="font-size:9px;color:var(--muted);margin-top:4px;text-align:right">' + ccElapsed + 's</div>');
|
|
188
|
+
|
|
189
|
+
// Execute actions
|
|
190
|
+
if (data.actions && data.actions.length > 0) {
|
|
191
|
+
for (const action of data.actions) {
|
|
192
|
+
await ccExecuteAction(action);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} catch (e) {
|
|
196
|
+
clearInterval(ccTimer);
|
|
197
|
+
thinking.remove();
|
|
198
|
+
ccAddMessage('assistant', '<span style="color:var(--red)">Error: ' + escHtml(e.message) + '</span>');
|
|
199
|
+
} finally {
|
|
200
|
+
_ccSending = false;
|
|
201
|
+
// Show notification badge on CC button if drawer is closed
|
|
202
|
+
if (!_ccOpen) showNotifBadge(document.getElementById('cc-toggle-btn'));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function ccExecuteAction(action) {
|
|
207
|
+
const msgs = document.getElementById('cc-messages');
|
|
208
|
+
const status = document.createElement('div');
|
|
209
|
+
status.style.cssText = 'padding:4px 10px;border-radius:4px;font-size:10px;align-self:flex-start;border:1px dashed var(--border);color:var(--muted)';
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
switch (action.type) {
|
|
213
|
+
case 'dispatch': {
|
|
214
|
+
const res = await fetch('/api/work-items', {
|
|
215
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
216
|
+
body: JSON.stringify({
|
|
217
|
+
title: action.title, type: action.workType || 'implement',
|
|
218
|
+
priority: action.priority || 'medium', description: action.description || '',
|
|
219
|
+
project: action.project || '', agents: action.agents || [],
|
|
220
|
+
})
|
|
221
|
+
});
|
|
222
|
+
const d = await res.json();
|
|
223
|
+
status.innerHTML = '✓ Dispatched: <strong>' + escHtml(d.id || action.title) + '</strong>';
|
|
224
|
+
status.style.color = 'var(--green)';
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case 'note': {
|
|
228
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
229
|
+
await fetch('/api/notes', {
|
|
230
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
231
|
+
body: JSON.stringify({ title: action.title, what: action.content || action.description, author: 'command-center' })
|
|
232
|
+
});
|
|
233
|
+
status.innerHTML = '✓ Note saved: <strong>' + escHtml(action.title) + '</strong>';
|
|
234
|
+
status.style.color = 'var(--green)';
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
case 'plan': {
|
|
238
|
+
await fetch('/api/plan', {
|
|
239
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
240
|
+
body: JSON.stringify({ title: action.title, description: action.description, project: action.project, branchStrategy: action.branchStrategy || 'parallel' })
|
|
241
|
+
});
|
|
242
|
+
status.innerHTML = '✓ Plan queued: <strong>' + escHtml(action.title) + '</strong>';
|
|
243
|
+
status.style.color = 'var(--green)';
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
case 'cancel': {
|
|
247
|
+
await fetch('/api/agents/cancel', {
|
|
248
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
249
|
+
body: JSON.stringify({ agentId: action.agent, reason: action.reason || 'Cancelled via command center' })
|
|
250
|
+
});
|
|
251
|
+
status.innerHTML = '✓ Cancelled agent: <strong>' + escHtml(action.agent) + '</strong>';
|
|
252
|
+
status.style.color = 'var(--orange)';
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
case 'retry': {
|
|
256
|
+
for (const id of (action.ids || [])) {
|
|
257
|
+
await fetch('/api/work-items/retry', {
|
|
258
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
259
|
+
body: JSON.stringify({ id, source: '' })
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
status.innerHTML = '✓ Retried: <strong>' + escHtml((action.ids || []).join(', ')) + '</strong>';
|
|
263
|
+
status.style.color = 'var(--green)';
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
case 'pause-plan': {
|
|
267
|
+
await fetch('/api/plans/pause', {
|
|
268
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
269
|
+
body: JSON.stringify({ file: action.file })
|
|
270
|
+
});
|
|
271
|
+
status.innerHTML = '✓ Paused plan: <strong>' + escHtml(action.file) + '</strong>';
|
|
272
|
+
status.style.color = 'var(--orange)';
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
case 'approve-plan': {
|
|
276
|
+
await fetch('/api/plans/approve', {
|
|
277
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
278
|
+
body: JSON.stringify({ file: action.file })
|
|
279
|
+
});
|
|
280
|
+
status.innerHTML = '✓ Approved plan: <strong>' + escHtml(action.file) + '</strong>';
|
|
281
|
+
status.style.color = 'var(--green)';
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
case 'edit-prd-item': {
|
|
285
|
+
await fetch('/api/prd-items/update', {
|
|
286
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
287
|
+
body: JSON.stringify({ source: action.source, itemId: action.itemId, name: action.name, description: action.description, priority: action.priority, estimated_complexity: action.complexity })
|
|
288
|
+
});
|
|
289
|
+
status.innerHTML = '✓ Updated PRD item: <strong>' + escHtml(action.itemId) + '</strong>';
|
|
290
|
+
status.style.color = 'var(--green)';
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
case 'remove-prd-item': {
|
|
294
|
+
await fetch('/api/prd-items/remove', {
|
|
295
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
296
|
+
body: JSON.stringify({ source: action.source, itemId: action.itemId })
|
|
297
|
+
});
|
|
298
|
+
status.innerHTML = '✓ Removed PRD item: <strong>' + escHtml(action.itemId) + '</strong>';
|
|
299
|
+
status.style.color = 'var(--orange)';
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
case 'delete-work-item': {
|
|
303
|
+
await fetch('/api/work-items/delete', {
|
|
304
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
305
|
+
body: JSON.stringify({ id: action.id, source: action.source || '' })
|
|
306
|
+
});
|
|
307
|
+
status.innerHTML = '✓ Deleted work item: <strong>' + escHtml(action.id) + '</strong>';
|
|
308
|
+
status.style.color = 'var(--orange)';
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
case 'plan-edit': {
|
|
312
|
+
// Read the plan, send instruction to doc-chat, show version actions
|
|
313
|
+
const normalizedFile = normalizePlanFile(action.file);
|
|
314
|
+
const planContent = await fetch('/api/plans/' + encodeURIComponent(normalizedFile)).then(r => r.text());
|
|
315
|
+
const res = await fetch('/api/doc-chat', {
|
|
316
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
317
|
+
body: JSON.stringify({
|
|
318
|
+
message: action.instruction,
|
|
319
|
+
document: planContent,
|
|
320
|
+
title: normalizedFile,
|
|
321
|
+
filePath: 'plans/' + normalizedFile,
|
|
322
|
+
}),
|
|
323
|
+
});
|
|
324
|
+
const data = await res.json();
|
|
325
|
+
if (data.ok && data.edited) {
|
|
326
|
+
status.innerHTML = '✓ Plan edited: <strong>' + escHtml(action.file) + '</strong>';
|
|
327
|
+
status.style.color = 'var(--green)';
|
|
328
|
+
} else {
|
|
329
|
+
status.innerHTML = data.answer ? escHtml(data.answer) : '✗ Could not edit plan';
|
|
330
|
+
status.style.color = data.answer ? 'var(--muted)' : 'var(--red)';
|
|
331
|
+
}
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
case 'execute-plan': {
|
|
335
|
+
await fetch('/api/plans/execute', {
|
|
336
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
337
|
+
body: JSON.stringify({ file: action.file, project: action.project || '' })
|
|
338
|
+
});
|
|
339
|
+
status.innerHTML = '✓ Plan execution queued: <strong>' + escHtml(action.file) + '</strong>';
|
|
340
|
+
status.style.color = 'var(--green)';
|
|
341
|
+
refreshPlans();
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
case 'file-edit': {
|
|
345
|
+
// doc-chat reads current content from disk via filePath — pass placeholder for required field
|
|
346
|
+
const res = await fetch('/api/doc-chat', {
|
|
347
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
348
|
+
body: JSON.stringify({
|
|
349
|
+
message: action.instruction,
|
|
350
|
+
document: '(loaded from disk)',
|
|
351
|
+
title: action.file.split('/').pop(),
|
|
352
|
+
filePath: action.file,
|
|
353
|
+
}),
|
|
354
|
+
});
|
|
355
|
+
const data = await res.json();
|
|
356
|
+
if (data.ok && data.edited) {
|
|
357
|
+
status.innerHTML = '✓ Edited: <strong>' + escHtml(action.file) + '</strong>';
|
|
358
|
+
status.style.color = 'var(--green)';
|
|
359
|
+
} else {
|
|
360
|
+
status.innerHTML = data.answer ? escHtml(data.answer) : '✗ Could not edit file';
|
|
361
|
+
status.style.color = data.answer ? 'var(--muted)' : 'var(--red)';
|
|
362
|
+
}
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
default:
|
|
366
|
+
status.innerHTML = '? Unknown action: ' + escHtml(action.type);
|
|
367
|
+
status.style.color = 'var(--muted)';
|
|
368
|
+
}
|
|
369
|
+
} catch (e) {
|
|
370
|
+
status.innerHTML = '✗ Action failed: ' + escHtml(e.message);
|
|
371
|
+
status.style.color = 'var(--red)';
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
msgs.appendChild(status);
|
|
375
|
+
msgs.scrollTop = msgs.scrollHeight;
|
|
376
|
+
refresh();
|
|
377
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// command-history.js — Command history functions extracted from dashboard.html
|
|
2
|
+
|
|
3
|
+
const CMD_HISTORY_KEY = 'minions-cmd-history';
|
|
4
|
+
const CMD_HISTORY_MAX = 50;
|
|
5
|
+
let _cmdHistoryIdx = -1; // -1 = not browsing history
|
|
6
|
+
let _cmdHistoryDraft = ''; // saves current draft when browsing
|
|
7
|
+
|
|
8
|
+
function cmdGetHistory() {
|
|
9
|
+
try { return JSON.parse(localStorage.getItem(CMD_HISTORY_KEY) || '[]'); } catch { return []; }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function cmdSaveHistory(raw, intent) {
|
|
13
|
+
const history = cmdGetHistory();
|
|
14
|
+
history.unshift({ text: raw, intent, timestamp: new Date().toISOString() });
|
|
15
|
+
if (history.length > CMD_HISTORY_MAX) history.length = CMD_HISTORY_MAX;
|
|
16
|
+
localStorage.setItem(CMD_HISTORY_KEY, JSON.stringify(history));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function cmdShowHistory() {
|
|
20
|
+
const history = cmdGetHistory();
|
|
21
|
+
const title = document.getElementById('modal-title');
|
|
22
|
+
const body = document.getElementById('modal-body');
|
|
23
|
+
title.textContent = 'Past Commands (' + history.length + ')';
|
|
24
|
+
|
|
25
|
+
if (history.length === 0) {
|
|
26
|
+
body.innerHTML = '<div class="cmd-history-empty">No commands yet. Submit something from the command center.</div>';
|
|
27
|
+
} else {
|
|
28
|
+
const intentColors = { 'work-item': 'var(--blue)', 'note': 'var(--green)', 'plan': 'var(--purple,#a855f7)' };
|
|
29
|
+
const intentLabels = { 'work-item': 'Work Item', 'note': 'Note', 'plan': 'Plan' };
|
|
30
|
+
body.innerHTML = '<ul class="cmd-history-list">' + history.map((item, i) => {
|
|
31
|
+
const date = new Date(item.timestamp);
|
|
32
|
+
const ago = timeSinceStr(date);
|
|
33
|
+
const intentLabel = intentLabels[item.intent] || item.intent || 'work-item';
|
|
34
|
+
const intentColor = intentColors[item.intent] || 'var(--blue)';
|
|
35
|
+
return '<li class="cmd-history-item">' +
|
|
36
|
+
'<div class="cmd-history-item-body">' +
|
|
37
|
+
'<div class="cmd-history-item-text">' + escHtml(item.text) + '</div>' +
|
|
38
|
+
'<div class="cmd-history-item-meta">' +
|
|
39
|
+
'<span class="chip" style="color:' + intentColor + '">' + intentLabel + '</span>' +
|
|
40
|
+
'<span>' + ago + '</span>' +
|
|
41
|
+
'<span>' + date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'}) + '</span>' +
|
|
42
|
+
'</div>' +
|
|
43
|
+
'</div>' +
|
|
44
|
+
'<button class="cmd-history-resubmit" onclick="cmdResubmit(' + i + ')">Resubmit</button>' +
|
|
45
|
+
'</li>';
|
|
46
|
+
}).join('') + '</ul>';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
document.getElementById('modal').classList.add('open');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function cmdResubmit(idx) {
|
|
53
|
+
const history = cmdGetHistory();
|
|
54
|
+
const item = history[idx];
|
|
55
|
+
if (!item) return;
|
|
56
|
+
document.getElementById('modal').classList.remove('open');
|
|
57
|
+
const input = document.getElementById('cmd-input');
|
|
58
|
+
input.value = item.text;
|
|
59
|
+
cmdAutoResize();
|
|
60
|
+
cmdRenderMeta();
|
|
61
|
+
input.focus();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function timeSinceStr(date) {
|
|
65
|
+
const s = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
66
|
+
if (s < 60) return s + 's ago';
|
|
67
|
+
if (s < 3600) return Math.floor(s / 60) + 'm ago';
|
|
68
|
+
if (s < 86400) return Math.floor(s / 3600) + 'h ago';
|
|
69
|
+
return Math.floor(s / 86400) + 'd ago';
|
|
70
|
+
}
|