@cluesmith/codev 1.3.0 → 1.4.1

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 (63) hide show
  1. package/bin/af.js +0 -0
  2. package/bin/codev.js +0 -0
  3. package/bin/consult.js +0 -0
  4. package/bin/generate-image.js +0 -0
  5. package/dist/agent-farm/cli.d.ts.map +1 -1
  6. package/dist/agent-farm/cli.js +2 -1
  7. package/dist/agent-farm/cli.js.map +1 -1
  8. package/dist/agent-farm/commands/cleanup.js +12 -51
  9. package/dist/agent-farm/commands/cleanup.js.map +1 -1
  10. package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
  11. package/dist/agent-farm/commands/spawn.js +13 -1
  12. package/dist/agent-farm/commands/spawn.js.map +1 -1
  13. package/dist/agent-farm/servers/dashboard-server.js +107 -5
  14. package/dist/agent-farm/servers/dashboard-server.js.map +1 -1
  15. package/dist/agent-farm/types.d.ts +1 -0
  16. package/dist/agent-farm/types.d.ts.map +1 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +2 -17
  19. package/dist/cli.js.map +1 -1
  20. package/dist/commands/adopt.d.ts.map +1 -1
  21. package/dist/commands/adopt.js +27 -2
  22. package/dist/commands/adopt.js.map +1 -1
  23. package/dist/commands/consult/index.d.ts.map +1 -1
  24. package/dist/commands/consult/index.js +23 -7
  25. package/dist/commands/consult/index.js.map +1 -1
  26. package/dist/commands/doctor.d.ts.map +1 -1
  27. package/dist/commands/doctor.js +51 -0
  28. package/dist/commands/doctor.js.map +1 -1
  29. package/dist/commands/init.d.ts.map +1 -1
  30. package/dist/commands/init.js +23 -2
  31. package/dist/commands/init.js.map +1 -1
  32. package/dist/version.d.ts +3 -0
  33. package/dist/version.d.ts.map +1 -0
  34. package/dist/version.js +23 -0
  35. package/dist/version.js.map +1 -0
  36. package/package.json +1 -1
  37. package/skeleton/DEPENDENCIES.md +3 -3
  38. package/skeleton/protocols/maintain/protocol.md +2 -2
  39. package/skeleton/{docs → resources}/commands/codev.md +0 -39
  40. package/skeleton/{docs → resources}/commands/consult.md +12 -2
  41. package/skeleton/{docs → resources}/commands/overview.md +0 -1
  42. package/skeleton/roles/architect.md +22 -0
  43. package/skeleton/roles/builder.md +22 -0
  44. package/skeleton/templates/arch.md +56 -0
  45. package/skeleton/templates/pr-overview.md +73 -0
  46. package/templates/dashboard-split.html +526 -164
  47. package/templates/open.html +285 -2
  48. package/templates/tower.html +71 -12
  49. package/dist/agent-farm/index.d.ts +0 -7
  50. package/dist/agent-farm/index.d.ts.map +0 -1
  51. package/dist/agent-farm/index.js +0 -373
  52. package/dist/agent-farm/index.js.map +0 -1
  53. package/skeleton/bin/agent-farm +0 -7
  54. package/skeleton/bin/codev-doctor +0 -335
  55. package/skeleton/resources/lessons-learned.md +0 -30
  56. /package/skeleton/{roles/review-types → consult-types}/impl-review.md +0 -0
  57. /package/skeleton/{roles/review-types → consult-types}/integration-review.md +0 -0
  58. /package/skeleton/{roles/review-types → consult-types}/plan-review.md +0 -0
  59. /package/skeleton/{roles/review-types → consult-types}/pr-ready.md +0 -0
  60. /package/skeleton/{roles/review-types → consult-types}/spec-review.md +0 -0
  61. /package/skeleton/{docs → resources}/commands/agent-farm.md +0 -0
  62. /package/skeleton/{AGENTS.md.template → templates/AGENTS.md} +0 -0
  63. /package/skeleton/{CLAUDE.md.template → templates/CLAUDE.md} +0 -0
@@ -13,11 +13,14 @@
13
13
  </script>
14
14
  <style>
15
15
  * { box-sizing: border-box; margin: 0; padding: 0; }
