@cluesmith/codev 2.0.0-rc.63 → 2.0.0-rc.69

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 (59) hide show
  1. package/dashboard/dist/assets/{index-D6VqWAaI.js → index-CG7nUttd.js} +22 -22
  2. package/dashboard/dist/assets/index-CG7nUttd.js.map +1 -0
  3. package/dashboard/dist/index.html +1 -1
  4. package/dist/agent-farm/cli.d.ts.map +1 -1
  5. package/dist/agent-farm/cli.js +4 -1
  6. package/dist/agent-farm/cli.js.map +1 -1
  7. package/dist/agent-farm/commands/architect.d.ts.map +1 -1
  8. package/dist/agent-farm/commands/architect.js +4 -6
  9. package/dist/agent-farm/commands/architect.js.map +1 -1
  10. package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
  11. package/dist/agent-farm/commands/spawn.js +54 -6
  12. package/dist/agent-farm/commands/spawn.js.map +1 -1
  13. package/dist/agent-farm/commands/tower-cloud.d.ts +1 -0
  14. package/dist/agent-farm/commands/tower-cloud.d.ts.map +1 -1
  15. package/dist/agent-farm/commands/tower-cloud.js +9 -8
  16. package/dist/agent-farm/commands/tower-cloud.js.map +1 -1
  17. package/dist/agent-farm/db/index.d.ts.map +1 -1
  18. package/dist/agent-farm/db/index.js +18 -0
  19. package/dist/agent-farm/db/index.js.map +1 -1
  20. package/dist/agent-farm/lib/cloud-config.d.ts +13 -0
  21. package/dist/agent-farm/lib/cloud-config.d.ts.map +1 -1
  22. package/dist/agent-farm/lib/cloud-config.js +38 -1
  23. package/dist/agent-farm/lib/cloud-config.js.map +1 -1
  24. package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
  25. package/dist/agent-farm/lib/tunnel-client.js +15 -6
  26. package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
  27. package/dist/agent-farm/servers/tower-server.js +166 -138
  28. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  29. package/dist/agent-farm/utils/session.d.ts +22 -0
  30. package/dist/agent-farm/utils/session.d.ts.map +1 -1
  31. package/dist/agent-farm/utils/session.js +45 -0
  32. package/dist/agent-farm/utils/session.js.map +1 -1
  33. package/dist/commands/consult/index.d.ts +10 -2
  34. package/dist/commands/consult/index.d.ts.map +1 -1
  35. package/dist/commands/consult/index.js +133 -37
  36. package/dist/commands/consult/index.js.map +1 -1
  37. package/dist/commands/doctor.d.ts.map +1 -1
  38. package/dist/commands/doctor.js +96 -52
  39. package/dist/commands/doctor.js.map +1 -1
  40. package/dist/commands/porch/index.d.ts +4 -0
  41. package/dist/commands/porch/index.d.ts.map +1 -1
  42. package/dist/commands/porch/index.js +40 -11
  43. package/dist/commands/porch/index.js.map +1 -1
  44. package/dist/commands/porch/state.d.ts +18 -0
  45. package/dist/commands/porch/state.d.ts.map +1 -1
  46. package/dist/commands/porch/state.js +41 -2
  47. package/dist/commands/porch/state.js.map +1 -1
  48. package/package.json +2 -1
  49. package/skeleton/protocols/bugfix/builder-prompt.md +3 -3
  50. package/skeleton/protocols/bugfix/prompts/pr.md +8 -4
  51. package/skeleton/protocols/bugfix/protocol.json +2 -32
  52. package/skeleton/protocols/experiment/builder-prompt.md +1 -1
  53. package/skeleton/protocols/maintain/builder-prompt.md +1 -1
  54. package/skeleton/protocols/spir/builder-prompt.md +1 -1
  55. package/skeleton/protocols/tick/builder-prompt.md +1 -1
  56. package/skeleton/protocols/tick/protocol.json +1 -1
  57. package/skeleton/roles/builder.md +9 -8
  58. package/templates/tower.html +275 -41
  59. package/dashboard/dist/assets/index-D6VqWAaI.js.map +0 -1
