@cluesmith/codev 1.2.3 → 1.3.0

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 (31) hide show
  1. package/bin/generate-image.js +7 -0
  2. package/dist/agent-farm/cli.d.ts.map +1 -1
  3. package/dist/agent-farm/cli.js +0 -36
  4. package/dist/agent-farm/cli.js.map +1 -1
  5. package/dist/agent-farm/commands/start.js +32 -0
  6. package/dist/agent-farm/commands/start.js.map +1 -1
  7. package/dist/agent-farm/servers/dashboard-server.d.ts +0 -2
  8. package/dist/agent-farm/servers/dashboard-server.d.ts.map +1 -1
  9. package/dist/agent-farm/servers/dashboard-server.js +89 -4
  10. package/dist/agent-farm/servers/dashboard-server.js.map +1 -1
  11. package/dist/agent-farm/servers/open-server.d.ts +0 -2
  12. package/dist/agent-farm/servers/open-server.d.ts.map +1 -1
  13. package/dist/agent-farm/servers/open-server.js +12 -7
  14. package/dist/agent-farm/servers/open-server.js.map +1 -1
  15. package/dist/agent-farm/servers/tower-server.d.ts +0 -2
  16. package/dist/agent-farm/servers/tower-server.d.ts.map +1 -1
  17. package/dist/agent-farm/servers/tower-server.js +16 -4
  18. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  19. package/dist/agent-farm/utils/shell.d.ts +2 -1
  20. package/dist/agent-farm/utils/shell.d.ts.map +1 -1
  21. package/dist/agent-farm/utils/shell.js +35 -2
  22. package/dist/agent-farm/utils/shell.js.map +1 -1
  23. package/dist/cli.d.ts.map +1 -1
  24. package/dist/cli.js +24 -0
  25. package/dist/cli.js.map +1 -1
  26. package/dist/commands/generate-image.d.ts +13 -0
  27. package/dist/commands/generate-image.d.ts.map +1 -0
  28. package/dist/commands/generate-image.js +155 -0
  29. package/dist/commands/generate-image.js.map +1 -0
  30. package/package.json +5 -3
  31. package/templates/dashboard-split.html +378 -3
@@ -1156,6 +1156,138 @@
1156
1156
  .tab.tab-uncloseable .close {
1157
1157
  display: none;
1158
1158
  }