16
- body {
16
+ html, body {
17
17
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
18
18
  background: #1a1a1a;
19
19
  color: #fff;
20
- min-height: 100vh;
20
+ height: 100%;
21
+ }
22
+ body.preview-active {
23
+ overflow: hidden;
21
24
  }
22
25
  .header {
23
26
  padding: 15px 20px;
@@ -199,6 +202,78 @@
199
202
  outline: none;
200
203
  }
201
204
 
205
+ /* Search bar */
206
+ .search-bar {
207
+ display: none;
208
+ position: fixed;
209
+ top: 10px;
210
+ right: 20px;
211
+ background: #2a2a2a;
212
+ border: 1px solid #444;
213
+ border-radius: 6px;
214
+ padding: 8px 12px;
215
+ z-index: 250;
216
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
217
+ }
218
+ .search-bar.active {
219
+ display: flex;
220
+ align-items: center;
221
+ gap: 8px;
222
+ }
223
+ .search-bar input {
224
+ background: #1a1a1a;
225
+ border: 1px solid #444;
226
+ border-radius: 4px;
227
+ color: #fff;
228
+ padding: 6px 10px;
229
+ font-size: 14px;
230
+ width: 250px;
231
+ outline: none;
232
+ }
233
+ .search-bar input:focus {
234
+ border-color: #3b82f6;
235
+ }
236
+ .search-bar .search-nav {
237
+ display: flex;
238
+ align-items: center;
239
+ gap: 4px;
240
+ }
241
+ .search-bar .search-nav button {
242
+ background: #444;
243
+ border: none;
244
+ color: #fff;
245
+ padding: 4px 8px;
246
+ border-radius: 4px;
247
+ cursor: pointer;
248
+ font-size: 14px;
249
+ }
250
+ .search-bar .search-nav button:hover {
251
+ background: #555;
252
+ }
253
+ .search-bar .search-count {
254
+ color: #888;
255
+ font-size: 12px;
256
+ min-width: 60px;
257
+ }
258
+ .search-bar .search-close {
259
+ background: none;
260
+ border: none;
261
+ color: #888;
262
+ cursor: pointer;
263
+ font-size: 18px;
264
+ padding: 0 4px;
265
+ }
266
+ .search-bar .search-close:hover {
267
+ color: #fff;
268
+ }
269
+ .search-highlight {
270
+ background: rgba(250, 204, 21, 0.4);
271
+ border-radius: 2px;
272
+ }
273
+ .search-highlight.current {
274
+ background: rgba(59, 130, 246, 0.6);
275
+ }
276
+
202
277
  /* Notification toast */
203
278
  .notification {
204
279
  position: fixed;
@@ -401,6 +476,17 @@
401
476
  <!-- Editor mode -->
402
477
  <textarea id="editor" spellcheck="false"></textarea>
403
478
 
479
+ <!-- Search Bar -->
480
+ <div class="search-bar" id="searchBar">
481
+ <input type="text" id="searchInput" placeholder="Search..." autocomplete="off" />
482
+ <div class="search-nav">
483
+ <span class="search-count" id="searchCount"></span>
484
+ <button onclick="searchPrev()" title="Previous (Shift+Enter)">↑</button>
485
+ <button onclick="searchNext()" title="Next (Enter)">↓</button>
486
+ </div>
487
+ <button class="search-close" onclick="closeSearch()" title="Close (Esc)">×</button>
488
+ </div>
489
+
404
490
  <!-- Comment Dialog -->
405
491
  <div class="overlay" id="overlay">
406
492
  <div class="dialog">
@@ -702,12 +788,14 @@
702
788
  renderPreview();
703
789
  viewMode.style.display = 'none';
704
790
  previewContainer.style.display = 'block';
791
+ document.body.classList.add('preview-active');
705
792
  toggleIcon.textContent = '📝';
706
793
  toggleText.textContent = 'Annotate';
707
794
  } else {
708
795
  // Switch back to annotated view
709
796
  viewMode.style.display = 'grid';
710
797
  previewContainer.style.display = 'none';
798
+ document.body.classList.remove('preview-active');
711
799
  toggleIcon.textContent = '👁';
712
800
  toggleText.textContent = 'Preview';
713
801
  }
@@ -1377,6 +1465,201 @@
1377
1465
  }
1378
1466
  });
1379
1467
 
