@cluesmith/codev 1.5.10 → 1.5.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/dist/agent-farm/commands/architect.d.ts.map +1 -1
- package/dist/agent-farm/commands/architect.js +5 -20
- package/dist/agent-farm/commands/architect.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +35 -22
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/start.d.ts.map +1 -1
- package/dist/agent-farm/commands/start.js +35 -21
- package/dist/agent-farm/commands/start.js.map +1 -1
- package/dist/agent-farm/servers/dashboard-server.js +5 -53
- package/dist/agent-farm/servers/dashboard-server.js.map +1 -1
- package/dist/agent-farm/servers/open-server.js +2 -11
- package/dist/agent-farm/servers/open-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +2 -54
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/utils/roles.d.ts +32 -0
- package/dist/agent-farm/utils/roles.d.ts.map +1 -0
- package/dist/agent-farm/utils/roles.js +43 -0
- package/dist/agent-farm/utils/roles.js.map +1 -0
- package/dist/agent-farm/utils/server-utils.d.ts +24 -0
- package/dist/agent-farm/utils/server-utils.d.ts.map +1 -0
- package/dist/agent-farm/utils/server-utils.js +66 -0
- package/dist/agent-farm/utils/server-utils.js.map +1 -0
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +37 -157
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +26 -138
- package/dist/commands/init.js.map +1 -1
- package/dist/lib/cli-prompts.d.ts +20 -0
- package/dist/lib/cli-prompts.d.ts.map +1 -0
- package/dist/lib/cli-prompts.js +51 -0
- package/dist/lib/cli-prompts.js.map +1 -0
- package/dist/lib/scaffold.d.ts +81 -0
- package/dist/lib/scaffold.d.ts.map +1 -0
- package/dist/lib/scaffold.js +189 -0
- package/dist/lib/scaffold.js.map +1 -0
- package/package.json +1 -1
- package/templates/3d-viewer.html +5 -5
- package/templates/dashboard/js/activity.js +4 -130
- package/templates/dashboard/js/dialogs.js +4 -49
- package/templates/dashboard/js/files.js +2 -29
- package/templates/dashboard/js/main.js +2 -31
- package/templates/dashboard/js/tabs.js +6 -30
- package/templates/dashboard/js/utils.js +199 -0
- package/templates/open.html +8 -8
|
@@ -26,38 +26,9 @@ function setupBroadcastChannel() {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// Open a file from a BroadcastChannel message
|
|
29
|
+
// Uses shared openFileTab from utils.js (Maintenance Run 0004)
|
|
29
30
|
async function openFileFromMessage(filePath, lineNumber) {
|
|
30
|
-
|
|
31
|
-
const existingTab = tabs.find(t => t.type === 'file' && t.path === filePath);
|
|
32
|
-
if (existingTab) {
|
|
33
|
-
selectTab(existingTab.id);
|
|
34
|
-
refreshFileTab(existingTab.id);
|
|
35
|
-
showToast(`Switched to ${getFileName(filePath)}`, 'success');
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const response = await fetch('/api/tabs/file', {
|
|
40
|
-
method: 'POST',
|
|
41
|
-
headers: { 'Content-Type': 'application/json' },
|
|
42
|
-
body: JSON.stringify({ path: filePath })
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
if (!response.ok) {
|
|
46
|
-
throw new Error(await response.text());
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const result = await response.json();
|
|
50
|
-
await refresh();
|
|
51
|
-
|
|
52
|
-
const newTab = tabs.find(t => t.type === 'file' && (t.path === filePath || t.annotationId === result.id));
|
|
53
|
-
if (newTab) {
|
|
54
|
-
selectTab(newTab.id);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
showToast(`Opened ${getFileName(filePath)}${lineNumber ? ':' + lineNumber : ''}`, 'success');
|
|
58
|
-
} catch (err) {
|
|
59
|
-
showToast('Failed to open file: ' + err.message, 'error');
|
|
60
|
-
}
|
|
31
|
+
await openFileTab(filePath, { lineNumber });
|
|
61
32
|
}
|
|
62
33
|
|
|
63
34
|
// Refresh state from API
|
|
@@ -31,7 +31,7 @@ function getTerminalUrl(tab) {
|
|
|
31
31
|
|
|
32
32
|
// File/annotation tabs - use the annotation ID for proxy routing
|
|
33
33
|
if (tab.type === 'file' && tab.annotationId) {
|
|
34
|
-
return `/annotation/${tab.annotationId}
|
|
34
|
+
return `/annotation/${tab.annotationId}/`;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// Fallback for backward compatibility
|
|
@@ -494,36 +494,12 @@ function openInNewTabFromMenu(tabId) {
|
|
|
494
494
|
}
|
|
495
495
|
|
|
496
496
|
// Handle keyboard navigation in overflow menu
|
|
497
|
+
// Uses shared handleMenuKeydown from utils.js (Maintenance Run 0004)
|
|
497
498
|
function handleOverflowMenuKeydown(event, tabId) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
switch (event.key) {
|
|
503
|
-
case 'ArrowDown':
|
|
504
|
-
event.preventDefault();
|
|
505
|
-
const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
506
|
-
items[nextIndex].focus();
|
|
507
|
-
break;
|
|
508
|
-
case 'ArrowUp':
|
|
509
|
-
event.preventDefault();
|
|
510
|
-
const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
511
|
-
items[prevIndex].focus();
|
|
512
|
-
break;
|
|
513
|
-
case 'Enter':
|
|
514
|
-
case ' ':
|
|
515
|
-
event.preventDefault();
|
|
516
|
-
selectTabFromMenu(tabId);
|
|
517
|
-
break;
|
|
518
|
-
case 'Escape':
|
|
519
|
-
event.preventDefault();
|
|
520
|
-
hideOverflowMenu();
|
|
521
|
-
document.getElementById('overflow-btn').focus();
|
|
522
|
-
break;
|
|
523
|
-
case 'Tab':
|
|
524
|
-
hideOverflowMenu();
|
|
525
|
-
break;
|
|
526
|
-
}
|
|
499
|
+
handleMenuKeydown(event, 'overflow-menu', 'overflow-menu-item', hideOverflowMenu, {
|
|
500
|
+
onEnter: () => selectTabFromMenu(tabId),
|
|
501
|
+
focusOnEscape: 'overflow-btn'
|
|
502
|
+
});
|
|
527
503
|
}
|
|
528
504
|
|
|
529
505
|
// Open tab content in a new browser tab
|
|
@@ -55,3 +55,202 @@ function showToast(message, type = 'info') {
|
|
|
55
55
|
toast.remove();
|
|
56
56
|
}, 3000);
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
// ========================================
|
|
60
|
+
// Shared Utilities (Maintenance Run 0004)
|
|
61
|
+
// ========================================
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Open a file in a new tab (or switch to existing)
|
|
65
|
+
* Consolidates duplicate code from main.js, files.js, dialogs.js
|
|
66
|
+
*
|
|
67
|
+
* @param {string} filePath - Path to the file to open
|
|
68
|
+
* @param {Object} options - Optional settings
|
|
69
|
+
* @param {number} options.lineNumber - Line number to show in toast
|
|
70
|
+
* @param {boolean} options.showSwitchToast - Show toast when switching to existing tab
|
|
71
|
+
* @param {Function} options.onSuccess - Callback after successful open
|
|
72
|
+
*/
|
|
73
|
+
async function openFileTab(filePath, options = {}) {
|
|
74
|
+
const { lineNumber, showSwitchToast = true, onSuccess } = options;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
// Check for existing tab
|
|
78
|
+
const existingTab = tabs.find(t => t.type === 'file' && t.path === filePath);
|
|
79
|
+
if (existingTab) {
|
|
80
|
+
selectTab(existingTab.id);
|
|
81
|
+
refreshFileTab(existingTab.id);
|
|
82
|
+
if (showSwitchToast) {
|
|
83
|
+
showToast(`Switched to ${getFileName(filePath)}`, 'success');
|
|
84
|
+
}
|
|
85
|
+
if (onSuccess) onSuccess();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Create new tab
|
|
90
|
+
const response = await fetch('/api/tabs/file', {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
headers: { 'Content-Type': 'application/json' },
|
|
93
|
+
body: JSON.stringify({ path: filePath })
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
throw new Error(await response.text());
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const result = await response.json();
|
|
101
|
+
await refresh();
|
|
102
|
+
|
|
103
|
+
// Select the new tab
|
|
104
|
+
const newTab = tabs.find(t => t.type === 'file' && (t.path === filePath || t.annotationId === result.id));
|
|
105
|
+
if (newTab) {
|
|
106
|
+
selectTab(newTab.id);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const lineInfo = lineNumber ? `:${lineNumber}` : '';
|
|
110
|
+
showToast(`Opened ${getFileName(filePath)}${lineInfo}`, 'success');
|
|
111
|
+
if (onSuccess) onSuccess();
|
|
112
|
+
} catch (err) {
|
|
113
|
+
showToast('Failed to open file: ' + err.message, 'error');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Handle keyboard navigation in dropdown menus
|
|
119
|
+
* Consolidates duplicate code from dialogs.js and tabs.js
|
|
120
|
+
*
|
|
121
|
+
* @param {KeyboardEvent} event - The keyboard event
|
|
122
|
+
* @param {string} menuId - ID of the menu element
|
|
123
|
+
* @param {string} itemClass - CSS class of menu items
|
|
124
|
+
* @param {Function} hideFunction - Function to hide the menu
|
|
125
|
+
* @param {Object} options - Optional settings
|
|
126
|
+
* @param {Function} options.onEnter - Custom handler for Enter/Space (default: call data-action)
|
|
127
|
+
* @param {string} options.focusOnEscape - Element ID to focus after Escape
|
|
128
|
+
*/
|
|
129
|
+
function handleMenuKeydown(event, menuId, itemClass, hideFunction, options = {}) {
|
|
130
|
+
const { onEnter, focusOnEscape } = options;
|
|
131
|
+
const menu = document.getElementById(menuId);
|
|
132
|
+
const items = Array.from(menu.querySelectorAll(`.${itemClass}`));
|
|
133
|
+
const currentIndex = items.findIndex(item => item === document.activeElement);
|
|
134
|
+
|
|
135
|
+
switch (event.key) {
|
|
136
|
+
case 'ArrowDown':
|
|
137
|
+
event.preventDefault();
|
|
138
|
+
const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
139
|
+
items[nextIndex].focus();
|
|
140
|
+
break;
|
|
141
|
+
case 'ArrowUp':
|
|
142
|
+
event.preventDefault();
|
|
143
|
+
const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
144
|
+
items[prevIndex].focus();
|
|
145
|
+
break;
|
|
146
|
+
case 'Enter':
|
|
147
|
+
case ' ':
|
|
148
|
+
event.preventDefault();
|
|
149
|
+
if (onEnter) {
|
|
150
|
+
onEnter(event);
|
|
151
|
+
} else {
|
|
152
|
+
const actionName = event.target.dataset.action;
|
|
153
|
+
if (actionName && typeof window[actionName] === 'function') {
|
|
154
|
+
window[actionName]();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
case 'Escape':
|
|
159
|
+
event.preventDefault();
|
|
160
|
+
hideFunction();
|
|
161
|
+
if (focusOnEscape) {
|
|
162
|
+
document.getElementById(focusOnEscape).focus();
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
case 'Tab':
|
|
166
|
+
hideFunction();
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Format ISO time string for display
|
|
173
|
+
* Used by activity rendering
|
|
174
|
+
*/
|
|
175
|
+
function formatActivityTime(isoString) {
|
|
176
|
+
if (!isoString) return '--';
|
|
177
|
+
const date = new Date(isoString);
|
|
178
|
+
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Render activity summary content
|
|
183
|
+
* Consolidates duplicate code from renderActivityTabContent and renderActivitySummary
|
|
184
|
+
*
|
|
185
|
+
* @param {Object} data - Activity data
|
|
186
|
+
* @param {Object} options - Render options
|
|
187
|
+
* @param {boolean} options.isTab - Whether rendering for tab (includes wrapper and copy button)
|
|
188
|
+
* @returns {string} HTML content
|
|
189
|
+
*/
|
|
190
|
+
function renderActivityContentHtml(data, options = {}) {
|
|
191
|
+
const { isTab = false } = options;
|
|
192
|
+
|
|
193
|
+
if (data.commits.length === 0 && data.prs.length === 0 && data.builders.length === 0) {
|
|
194
|
+
return `
|
|
195
|
+
<div class="activity-empty">
|
|
196
|
+
<p>No activity recorded today</p>
|
|
197
|
+
<p style="font-size: 12px; margin-top: 8px;">Make some commits or create PRs to see your daily summary!</p>
|
|
198
|
+
</div>
|
|
199
|
+
`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const hours = Math.floor(data.timeTracking.activeMinutes / 60);
|
|
203
|
+
const mins = data.timeTracking.activeMinutes % 60;
|
|
204
|
+
const uniqueBranches = new Set(data.commits.map(c => c.branch)).size;
|
|
205
|
+
const mergedPrs = data.prs.filter(p => p.state === 'MERGED').length;
|
|
206
|
+
|
|
207
|
+
let html = isTab ? '<div class="activity-tab-container"><div class="activity-summary">' : '<div class="activity-summary">';
|
|
208
|
+
|
|
209
|
+
if (data.aiSummary) {
|
|
210
|
+
html += `<div class="activity-ai-summary">${escapeHtml(data.aiSummary)}</div>`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
html += `
|
|
214
|
+
<div class="activity-section">
|
|
215
|
+
<h4>Activity</h4>
|
|
216
|
+
<ul>
|
|
217
|
+
<li>${data.commits.length} commits across ${uniqueBranches} branch${uniqueBranches !== 1 ? 'es' : ''}</li>
|
|
218
|
+
<li>${data.files.length} files modified</li>
|
|
219
|
+
<li>${data.prs.length} PR${data.prs.length !== 1 ? 's' : ''} created${mergedPrs > 0 ? `, ${mergedPrs} merged` : ''}</li>
|
|
220
|
+
</ul>
|
|
221
|
+
</div>
|
|
222
|
+
`;
|
|
223
|
+
|
|
224
|
+
if (data.projectChanges && data.projectChanges.length > 0) {
|
|
225
|
+
html += `
|
|
226
|
+
<div class="activity-section">
|
|
227
|
+
<h4>Projects Touched</h4>
|
|
228
|
+
<ul>
|
|
229
|
+
${data.projectChanges.map(p => `<li>${escapeHtml(p.id)}: ${escapeHtml(p.title)} (${escapeHtml(p.oldStatus)} → ${escapeHtml(p.newStatus)})</li>`).join('')}
|
|
230
|
+
</ul>
|
|
231
|
+
</div>
|
|
232
|
+
`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
html += `
|
|
236
|
+
<div class="activity-section">
|
|
237
|
+
<h4>Time</h4>
|
|
238
|
+
<p><span class="activity-time-value">~${hours}h ${mins}m</span> active time</p>
|
|
239
|
+
<p>First activity: ${formatActivityTime(data.timeTracking.firstActivity)}</p>
|
|
240
|
+
<p>Last activity: ${formatActivityTime(data.timeTracking.lastActivity)}</p>
|
|
241
|
+
</div>
|
|
242
|
+
`;
|
|
243
|
+
|
|
244
|
+
if (isTab) {
|
|
245
|
+
html += `
|
|
246
|
+
<div class="activity-actions">
|
|
247
|
+
<button class="btn" onclick="copyActivityToClipboard()">Copy to Clipboard</button>
|
|
248
|
+
</div>
|
|
249
|
+
`;
|
|
250
|
+
html += '</div></div>';
|
|
251
|
+
} else {
|
|
252
|
+
html += '</div>';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return html;
|
|
256
|
+
}
|
package/templates/open.html
CHANGED
|
@@ -622,7 +622,7 @@
|
|
|
622
622
|
};
|
|
623
623
|
|
|
624
624
|
// Add cache-busting query param to allow reload
|
|
625
|
-
img.src = '
|
|
625
|
+
img.src = 'api/image?t=' + Date.now();
|
|
626
626
|
}
|
|
627
627
|
|
|
628
628
|
// Initialize video viewer
|
|
@@ -656,7 +656,7 @@
|
|
|
656
656
|
};
|
|
657
657
|
|
|
658
658
|
// Add cache-busting query param to allow reload
|
|
659
|
-
video.src = '
|
|
659
|
+
video.src = 'api/video?t=' + Date.now();
|
|
660
660
|
}
|
|
661
661
|
|
|
662
662
|
// Update image info display (in both header and controls)
|
|
@@ -1245,7 +1245,7 @@
|
|
|
1245
1245
|
|
|
1246
1246
|
async function saveFile() {
|
|
1247
1247
|
try {
|
|
1248
|
-
const response = await fetch('
|
|
1248
|
+
const response = await fetch('save', {
|
|
1249
1249
|
method: 'POST',
|
|
1250
1250
|
headers: { 'Content-Type': 'application/json' },
|
|
1251
1251
|
body: JSON.stringify({
|
|
@@ -1348,7 +1348,7 @@
|
|
|
1348
1348
|
const content = editor.value;
|
|
1349
1349
|
|
|
1350
1350
|
try {
|
|
1351
|
-
const response = await fetch('
|
|
1351
|
+
const response = await fetch('save', {
|
|
1352
1352
|
method: 'POST',
|
|
1353
1353
|
headers: { 'Content-Type': 'application/json' },
|
|
1354
1354
|
body: JSON.stringify({ file: filePath, content })
|
|
@@ -1670,7 +1670,7 @@
|
|
|
1670
1670
|
if (hasUnsavedChanges || editMode) return;
|
|
1671
1671
|
|
|
1672
1672
|
try {
|
|
1673
|
-
const res = await fetch('
|
|
1673
|
+
const res = await fetch('api/mtime');
|
|
1674
1674
|
if (res.ok) {
|
|
1675
1675
|
const data = await res.json();
|
|
1676
1676
|
if (lastMtime === null) {
|
|
@@ -1689,21 +1689,21 @@
|
|
|
1689
1689
|
// For images and videos, just reload the source
|
|
1690
1690
|
if (isImageFile) {
|
|
1691
1691
|
const img = document.getElementById('image-display');
|
|
1692
|
-
img.src = '
|
|
1692
|
+
img.src = 'api/image?t=' + Date.now();
|
|
1693
1693
|
showNotification('Image reloaded');
|
|
1694
1694
|
return;
|
|
1695
1695
|
}
|
|
1696
1696
|
|
|
1697
1697
|
if (isVideoFile) {
|
|
1698
1698
|
const video = document.getElementById('video-display');
|
|
1699
|
-
video.src = '
|
|
1699
|
+
video.src = 'api/video?t=' + Date.now();
|
|
1700
1700
|
showNotification('Video reloaded');
|
|
1701
1701
|
return;
|
|
1702
1702
|
}
|
|
1703
1703
|
|
|
1704
1704
|
// For text files, fetch new content
|
|
1705
1705
|
try {
|
|
1706
|
-
const res = await fetch('
|
|
1706
|
+
const res = await fetch('file');
|
|
1707
1707
|
if (res.ok) {
|
|
1708
1708
|
const content = await res.text();
|
|
1709
1709
|
currentContent = content;
|