1159
+
1160
+ /* Files Tab Styles (Spec 0055) */
1161
+ .files-container {
1162
+ flex: 1;
1163
+ overflow-y: auto;
1164
+ display: flex;
1165
+ flex-direction: column;
1166
+ }
1167
+
1168
+ .files-header {
1169
+ display: flex;
1170
+ justify-content: space-between;
1171
+ align-items: center;
1172
+ padding: 8px 12px;
1173
+ background: var(--bg-secondary);
1174
+ border-bottom: 1px solid var(--border);
1175
+ }
1176
+
1177
+ .files-header-title {
1178
+ font-size: 12px;
1179
+ color: var(--text-muted);
1180
+ text-transform: uppercase;
1181
+ letter-spacing: 0.5px;
1182
+ }
1183
+
1184
+ .files-header-actions {
1185
+ display: flex;
1186
+ gap: 4px;
1187
+ }
1188
+
1189
+ .files-header-actions button {
1190
+ padding: 4px 8px;
1191
+ border-radius: 4px;
1192
+ border: 1px solid var(--border);
1193
+ background: var(--bg-tertiary);
1194
+ color: var(--text-secondary);
1195
+ cursor: pointer;
1196
+ font-size: 11px;
1197
+ }
1198
+
1199
+ .files-header-actions button:hover {
1200
+ background: var(--tab-hover);
1201
+ color: var(--text-primary);
1202
+ }
1203
+
1204
+ .files-tree {
1205
+ flex: 1;
1206
+ overflow-y: auto;
1207
+ padding: 8px 0;
1208
+ }
1209
+
1210
+ .tree-item {
1211
+ display: flex;
1212
+ align-items: center;
1213
+ padding: 4px 8px;
1214
+ cursor: pointer;
1215
+ user-select: none;
1216
+ }
1217
+
1218
+ .tree-item:hover {
1219
+ background: var(--bg-secondary);
1220
+ }
1221
+
1222
+ .tree-item.selected {
1223
+ background: var(--tab-active);
1224
+ }
1225
+
1226
+ .tree-item-icon {
1227
+ width: 16px;
1228
+ height: 16px;
1229
+ margin-right: 4px;
1230
+ display: flex;
1231
+ align-items: center;
1232
+ justify-content: center;
1233
+ font-size: 10px;
1234
+ color: var(--text-muted);
1235
+ }
1236
+
1237
+ .tree-item-icon.folder-toggle {
1238
+ cursor: pointer;
1239
+ }
1240
+
1241
+ .tree-item-icon.folder-toggle:hover {
1242
+ color: var(--text-secondary);
1243
+ }
1244
+
1245
+ .tree-item-name {
1246
+ font-size: 13px;
1247
+ color: var(--text-secondary);
1248
+ overflow: hidden;
1249
+ text-overflow: ellipsis;
1250
+ white-space: nowrap;
1251
+ }
1252
+
1253
+ .tree-item:hover .tree-item-name {
1254
+ color: var(--text-primary);
1255
+ }
1256
+
1257
+ .tree-item[data-type="dir"] .tree-item-name {
1258
+ color: var(--text-primary);
1259
+ }
1260
+
1261
+ .tree-item[data-type="file"]:hover .tree-item-name {
1262
+ color: var(--accent);
1263
+ }
1264
+
1265
+ .tree-children {
1266
+ overflow: hidden;
1267
+ }
1268
+
1269
+ .tree-children.collapsed {
1270
+ display: none;
1271
+ }
1272
+
1273
+ .files-loading {
1274
+ display: flex;
1275
+ align-items: center;
1276
+ justify-content: center;
1277
+ padding: 24px;
1278
+ color: var(--text-muted);
1279
+ font-size: 13px;
1280
+ }
1281
+
1282
+ .files-error {
1283
+ padding: 16px;
1284
+ margin: 8px;
1285
+ background: rgba(239, 68, 68, 0.1);
1286
+ border: 1px solid var(--status-error);
1287
+ border-radius: 6px;
1288
+ color: var(--text-secondary);
1289
+ font-size: 13px;
1290
+ }
1159
1291
  </style>
1160
1292
  </head>
1161
1293
  <body>
@@ -1183,7 +1315,6 @@
1183
1315
  </button>
1184
1316
  <div class="overflow-menu hidden" id="overflow-menu" role="menu"></div>
1185
1317
  <div class="add-buttons">
1186
- <button class="add-btn" onclick="showFileDialog()" title="Open file">+ 📄</button>
1187
1318
  <button class="add-btn" onclick="spawnBuilder()" title="Spawn worktree builder">+ 🔨</button>
1188
1319
  <button class="add-btn" onclick="spawnShell()" title="New shell">+ >_</button>
1189
1320
  </div>
@@ -1374,6 +1505,12 @@
1374
1505
  let projectlistError = null;
1375
1506
  let projectlistDebounce = null;
1376
1507
 
1508
+ // Files tab state (Spec 0055)
1509
+ let filesTreeData = [];
1510
+ let filesTreeExpanded = new Set(); // Set of expanded folder paths
1511
+ let filesTreeError = null;
1512
+ let filesTreeLoaded = false;
1513
+
1377
1514
  // Build tabs from initial state
1378
1515
  function buildTabsFromState() {
1379
1516
  const previousTabIds = new Set(tabs.map(t => t.id));
@@ -1387,6 +1524,14 @@
1387
1524
  closeable: false
1388
1525
  });
1389
1526
 
1527
+ // Files tab is second and uncloseable (Spec 0055)
1528
+ tabs.push({
1529
+ id: 'files',
1530
+ type: 'files',
1531
+ name: 'Files',
1532
+ closeable: false
1533
+ });
1534
+
1390
1535
  // Add file tabs from annotations