1468
+ // ==================== Search Functions ====================
1469
+
1470
+ let searchMatches = [];
1471
+ let currentMatchIndex = -1;
1472
+ let searchActive = false;
1473
+
1474
+ function openSearch() {
1475
+ const searchBar = document.getElementById('searchBar');
1476
+ const searchInput = document.getElementById('searchInput');
1477
+ searchBar.classList.add('active');
1478
+ searchInput.focus();
1479
+ searchInput.select();
1480
+ searchActive = true;
1481
+ }
1482
+
1483
+ function closeSearch() {
1484
+ const searchBar = document.getElementById('searchBar');
1485
+ searchBar.classList.remove('active');
1486
+ clearSearchHighlights();
1487
+ searchMatches = [];
1488
+ currentMatchIndex = -1;
1489
+ searchActive = false;
1490
+ document.getElementById('searchCount').textContent = '';
1491
+ }
1492
+
1493
+ function clearSearchHighlights() {
1494
+ document.querySelectorAll('.search-highlight').forEach(el => {
1495
+ const parent = el.parentNode;
1496
+ parent.replaceChild(document.createTextNode(el.textContent), el);
1497
+ parent.normalize();
1498
+ });
1499
+ }
1500
+
1501
+ function performSearch(query) {
1502
+ clearSearchHighlights();
1503
+ searchMatches = [];
1504
+ currentMatchIndex = -1;
1505
+
1506
+ if (!query) {
1507
+ document.getElementById('searchCount').textContent = '';
1508
+ return;
1509
+ }
1510
+
1511
+ const viewMode = document.getElementById('viewMode');
1512
+ const codeLines = viewMode.querySelectorAll('.code-line');
1513
+ const queryLower = query.toLowerCase();
1514
+
1515
+ codeLines.forEach((codeLine, lineIndex) => {
1516
+ const walker = document.createTreeWalker(codeLine, NodeFilter.SHOW_TEXT, null, false);
1517
+ const textNodes = [];
1518
+ while (walker.nextNode()) {
1519
+ textNodes.push(walker.currentNode);
1520
+ }
1521
+
1522
+ textNodes.forEach(textNode => {
1523
+ const text = textNode.textContent;
1524
+ const textLower = text.toLowerCase();
1525
+ let startIndex = 0;
1526
+ let index;
1527
+
1528
+ while ((index = textLower.indexOf(queryLower, startIndex)) !== -1) {
1529
+ searchMatches.push({
1530
+ node: textNode,
1531
+ index: index,
1532
+ length: query.length,
1533
+ lineIndex: lineIndex
1534
+ });
1535
+ startIndex = index + 1;
1536
+ }
1537
+ });
1538
+ });
1539
+
1540
+ // Highlight matches (in reverse to preserve indices)
1541
+ const processedNodes = new Set();
1542
+ searchMatches.slice().reverse().forEach((match, reverseIdx) => {
1543
+ const idx = searchMatches.length - 1 - reverseIdx;
1544
+ if (processedNodes.has(match.node)) return;
1545
+
1546
+ const text = match.node.textContent;
1547
+ const parent = match.node.parentNode;
1548
+
1549
+ // Find all matches in this node
1550
+ const nodeMatches = searchMatches.filter(m => m.node === match.node);
1551
+ processedNodes.add(match.node);
1552
+
1553
+ // Build new content with highlights
1554
+ let lastEnd = 0;
1555
+ const fragment = document.createDocumentFragment();
1556
+
1557
+ nodeMatches.forEach((m, i) => {
1558
+ // Text before match
1559
+ if (m.index > lastEnd) {
1560
+ fragment.appendChild(document.createTextNode(text.substring(lastEnd, m.index)));
1561
+ }
1562
+ // Highlighted match
1563
+ const span = document.createElement('span');
1564
+ span.className = 'search-highlight';
1565
+ span.textContent = text.substring(m.index, m.index + m.length);
1566
+ span.dataset.matchIndex = searchMatches.indexOf(m);
1567
+ fragment.appendChild(span);
1568
+ lastEnd = m.index + m.length;
1569
+ });
1570
+
1571
+ // Text after last match
1572
+ if (lastEnd < text.length) {
1573
+ fragment.appendChild(document.createTextNode(text.substring(lastEnd)));
1574
+ }
1575
+
1576
+ parent.replaceChild(fragment, match.node);
1577
+ });
1578
+
1579
+ updateSearchCount();
1580
+
1581
+ // Jump to first match
1582
+ if (searchMatches.length > 0) {
1583
+ currentMatchIndex = 0;
1584
+ highlightCurrentMatch();
1585
+ }
1586
+ }
1587
+
1588
+ function updateSearchCount() {
1589
+ const countEl = document.getElementById('searchCount');
1590
+ if (searchMatches.length === 0) {
1591
+ countEl.textContent = 'No results';
1592
+ } else {
1593
+ countEl.textContent = `${currentMatchIndex + 1} of ${searchMatches.length}`;
1594
+ }
1595
+ }
1596
+
1597
+ function highlightCurrentMatch() {
1598
+ // Remove current class from all
1599
+ document.querySelectorAll('.search-highlight.current').forEach(el => {
1600
+ el.classList.remove('current');
1601
+ });
1602
+
1603
+ // Add current class to current match
1604
+ const currentHighlight = document.querySelector(`.search-highlight[data-match-index="${currentMatchIndex}"]`);
1605
+ if (currentHighlight) {
1606
+ currentHighlight.classList.add('current');
1607
+ currentHighlight.scrollIntoView({ behavior: 'smooth', block: 'center' });
1608
+ }
1609
+
1610
+ updateSearchCount();
1611
+ }
1612
+
1613
+ function searchNext() {
1614
+ if (searchMatches.length === 0) return;
1615
+ currentMatchIndex = (currentMatchIndex + 1) % searchMatches.length;
1616
+ highlightCurrentMatch();
1617
+ }
1618
+
1619
+ function searchPrev() {
1620
+ if (searchMatches.length === 0) return;
1621
+ currentMatchIndex = (currentMatchIndex - 1 + searchMatches.length) % searchMatches.length;
1622
+ highlightCurrentMatch();
1623
+ }
1624
+
1625
+ // Search input handlers
1626
+ document.getElementById('searchInput').addEventListener('input', (e) => {
1627
+ performSearch(e.target.value);
1628
+ });
1629
+
1630
+ document.getElementById('searchInput').addEventListener('keydown', (e) => {
1631
+ if (e.key === 'Enter') {
1632
+ e.preventDefault();
1633
+ if (e.shiftKey) {
1634
+ searchPrev();
1635
+ } else {
1636
+ searchNext();
1637
+ }
1638
+ } else if (e.key === 'Escape') {
1639
+ closeSearch();
1640
+ }
1641
+ });
1642
+
1643
+ // Cmd/Ctrl+F to open search, Cmd/Ctrl+G for next match
1644
+ document.addEventListener('keydown', (e) => {
1645
+ if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
1646
+ e.preventDefault();
1647
+ openSearch();
1648
+ }
1649
+ if ((e.ctrlKey || e.metaKey) && e.key === 'g') {
1650
+ e.preventDefault();
1651
+ if (searchActive) {
1652
+ if (e.shiftKey) {
1653
+ searchPrev();
1654
+ } else {
1655
+ searchNext();
1656
+ }
1657
+ } else {
1658
+ openSearch();
1659
+ }
1660
+ }
1661
+ });
1662
+
1380
1663
  // FILE_CONTENT will be injected by the server