@@ -422,6 +422,11 @@
422
422
  gap: 24px;
423
423
  }
424
424
 
425
+ .new-shell-row {
426
+ margin-top: 8px;
427
+ padding-top: 8px;
428
+ }
429
+
425
430
  /* Recents section */
426
431
  .recents-section {
427
432
  margin-top: 32px;
@@ -637,6 +642,61 @@
637
642
  }
638
643
  }
639
644
 
645
+ /* Cloud status */
646
+ .cloud-status {
647
+ display: inline-flex;
648
+ align-items: center;
649
+ gap: 8px;
650
+ font-size: 14px;
651
+ color: var(--text-secondary);
652
+ }
653
+
654
+ .cloud-dot {
655
+ width: 8px;
656
+ height: 8px;
657
+ border-radius: 50%;
658
+ display: inline-block;
659
+ }
660
+
661
+ .cloud-dot--green { background: var(--status-running); }
662
+ .cloud-dot--yellow { background: #eab308; }
663
+ .cloud-dot--red { background: #ef4444; }
664
+ .cloud-dot--gray { background: var(--text-muted); }
665
+
666
+ .cloud-link {
667
+ color: var(--accent);
668
+ text-decoration: none;
669
+ font-size: 13px;
670
+ }
671
+
672
+ .cloud-link:hover {
673
+ text-decoration: underline;
674
+ }
675
+
676
+ .cloud-uptime {
677
+ color: var(--text-muted);
678
+ font-size: 12px;
679
+ }
680
+
681
+ .cloud-btn {
682
+ padding: 4px 10px;
683
+ border-radius: 4px;
684
+ border: 1px solid var(--border);
685
+ background: var(--bg-tertiary);
686
+ color: var(--text-secondary);
687
+ cursor: pointer;
688
+ font-size: 12px;
689
+ }
690
+
691
+ .cloud-btn:hover {
692
+ background: #333;
693
+ }
694
+
695
+ .cloud-btn:disabled {
696
+ opacity: 0.5;
697
+ cursor: default;
698
+ }
699
+
640
700
  /* Reduced motion */
641
701
  @media (prefers-reduced-motion: reduce) {
642
702
  .status-dot.running,
@@ -672,10 +732,24 @@
672
732
  justify-content: flex-end;
673
733
  }
674
734
 
735
+ /* 1. Hide Share button on mobile (tunnel is for reaching phone, not FROM phone) */
736
+ #share-btn {
737
+ display: none !important;
738
+ }
739
+
740
+ /* 7. Reduce section spacing */
675
741
  .main {
676
- padding: 16px;
677
- padding-left: calc(16px + env(safe-area-inset-left, 0));
678
- padding-right: calc(16px + env(safe-area-inset-right, 0));
742
+ padding: 12px;
743
+ padding-left: calc(12px + env(safe-area-inset-left, 0));
744
+ padding-right: calc(12px + env(safe-area-inset-right, 0));
745
+ }
746
+
747
+ .section-header {
748
+ margin-bottom: 8px;
749
+ }
750
+
751
+ .instances {
752
+ gap: 10px;
679
753
  }
680
754
 
681
755
  .btn {
@@ -689,34 +763,78 @@
689
763
  padding: 10px 14px;
690
764
  }
691
765
 
766
+ /* 2. Keep project name + status badge + Restart/Stop on one line */
692
767
  .instance-header {
693
- flex-direction: column;
694
- align-items: flex-start;
695
- gap: 12px;
696
- padding: 16px;
768
+ flex-wrap: wrap;
769
+ gap: 8px;
770
+ padding: 12px 16px;
697
771
  }
698
772
 
699
773
  .instance-actions {
700
- width: 100%;
701
- justify-content: flex-end;
774
+ margin-left: auto;
702
775
  }
703
776
 
777
+ /* 3. Hide project path row on mobile */
778
+ .instance-path-row {
779
+ display: none;
780
+ }
781
+
782
+ /* 4. Compact terminal list (Overview, Architect, shells) */
704
783
  .port-item {
705
- flex-direction: column;
706
- align-items: flex-start;
784
+ flex-direction: row;
785
+ align-items: center;
786
+ padding: 8px 12px;
707
787
  gap: 8px;
708
788
  }
709
789
 
710
790
  .port-actions {
711
- width: 100%;
791
+ width: auto;
712
792
  }
713
793
 
714
794
  .port-actions a {
715
- min-height: 44px;
716
795
  display: flex;
717
796
  align-items: center;
718
797
  justify-content: center;
719
- flex: 1;
798
+ padding: 6px 12px;
799
+ flex: 0;
800
+ /* min-height handled by @media (pointer: coarse) at 44px */
801
+ }
802
+
803
+ .instance-body {
804
+ padding: 12px;
805
+ }
806
+
807
+ /* 5. Compact New Shell row */
808
+ .new-shell-row {
809
+ margin-top: 4px;
810
+ padding-top: 4px;
811
+ }
812
+
813
+ /* 6. Compact Recent Projects */
814
+ .recent-item {
815
+ flex-direction: row;
816
+ align-items: center;
817
+ padding: 12px 16px;
818
+ gap: 8px;
819
+ }
820
+
821
+ .recent-path {
822
+ display: none;
823
+ }
824
+
825
+ .recent-time {
826
+ font-size: 12px;
827
+ }
828
+
829
+ /* 7. Reduce section spacing (continued) */
830
+ .recents-section {
831
+ margin-top: 16px;
832
+ padding-top: 16px;
833
+ }
834
+
835
+ .instance-meta {
836
+ margin-top: 8px;
837
+ padding-top: 8px;
720
838
  }
721
839
 
722
840
  .launch-section {
@@ -736,13 +854,6 @@
736
854
  min-height: 44px;
737
855
  }
738
856
 
739
- .recent-item {
740
- flex-direction: column;
741
- align-items: flex-start;
742
- gap: 12px;
743
- padding: 16px;
744
- }
745
-
746
857
  .dialog-box {
747
858
  min-width: auto;
748
859
  width: calc(100% - 32px);
@@ -840,6 +951,7 @@
840
951
  Agent Farm Control Tower
841
952
  </h1>
842
953
  <div class="header-actions">
954
+ <span id="cloud-status"></span>
843
955
  <button class="btn" onclick="refresh()">Refresh</button>
844
956
  </div>
845
957
  </header>
@@ -905,7 +1017,7 @@
905
1017
 
906
1018
  // Auth helper: get headers with auth token if available
907
1019
  function getAuthHeaders() {
908
- const key = localStorage.getItem('codev_web_key');
1020
+ const key = localStorage.getItem('codev-web-key');
909
1021
  return key ? { 'Authorization': `Bearer ${key}` } : {};
910
1022
  }
911
1023
 
@@ -916,7 +1028,7 @@
916
1028
 
917
1029
  // If 401, clear key and redirect to login
918
1030
  if (response.status === 401) {
919
- localStorage.removeItem('codev_web_key');
1031
+ localStorage.removeItem('codev-web-key');
920
1032
  location.reload();
921
1033
  throw new Error('Unauthorized');
922
1034
  }
@@ -925,8 +1037,8 @@
925
1037
 
926
1038
  // Logout function
927
1039
  function logout() {
928
- localStorage.removeItem('codev_web_key');
929
- window.location.href = '/';
1040
+ localStorage.removeItem('codev-web-key');
1041
+ window.location.href = './';
930
1042
  }
931
1043
 
932
1044
  // Initialize
@@ -961,7 +1073,7 @@
961
1073
  sseController = new AbortController();
962
1074
 
963
1075
  try {
964
- const response = await fetch('/api/events', {
1076
+ const response = await fetch('./api/events', {
965
1077
  headers: getAuthHeaders(),
966
1078
  signal: sseController.signal,
967
1079
  });
@@ -1038,13 +1150,18 @@
1038
1150
  // Refresh data from API
1039
1151
  async function refresh() {
1040
1152
  try {
1041
- const response = await authFetch('/api/status');
1042
- if (!response.ok) throw new Error('Failed to fetch status');
1153
+ const [statusResponse, cloudStatus] = await Promise.all([
1154
+ authFetch('./api/status'),
1155
+ fetchCloudStatus(),
1156
+ ]);
1043
1157
 
1044
- const data = await response.json();
1158
+ if (!statusResponse.ok) throw new Error('Failed to fetch status');
1159
+
1160
+ const data = await statusResponse.json();
1045
1161
  runningInstances = (data.instances || []).filter(i => i.running);
1046
1162
  recentInstances = (data.instances || []).filter(i => !i.running);
1047
1163
  render();
1164
+ renderCloudStatus(cloudStatus);
1048
1165
  } catch (err) {
1049
1166
  console.error('Refresh error:', err);
1050
1167
  showToast('Failed to refresh: ' + err.message, 'error');
@@ -1111,7 +1228,7 @@
1111
1228
  <span class="port-type">Overview</span>
1112
1229
  </div>
1113
1230
  <div class="port-actions">
1114
- <a href="${escapeHtml(instance.proxyUrl)}" target="_blank">Open</a>
1231
+ <a href="${escapeHtml(relUrl(instance.proxyUrl))}" target="_blank">Open</a>
1115
1232
  </div>
1116
1233
  </div>
1117
1234
  `;
@@ -1125,7 +1242,7 @@
1125
1242
  <span class="port-type">${escapeHtml(terminal.label)}</span>
1126
1243
  </div>
1127
1244
  <div class="port-actions">
1128
- <a href="${escapeHtml(terminal.url)}&fullscreen=1" target="_blank">Open</a>
1245
+ <a href="${escapeHtml(relUrl(terminal.url))}&fullscreen=1" target="_blank">Open</a>
1129
1246
  </div>
1130
1247
  </div>
1131
1248
  `).join('');
@@ -1133,7 +1250,7 @@
1133
1250
 
1134
1251
  // Add "New Shell" button for this instance
1135
1252
  terminalsHtml += `
1136
- <div class="port-item" style="border-top: 1px dashed var(--border); margin-top: 8px; padding-top: 8px;">
1253
+ <div class="port-item new-shell-row" style="border-top: 1px dashed var(--border);">
1137
1254
  <div class="port-info">
1138
1255
  <span class="port-status" style="background: var(--accent);"></span>
1139
1256
  <span class="port-type">New Shell</span>
@@ -1279,7 +1396,7 @@
1279
1396
  }
1280
1397
 
1281
1398
  try {
1282
- const response = await authFetch('/api/browse?path=' + encodeURIComponent(inputPath));
1399
+ const response = await authFetch('./api/browse?path=' + encodeURIComponent(inputPath));
1283
1400
  const data = await response.json();
1284
1401
  suggestions = data.suggestions || [];
1285
1402
  selectedIndex = -1;
@@ -1334,7 +1451,7 @@
1334
1451
  // Launch a specific path (from recents)
1335
1452
  async function launchPath(projectPath) {
1336
1453
  try {
1337
- const response = await authFetch('/api/launch', {
1454
+ const response = await authFetch('./api/launch', {
1338
1455
  method: 'POST',
1339
1456
  headers: { 'Content-Type': 'application/json' },
1340
1457
  body: JSON.stringify({ projectPath })
@@ -1360,7 +1477,7 @@
1360
1477
  // Stop an instance by project path
1361
1478
  async function stopInstance(projectPath) {
1362
1479
  try {
1363
- const response = await authFetch('/api/stop', {
1480
+ const response = await authFetch('./api/stop', {
1364
1481
  method: 'POST',
1365
1482
  headers: { 'Content-Type': 'application/json' },
1366
1483
  body: JSON.stringify({ projectPath })
@@ -1383,7 +1500,7 @@
1383
1500
  async function restartInstance(projectPath) {
1384
1501
  try {
1385
1502
  // First stop
1386
- await authFetch('/api/stop', {
1503
+ await authFetch('./api/stop', {
1387
1504
  method: 'POST',
1388
1505
  headers: { 'Content-Type': 'application/json' },
1389
1506
  body: JSON.stringify({ projectPath })
@@ -1393,7 +1510,7 @@
1393
1510
  await new Promise(r => setTimeout(r, 1000));
1394
1511
 
1395
1512
  // Then start
1396
- const response = await authFetch('/api/launch', {
1513
+ const response = await authFetch('./api/launch', {
1397
1514
  method: 'POST',
1398
1515
  headers: { 'Content-Type': 'application/json' },
1399
1516
  body: JSON.stringify({ projectPath })
@@ -1423,7 +1540,7 @@
1423
1540
  }
1424
1541
 
1425
1542
  try {
1426
- const response = await authFetch('/api/launch', {
1543
+ const response = await authFetch('./api/launch', {
1427
1544
  method: 'POST',
1428
1545
  headers: { 'Content-Type': 'application/json' },
1429
1546
  body: JSON.stringify({ projectPath })
@@ -1454,7 +1571,7 @@
1454
1571
  try {
1455
1572
  // Use tower proxy to route to the project's dashboard API
1456
1573
  const encodedPath = toBase64URL(projectPath);
1457
- const response = await authFetch(`/project/${encodedPath}/api/tabs/shell`, {
1574
+ const response = await authFetch(`./project/${encodedPath}/api/tabs/shell`, {
1458
1575
  method: 'POST',
1459
1576
  headers: { 'Content-Type': 'application/json' },
1460
1577
  });
@@ -1514,6 +1631,13 @@
1514
1631
  }
1515
1632
  }
1516
1633
 
1634
+ // Convert absolute paths to relative so links work behind reverse proxies.
1635
+ // E.g. "/project/abc/" → "./project/abc/"
1636
+ function relUrl(path) {
1637
+ if (path && path.startsWith('/')) return '.' + path;
1638
+ return path || '';
1639
+ }
1640
+
1517
1641
  // HTML escape
1518
1642
  function escapeHtml(str) {
1519
1643
  if (!str) return '';
@@ -1539,7 +1663,7 @@
1539
1663
  // The React dashboard handles tab selection internally
1540
1664
  function getProxyUrl(instance, portType) {
1541
1665
  const encodedPath = toBase64URL(instance.projectPath);
1542
- return `/project/${encodedPath}/`;
1666
+ return `./project/${encodedPath}/`;
1543
1667
  }
1544
1668
 
1545
1669
  // Toast notifications
@@ -1591,7 +1715,7 @@
1591
1715
  hideCreateProjectDialog();
1592
1716
 
1593
1717
  try {
1594
- const response = await authFetch('/api/create', {
1718
+ const response = await authFetch('./api/create', {
1595
1719
  method: 'POST',
1596
1720
  headers: { 'Content-Type': 'application/json' },
1597
1721
  body: JSON.stringify({ parent, name })
@@ -1624,6 +1748,116 @@
1624
1748
  }
1625
1749
  });
1626
1750
 
1751
+ // Cloud status
1752
+ let cloudLoading = false;
1753
+
1754
+ async function fetchCloudStatus() {
1755
+ try {
1756
+ const res = await authFetch('./api/tunnel/status');
1757
+ if (res.status === 404) return null;
1758
+ if (!res.ok) return { state: 'error' };
1759
+ return await res.json();
1760
+ } catch {
1761
+ return null;
1762
+ }
1763
+ }
1764
+
1765
+ function formatUptime(ms) {
1766
+ const s = Math.floor(ms / 1000);
1767
+ if (s < 60) return s + 's';
1768
+ if (s < 3600) return Math.floor(s / 60) + 'm ' + (s % 60) + 's';
1769
+ const h = Math.floor(s / 3600);
1770
+ const m = Math.floor((s % 3600) / 60);
1771
+ return h + 'h ' + m + 'm';
1772
+ }
1773
+
1774
+ function renderCloudStatus(status) {
1775
+ const el = document.getElementById('cloud-status');
1776
+ if (!status || status.state === 'error') {
1777
+ el.innerHTML = `
1778
+ <span class="cloud-status">
1779
+ <span class="cloud-dot cloud-dot--gray"></span>
1780
+ Cloud: not registered
1781
+ </span>`;
1782
+ return;
1783
+ }
1784
+
1785
+ if (!status.registered) {
1786
+ el.innerHTML = `
1787
+ <span class="cloud-status">
1788
+ <span class="cloud-dot cloud-dot--gray"></span>
1789
+ Cloud: not registered
1790
+ </span>`;
1791
+ return;
1792
+ }
1793
+
1794
+ if (status.state === 'auth_failed') {
1795
+ el.innerHTML = `
1796
+ <span class="cloud-status">
1797
+ <span class="cloud-dot cloud-dot--red"></span>
1798
+ Cloud: auth failed
1799
+ </span>`;
1800
+ return;
1801
+ }
1802
+
1803
+ if (status.state === 'connecting') {
1804
+ el.innerHTML = `
1805
+ <span class="cloud-status">
1806
+ <span class="cloud-dot cloud-dot--yellow"></span>
1807
+ Cloud: connecting...
1808
+ </span>`;
1809
+ return;
1810
+ }
1811
+
1812
+ if (status.state === 'connected') {
1813
+ const uptimeStr = status.uptime != null ? ' <span class="cloud-uptime">' + formatUptime(status.uptime) + '</span>' : '';
1814
+ const openLink = status.accessUrl ? ' <a href="' + escapeHtml(status.accessUrl) + '" target="_blank" rel="noopener" class="cloud-link">Open</a>' : '';
1815
+ el.innerHTML = `
1816
+ <span class="cloud-status">
1817
+ <span class="cloud-dot cloud-dot--green"></span>
1818
+ Cloud: ${escapeHtml(status.towerName || 'connected')}
1819
+ ${uptimeStr}
1820
+ ${openLink}
1821
+ <button class="cloud-btn" onclick="cloudDisconnect()" ${cloudLoading ? 'disabled' : ''}>Disconnect</button>
1822
+ </span>`;
1823
+ return;
1824
+ }
1825
+
1826
+ // Disconnected
1827
+ el.innerHTML = `
1828
+ <span class="cloud-status">
1829
+ <span class="cloud-dot cloud-dot--gray"></span>
1830
+ Cloud: disconnected
1831
+ <button class="cloud-btn" onclick="cloudConnect()" ${cloudLoading ? 'disabled' : ''}>Connect</button>
1832
+ </span>`;
1833
+ }
1834
+
1835
+ async function cloudConnect() {
1836
+ cloudLoading = true;
1837
+ renderCloudStatus({ registered: true, state: 'connecting' });
1838
+ try {
1839
+ await authFetch('./api/tunnel/connect', { method: 'POST' });
1840
+ showToast('Connecting to cloud...', 'success');
1841
+ } catch (err) {
1842
+ showToast('Connect failed: ' + err.message, 'error');
1843
+ }
1844
+ cloudLoading = false;
1845
+ // Refresh will pick up new status
1846
+ setTimeout(refresh, 2000);
1847
+ }
1848
+
1849
+ async function cloudDisconnect() {
1850
+ cloudLoading = true;
1851
+ try {
1852
+ await authFetch('./api/tunnel/disconnect', { method: 'POST' });
1853
+ showToast('Disconnected from cloud', 'success');
1854
+ } catch (err) {
1855
+ showToast('Disconnect failed: ' + err.message, 'error');
1856
+ }
1857
+ cloudLoading = false;
1858
+ setTimeout(refresh, 1000);
1859
+ }
1860
+
1627
1861
  // Initialize
1628
1862
  init();
1629
1863
  </script>