1391
1536
  for (const annotation of state.annotations || []) {
1392
1537
  tabs.push({
@@ -1424,7 +1569,7 @@
1424
1569
 
1425
1570
  // Detect new tabs and auto-switch to them (skip projects tab)
1426
1571
  for (const tab of tabs) {
1427
- if (tab.id !== 'projects' && !knownTabIds.has(tab.id) && previousTabIds.size > 0) {
1572
+ if (tab.id !== 'projects' && tab.id !== 'files' && !knownTabIds.has(tab.id) && previousTabIds.size > 0) {
1428
1573
  // This is a new tab - switch to it
1429
1574
  activeTabId = tab.id;
1430
1575
  break;
@@ -1519,6 +1664,7 @@
1519
1664
  function getTabIcon(type) {
1520
1665
  switch (type) {
1521
1666
  case 'projects': return '📋';
1667
+ case 'files': return '📁';
1522
1668
  case 'file': return '📄';
1523
1669
  case 'builder': return '🔨';
1524
1670
  case 'shell': return '>_';
@@ -1626,6 +1772,16 @@
1626
1772
  return;
1627
1773
  }
1628
1774
 
1775
+ // Handle files tab specially (no iframe, inline content)
1776
+ if (tab.type === 'files') {
1777
+ if (currentTabType !== 'files') {
1778
+ currentTabType = 'files';
1779
+ currentTabPort = null;
1780
+ renderFilesTab();
1781
+ }
1782
+ return;
1783
+ }
1784
+
1629
1785
  // For other tabs, only update iframe if port changed (avoid flashing on poll)
1630
1786
  if (currentTabPort !== tab.port || currentTabType !== tab.type) {
1631
1787
  currentTabPort = tab.port;
@@ -2810,6 +2966,225 @@
2810
2966
  `;
2811
2967
  }
2812
2968
 
2969
+ // ========================================
2970
+ // Files Tab Functions (Spec 0055)
2971
+ // ========================================
2972
+
2973
+ // Load the file tree from the API
2974
+ async function loadFilesTree() {
2975
+ try {
2976
+ const response = await fetch('/api/files');
2977
+ if (!response.ok) {
2978
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
2979
+ }
2980
+ filesTreeData = await response.json();
2981
+ filesTreeError = null;
2982
+ filesTreeLoaded = true;
2983
+ } catch (err) {
2984
+ console.error('Failed to load files tree:', err);
2985
+ filesTreeError = 'Could not load file tree: ' + err.message;
2986
+ filesTreeData = [];
2987
+ }
2988
+ }
2989
+
2990
+ // Render the files tab (entry point)
2991
+ // Only fetches on first load; use refreshFilesTree() to force reload
2992
+ async function renderFilesTab() {
2993
+ const content = document.getElementById('tab-content');
2994
+
2995
+ // If already loaded, just render cached data (no network request)
2996
+ if (filesTreeLoaded) {
2997
+ renderFilesTabContent();
2998
+ return;
2999
+ }
3000
+
3001
+ // First load - show loading state and fetch
3002
+ content.innerHTML = '<div class="files-loading">Loading files...</div>';
3003
+ await loadFilesTree();
3004
+ renderFilesTabContent();
3005
+ }
3006
+
3007
+ // Render the files tab content (internal - called after data is loaded)
3008
+ function renderFilesTabContent() {
3009
+ const content = document.getElementById('tab-content');
3010
+
3011
+ if (filesTreeError) {
3012
+ content.innerHTML = `
3013
+ <div class="files-container">
3014
+ <div class="files-error">${escapeHtml(filesTreeError)}</div>
3015
+ </div>
3016
+ `;
3017
+ return;
3018
+ }
3019
+
3020
+ content.innerHTML = `
3021
+ <div class="files-container">
3022
+ <div class="files-header">
3023
+ <span class="files-header-title">Explorer</span>
3024
+ <div class="files-header-actions">
3025
+ <button onclick="collapseAllFolders()" title="Collapse All">⊟</button>
3026
+ <button onclick="expandAllFolders()" title="Expand All">⊞</button>
3027
+ <button onclick="refreshFilesTree()" title="Refresh">↻</button>
3028
+ </div>
3029
+ </div>
3030
+ <div class="files-tree" id="files-tree">
3031
+ ${renderTreeNodes(filesTreeData, 0)}
3032
+ </div>
3033
+ </div>
3034
+ `;
3035
+ }
3036
+
3037
+ // Escape a string for use inside a JavaScript string literal in onclick handlers
3038
+ // This handles quotes, backslashes, and other special characters
3039
+ function escapeJsString(str) {
3040
+ return str
3041
+ .replace(/\\/g, '\\\\')
3042
+ .replace(/'/g, "\\'")
3043
+ .replace(/"/g, '\\"')
3044
+ .replace(/\n/g, '\\n')
3045
+ .replace(/\r/g, '\\r');
3046
+ }
3047
+
3048
+ // Render tree nodes recursively
3049
+ function renderTreeNodes(nodes, depth) {
3050
+ if (!nodes || nodes.length === 0) return '';
3051
+
3052
+ return nodes.map(node => {
3053
+ const indent = depth * 16;
3054
+ const isExpanded = filesTreeExpanded.has(node.path);
3055
+ // Use escapeJsString for onclick handlers (handles quotes correctly)
3056
+ // Use escapeHtml for data attributes and display text (handles XSS)
3057
+ const jsPath = escapeJsString(node.path);
3058
+
3059
+ if (node.type === 'dir') {
3060
+ const icon = isExpanded ? '▼' : '▶';
3061
+ const childrenHtml = node.children && node.children.length > 0
3062
+ ? `<div class="tree-children ${isExpanded ? '' : 'collapsed'}" data-path="${escapeHtml(node.path)}">${renderTreeNodes(node.children, depth + 1)}</div>`
3063
+ : '';
3064
+
3065
+ return `
3066
+ <div class="tree-item" data-type="dir" data-path="${escapeHtml(node.path)}" style="padding-left: ${indent + 8}px;" onclick="toggleFolder('${jsPath}')">
3067
+ <span class="tree-item-icon folder-toggle">${icon}</span>
3068
+ <span class="tree-item-name">${escapeHtml(node.name)}</span>
3069
+ </div>
3070
+ ${childrenHtml}
3071
+ `;
3072
+ } else {
3073
+ return `
3074
+ <div class="tree-item" data-type="file" data-path="${escapeHtml(node.path)}" style="padding-left: ${indent + 8}px;" onclick="openFileFromTree('${jsPath}')">
3075
+ <span class="tree-item-icon">${getFileIcon(node.name)}</span>
3076
+ <span class="tree-item-name">${escapeHtml(node.name)}</span>
3077
+ </div>
3078
+ `;
3079
+ }
3080
+ }).join('');
3081
+ }
3082
+
3083
+ // Get file icon based on extension
3084
+ function getFileIcon(filename) {
3085
+ const ext = filename.split('.').pop().toLowerCase();
3086
+ const iconMap = {
3087
+ 'js': '📜',
3088
+ 'ts': '📜',
3089
+ 'jsx': '⚛️',
3090
+ 'tsx': '⚛️',
3091
+ 'json': '{}',
3092
+ 'md': '📝',
3093
+ 'html': '🌐',
3094
+ 'css': '🎨',
3095
+ 'py': '🐍',
3096
+ 'sh': '⚙️',
3097
+ 'bash': '⚙️',
3098
+ 'yml': '⚙️',
3099
+ 'yaml': '⚙️',
3100
+ 'png': '🖼️',
3101
+ 'jpg': '🖼️',
3102
+ 'jpeg': '🖼️',
3103
+ 'gif': '🖼️',
3104
+ 'svg': '🖼️',
3105
+ };
3106
+ return iconMap[ext] || '📄';
3107
+ }
3108
+
3109
+ // Toggle folder expanded/collapsed state
3110
+ function toggleFolder(path) {
3111
+ if (filesTreeExpanded.has(path)) {
3112
+ filesTreeExpanded.delete(path);
3113
+ } else {
3114
+ filesTreeExpanded.add(path);
3115
+ }
3116
+ renderFilesTabContent();
3117
+ }
3118
+
3119
+ // Collapse all folders
3120
+ function collapseAllFolders() {
3121
+ filesTreeExpanded.clear();
3122
+ renderFilesTabContent();
3123
+ }
3124
+
3125
+ // Expand all folders
3126
+ function expandAllFolders() {
3127
+ function collectPaths(nodes) {
3128
+ for (const node of nodes) {
3129
+ if (node.type === 'dir') {
3130
+ filesTreeExpanded.add(node.path);
3131
+ if (node.children) {
3132
+ collectPaths(node.children);
3133
+ }
3134
+ }
3135
+ }
3136
+ }
3137
+ collectPaths(filesTreeData);
3138
+ renderFilesTabContent();
3139
+ }
3140
+
3141
+ // Refresh files tree
3142
+ async function refreshFilesTree() {
3143
+ await loadFilesTree();
3144
+ renderFilesTabContent();
3145
+ showToast('Files refreshed', 'success');
3146
+ }
3147
+
3148
+ // Open file from tree click
3149
+ async function openFileFromTree(filePath) {
3150
+ try {
3151
+ // Check if file is already open
3152
+ const existingTab = tabs.find(t => t.type === 'file' && t.path === filePath);
3153
+ if (existingTab) {
3154
+ selectTab(existingTab.id);
3155
+ return;
3156
+ }
3157
+
3158
+ // Open the file via API
3159
+ const response = await fetch('/api/tabs/file', {
3160
+ method: 'POST',
3161
+ headers: { 'Content-Type': 'application/json' },
3162
+ body: JSON.stringify({ path: filePath })
3163
+ });
3164
+
3165
+ if (!response.ok) {
3166
+ throw new Error(await response.text());
3167
+ }
3168
+
3169
+ // Refresh state and switch to the new tab
3170
+ await refresh();
3171
+
3172
+ // Find and select the new file tab
3173
+ const newTab = tabs.find(t => t.type === 'file' && t.path === filePath);
3174
+ if (newTab) {
3175
+ selectTab(newTab.id);
3176
+ }
3177
+
3178
+ showToast(`Opened ${getFileName(filePath)}`, 'success');
3179
+ } catch (err) {
3180
+ showToast('Failed to open file: ' + err.message, 'error');
3181
+ }
3182
+ }
3183
+
3184
+ // ========================================
3185
+ // Projects Tab Functions (Spec 0045)
3186
+ // ========================================
3187
+
2813
3188
  // Render the info header with helpful links
2814
3189
  function renderInfoHeader() {
2815
3190
  return `
@@ -2817,7 +3192,7 @@
2817
3192
  <h1 style="font-size: 20px; margin-bottom: 12px; color: var(--text-primary);">Codev: Project View</h1>
2818
3193
  <p>This shows the state of all projects. Our goal is to move each project through all the stages until it reaches INTGR'D (integrated). Hover over column headers to learn about each stage.</p>
2819
3194
  <p>To add projects, update status, or approve stages, use the <strong>Architect</strong> terminal on the left.</p>
2820
- <p>Docs: <a href="#" onclick="openProjectFile('codev/docs/lifecycle.md'); return false;">Lifecycle</a> · <a href="#" onclick="openProjectFile('codev/docs/commands/overview.md'); return false;">CLI Reference</a> · <a href="#" onclick="openProjectFile('codev/protocols/spider/protocol.md'); return false;">SPIDER Protocol</a> · <a href="https://github.com/cluesmith/codev#readme" target="_blank">README</a></p>
3195
+ <p>Docs: <a href="#" onclick="openProjectFile('codev/resources/cheatsheet.md'); return false;">Cheatsheet</a> · <a href="#" onclick="openProjectFile('codev/docs/lifecycle.md'); return false;">Lifecycle</a> · <a href="#" onclick="openProjectFile('codev/docs/commands/overview.md'); return false;">CLI Reference</a> · <a href="#" onclick="openProjectFile('codev/protocols/spider/protocol.md'); return false;">SPIDER Protocol</a> · <a href="https://github.com/cluesmith/codev#readme" target="_blank">README</a></p>
2821
3196
  </div>
2822
3197
  `;
2823
3198
  }