1381
1664
  </script>
1382
1665
  </body>
@@ -152,28 +152,42 @@
152
152
  font-size: 15px;
153
153
  }
154
154
 
155
- /* Instance cards - 4 column grid */
155
+ /* Instance cards - dynamic grid based on count */
156
156
  .instances {
157
157
  display: grid;
158
- grid-template-columns: repeat(4, 1fr);
159
158
  gap: 16px;
160
159
  margin-bottom: 32px;
161
160
  }
162
161
 
163
- @media (max-width: 1600px) {
164
- .instances {
165
- grid-template-columns: repeat(3, 1fr);
166
- }
162
+ .instances-1 {
163
+ grid-template-columns: 1fr;
164
+ }
165
+
166
+ .instances-2 {
167
+ grid-template-columns: repeat(2, 1fr);
168
+ }
169
+
170
+ .instances-3 {
171
+ grid-template-columns: repeat(3, 1fr);
172
+ }
173
+
174
+ .instances-many {
175
+ grid-template-columns: repeat(3, 1fr);
167
176
  }
168
177
 
169
178
  @media (max-width: 1200px) {
170
- .instances {
179
+ .instances-2,
180
+ .instances-3,
181
+ .instances-many {
171
182
  grid-template-columns: repeat(2, 1fr);
172
183
  }
173
184
  }
174
185
 
175
186
  @media (max-width: 800px) {
176
- .instances {
187
+ .instances-1,
188
+ .instances-2,
189
+ .instances-3,
190
+ .instances-many {
177
191
  grid-template-columns: 1fr;
178
192
  }
179
193
  }
@@ -204,10 +218,28 @@
204
218
  }
205
219
 
206
220
  .instance-path-row {
207
- padding: 0 20px 16px;
221
+ display: flex;
222
+ align-items: center;
223
+ gap: 8px;
224
+ padding: 8px 20px;
208
225
  border-bottom: 1px solid var(--border);
209
226
  }
210
227
 
228
+ .copy-btn {
229
+ background: none;
230
+ border: none;
231
+ color: var(--text-muted);
232
+ cursor: pointer;
233
+ padding: 4px;
234
+ font-size: 12px;
235
+ opacity: 0.6;
236
+ transition: opacity 0.15s;
237
+ }
238
+
239
+ .copy-btn:hover {
240
+ opacity: 1;
241
+ }
242
+
211
243
  .instance-name {
212
244
  font-size: 20px;
213
245
  font-weight: 600;
@@ -254,9 +286,14 @@
254
286
  }
255
287
 
256
288
  .instance-path {
257
- font-size: 14px;
289
+ font-size: 12px;
258
290
  color: var(--text-muted);
259
291
  font-family: monospace;
292
+ overflow: hidden;
293
+ text-overflow: ellipsis;
294
+ white-space: nowrap;
295
+ flex: 1;
296
+ min-width: 0;
260
297
  }
261
298
 
262
299
  .instance-body {
@@ -282,11 +319,14 @@
282
319
  display: flex;
283
320
  align-items: center;
284
321
  gap: 12px;
322
+ min-width: 0;
323
+ flex: 1;
285
324
  }
286
325
 
287
326
  .port-type {
288
327
  font-size: 15px;
289
328
  color: var(--text-secondary);
329
+ flex-shrink: 0;
290
330
  min-width: 100px;
291
331
  }
292
332
 
@@ -294,6 +334,9 @@
294
334
  font-size: 15px;
295
335
  font-family: monospace;
296
336
  color: var(--text-muted);
337
+ overflow: hidden;
338
+ text-overflow: ellipsis;
339
+ white-space: nowrap;
297
340
  }
298
341
 
299
342
  .port-status {
@@ -313,6 +356,7 @@
313
356
  .port-actions {
314
357
  display: flex;
315
358
  gap: 8px;
359
+ flex-shrink: 0;
316
360
  }
317
361
 
318
362
  .port-actions a {
@@ -643,9 +687,13 @@
643
687
  </div>
644
688
  `;
645
689
  } else {
690
+ const countClass = runningInstances.length === 1 ? 'instances-1'
691
+ : runningInstances.length === 2 ? 'instances-2'
692
+ : runningInstances.length === 3 ? 'instances-3'
693
+ : 'instances-many';
646
694
  content.innerHTML = `
647
695
  <h2 class="section-header">Running Instances</h2>
648
- <div class="instances">
696
+ <div class="instances ${countClass}">
649
697
  ${runningInstances.map(renderInstance).join('')}
650
698
  </div>
651
699
  `;
@@ -677,7 +725,7 @@
677
725
  <div class="port-info">
678
726
  <span class="port-status ${port.active ? 'active' : 'inactive'}"></span>
679
727
  <span class="port-type">${escapeHtml(port.type)}</span>
680
- <span class="port-url">${escapeHtml(port.url)}</span>
728
+ <span class="port-url">Port ${port.port}</span>
681
729
  </div>
682
730
  <div class="port-actions">
683
731
  <a href="${escapeHtml(port.url)}" target="_blank" class="${port.active ? '' : 'disabled'}">
@@ -714,6 +762,7 @@
714
762
  <span class="instance-path" title="${escapeHtml(instance.projectPath)}">
715
763
  ${escapeHtml(instance.projectPath)}
716
764
  </span>
765
+ <button class="copy-btn" onclick="copyPath('${escapeHtml(instance.projectPath)}')" title="Copy path">📋</button>
717
766
  </div>
718
767
  <div class="instance-body">
719
768
  <div class="ports-list">
@@ -1001,6 +1050,16 @@
1001
1050
  return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
1002
1051
  }
1003
1052
 
1053
+ // Copy path to clipboard
1054
+ async function copyPath(path) {
1055
+ try {
1056
+ await navigator.clipboard.writeText(path);
1057
+ showToast('Path copied', 'success');
1058
+ } catch (err) {
1059
+ showToast('Failed to copy', 'error');
1060
+ }
1061
+ }
1062
+
1004
1063
  // HTML escape
1005
1064
  function escapeHtml(str) {
1006
1065
  if (!str) return '';
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Agent Farm CLI
4
- * A multi-agent orchestration tool for software development
5
- */
6
- export {};
7
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent-farm/index.ts"],"names":[],"mappings":";AAEA;;;GAGG"}