@cluesmith/codev 1.5.10 → 1.5.11

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.
Files changed (45) hide show
  1. package/dist/agent-farm/commands/architect.d.ts.map +1 -1
  2. package/dist/agent-farm/commands/architect.js +5 -20
  3. package/dist/agent-farm/commands/architect.js.map +1 -1
  4. package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
  5. package/dist/agent-farm/commands/spawn.js +35 -22
  6. package/dist/agent-farm/commands/spawn.js.map +1 -1
  7. package/dist/agent-farm/commands/start.d.ts.map +1 -1
  8. package/dist/agent-farm/commands/start.js +35 -21
  9. package/dist/agent-farm/commands/start.js.map +1 -1
  10. package/dist/agent-farm/servers/dashboard-server.js +5 -53
  11. package/dist/agent-farm/servers/dashboard-server.js.map +1 -1
  12. package/dist/agent-farm/servers/open-server.js +2 -11
  13. package/dist/agent-farm/servers/open-server.js.map +1 -1
  14. package/dist/agent-farm/servers/tower-server.js +2 -54
  15. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  16. package/dist/agent-farm/utils/roles.d.ts +32 -0
  17. package/dist/agent-farm/utils/roles.d.ts.map +1 -0
  18. package/dist/agent-farm/utils/roles.js +43 -0
  19. package/dist/agent-farm/utils/roles.js.map +1 -0
  20. package/dist/agent-farm/utils/server-utils.d.ts +24 -0
  21. package/dist/agent-farm/utils/server-utils.d.ts.map +1 -0
  22. package/dist/agent-farm/utils/server-utils.js +66 -0
  23. package/dist/agent-farm/utils/server-utils.js.map +1 -0
  24. package/dist/commands/adopt.d.ts.map +1 -1
  25. package/dist/commands/adopt.js +37 -157
  26. package/dist/commands/adopt.js.map +1 -1
  27. package/dist/commands/init.d.ts.map +1 -1
  28. package/dist/commands/init.js +26 -138
  29. package/dist/commands/init.js.map +1 -1
  30. package/dist/lib/cli-prompts.d.ts +20 -0
  31. package/dist/lib/cli-prompts.d.ts.map +1 -0
  32. package/dist/lib/cli-prompts.js +51 -0
  33. package/dist/lib/cli-prompts.js.map +1 -0
  34. package/dist/lib/scaffold.d.ts +81 -0
  35. package/dist/lib/scaffold.d.ts.map +1 -0
  36. package/dist/lib/scaffold.js +189 -0
  37. package/dist/lib/scaffold.js.map +1 -0
  38. package/package.json +1 -1
  39. package/templates/dashboard/js/activity.js +4 -130
  40. package/templates/dashboard/js/dialogs.js +4 -49
  41. package/templates/dashboard/js/files.js +2 -29
  42. package/templates/dashboard/js/main.js +2 -31
  43. package/templates/dashboard/js/tabs.js +6 -30
  44. package/templates/dashboard/js/utils.js +199 -0
  45. package/templates/open.html +8 -8
@@ -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
- const menu = document.getElementById('overflow-menu');
499
- const items = Array.from(menu.querySelectorAll('.overflow-menu-item'));
500
- const currentIndex = items.findIndex(item => item === document.activeElement);
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
+ }
@@ -622,7 +622,7 @@
622
622
  };
623
623
 
624
624
  // Add cache-busting query param to allow reload
625
- img.src = '/api/image?t=' + Date.now();
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 = '/api/video?t=' + Date.now();
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('/save', {
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('/save', {
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('/api/mtime');
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 = '/api/image?t=' + Date.now();
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 = '/api/video?t=' + Date.now();
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('/file');
1706
+ const res = await fetch('file');
1707
1707
  if (res.ok) {
1708
1708
  const content = await res.text();
1709
1709
  currentContent = content;