@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.
- package/bin/af.js +0 -0
- package/bin/codev.js +0 -0
- package/bin/consult.js +0 -0
- package/bin/generate-image.js +0 -0
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +2 -1
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/cleanup.js +12 -51
- package/dist/agent-farm/commands/cleanup.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +13 -1
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/servers/dashboard-server.js +107 -5
- package/dist/agent-farm/servers/dashboard-server.js.map +1 -1
- package/dist/agent-farm/types.d.ts +1 -0
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -17
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +27 -2
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +23 -7
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +51 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +23 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +23 -0
- package/dist/version.js.map +1 -0
- package/package.json +1 -1
- package/skeleton/DEPENDENCIES.md +3 -3
- package/skeleton/protocols/maintain/protocol.md +2 -2
- package/skeleton/{docs → resources}/commands/codev.md +0 -39
- package/skeleton/{docs → resources}/commands/consult.md +12 -2
- package/skeleton/{docs → resources}/commands/overview.md +0 -1
- package/skeleton/roles/architect.md +22 -0
- package/skeleton/roles/builder.md +22 -0
- package/skeleton/templates/arch.md +56 -0
- package/skeleton/templates/pr-overview.md +73 -0
- package/templates/dashboard-split.html +526 -164
- package/templates/open.html +285 -2
- package/templates/tower.html +71 -12
- package/dist/agent-farm/index.d.ts +0 -7
- package/dist/agent-farm/index.d.ts.map +0 -1
- package/dist/agent-farm/index.js +0 -373
- package/dist/agent-farm/index.js.map +0 -1
- package/skeleton/bin/agent-farm +0 -7
- package/skeleton/bin/codev-doctor +0 -335
- package/skeleton/resources/lessons-learned.md +0 -30
- /package/skeleton/{roles/review-types → consult-types}/impl-review.md +0 -0
- /package/skeleton/{roles/review-types → consult-types}/integration-review.md +0 -0
- /package/skeleton/{roles/review-types → consult-types}/plan-review.md +0 -0
- /package/skeleton/{roles/review-types → consult-types}/pr-ready.md +0 -0
- /package/skeleton/{roles/review-types → consult-types}/spec-review.md +0 -0
- /package/skeleton/{docs → resources}/commands/agent-farm.md +0 -0
- /package/skeleton/{AGENTS.md.template → templates/AGENTS.md} +0 -0
- /package/skeleton/{CLAUDE.md.template → templates/CLAUDE.md} +0 -0
|
@@ -1157,36 +1157,174 @@
|
|
|
1157
1157
|
display: none;
|
|
1158
1158
|
}
|
|
1159
1159
|
|
|
1160
|
-
/*
|
|
1161
|
-
.
|
|
1160
|
+
/* Tree Styles (used by dashboard file browser) */
|
|
1161
|
+
.tree-item {
|
|
1162
|
+
display: flex;
|
|
1163
|
+
align-items: center;
|
|
1164
|
+
padding: 4px 8px;
|
|
1165
|
+
cursor: pointer;
|
|
1166
|
+
user-select: none;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
.tree-item:hover {
|
|
1170
|
+
background: var(--bg-secondary);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
.tree-item.selected {
|
|
1174
|
+
background: var(--tab-active);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
.tree-item-icon {
|
|
1178
|
+
width: 16px;
|
|
1179
|
+
height: 16px;
|
|
1180
|
+
margin-right: 4px;
|
|
1181
|
+
display: flex;
|
|
1182
|
+
align-items: center;
|
|
1183
|
+
justify-content: center;
|
|
1184
|
+
font-size: 10px;
|
|
1185
|
+
color: var(--text-muted);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
.tree-item-icon.folder-toggle {
|
|
1189
|
+
cursor: pointer;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
.tree-item-icon.folder-toggle:hover {
|
|
1193
|
+
color: var(--text-secondary);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
.tree-item-name {
|
|
1197
|
+
font-size: 13px;
|
|
1198
|
+
color: var(--text-secondary);
|
|
1199
|
+
overflow: hidden;
|
|
1200
|
+
text-overflow: ellipsis;
|
|
1201
|
+
white-space: nowrap;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
.tree-item:hover .tree-item-name {
|
|
1205
|
+
color: var(--text-primary);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
.tree-item[data-type="dir"] .tree-item-name {
|
|
1209
|
+
color: var(--text-primary);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
.tree-item[data-type="file"]:hover .tree-item-name {
|
|
1213
|
+
color: var(--accent);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
.tree-children {
|
|
1217
|
+
overflow: hidden;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
.tree-children.collapsed {
|
|
1221
|
+
display: none;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
/* Dashboard Tab Styles (Spec 0057) */
|
|
1225
|
+
.dashboard-container {
|
|
1162
1226
|
flex: 1;
|
|
1163
1227
|
overflow-y: auto;
|
|
1164
1228
|
display: flex;
|
|
1165
1229
|
flex-direction: column;
|
|
1166
1230
|
}
|
|
1167
1231
|
|
|
1168
|
-
.
|
|
1232
|
+
.dashboard-header {
|
|
1233
|
+
display: flex;
|
|
1234
|
+
gap: 16px;
|
|
1235
|
+
padding: 16px;
|
|
1236
|
+
flex-shrink: 0;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
@media (max-width: 900px) {
|
|
1240
|
+
.dashboard-header {
|
|
1241
|
+
flex-direction: column;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/* Collapsible section styles */
|
|
1246
|
+
.dashboard-section {
|
|
1247
|
+
background: var(--bg-secondary);
|
|
1248
|
+
border: 1px solid var(--border);
|
|
1249
|
+
border-radius: 8px;
|
|
1250
|
+
overflow: hidden;
|
|
1251
|
+
display: flex;
|
|
1252
|
+
flex-direction: column;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
.dashboard-section.section-tabs,
|
|
1256
|
+
.dashboard-section.section-files {
|
|
1257
|
+
flex: 1;
|
|
1258
|
+
max-height: 280px;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
.dashboard-section.section-projects {
|
|
1262
|
+
flex: 0 0 auto;
|
|
1263
|
+
margin: 0 16px 16px 16px;
|
|
1264
|
+
max-height: 50%;
|
|
1265
|
+
overflow-y: auto;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
.dashboard-section.section-projects .dashboard-section-content {
|
|
1269
|
+
flex: 0 0 auto;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/* Tabs/Files expand to fill remaining space above Projects */
|
|
1273
|
+
.dashboard-header {
|
|
1274
|
+
flex: 1;
|
|
1275
|
+
min-height: 0;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
.dashboard-section.section-tabs,
|
|
1279
|
+
.dashboard-section.section-files {
|
|
1280
|
+
max-height: none;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
.dashboard-section-header {
|
|
1169
1284
|
display: flex;
|
|
1170
1285
|
justify-content: space-between;
|
|
1171
1286
|
align-items: center;
|
|
1172
1287
|
padding: 8px 12px;
|
|
1173
|
-
|
|
1288
|
+
cursor: pointer;
|
|
1289
|
+
user-select: none;
|
|
1290
|
+
flex-shrink: 0;
|
|
1174
1291
|
border-bottom: 1px solid var(--border);
|
|
1175
1292
|
}
|
|
1176
1293
|
|
|
1177
|
-
.
|
|
1294
|
+
.dashboard-section-header:hover {
|
|
1295
|
+
background: var(--bg-tertiary);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
.dashboard-section-header h3 {
|
|
1178
1299
|
font-size: 12px;
|
|
1179
|
-
color: var(--text-muted);
|
|
1180
1300
|
text-transform: uppercase;
|
|
1301
|
+
color: var(--text-muted);
|
|
1181
1302
|
letter-spacing: 0.5px;
|
|
1303
|
+
margin: 0;
|
|
1304
|
+
display: flex;
|
|
1305
|
+
align-items: center;
|
|
1306
|
+
gap: 6px;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
.dashboard-section-header .collapse-icon {
|
|
1310
|
+
font-size: 10px;
|
|
1311
|
+
transition: transform 0.2s;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
.dashboard-section.collapsed .collapse-icon {
|
|
1315
|
+
transform: rotate(-90deg);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
.dashboard-section.collapsed .dashboard-section-header {
|
|
1319
|
+
border-bottom: none;
|
|
1182
1320
|
}
|
|
1183
1321
|
|
|
1184
|
-
.
|
|
1322
|
+
.dashboard-section-header .header-actions {
|
|
1185
1323
|
display: flex;
|
|
1186
1324
|
gap: 4px;
|
|
1187
1325
|
}
|
|
1188
1326
|
|
|
1189
|
-
.
|
|
1327
|
+
.dashboard-section-header .header-actions button {
|
|
1190
1328
|
padding: 4px 8px;
|
|
1191
1329
|
border-radius: 4px;
|
|
1192
1330
|
border: 1px solid var(--border);
|
|
@@ -1196,97 +1334,184 @@
|
|
|
1196
1334
|
font-size: 11px;
|
|
1197
1335
|
}
|
|
1198
1336
|
|
|
1199
|
-
.
|
|
1337
|
+
.dashboard-section-header .header-actions button:hover {
|
|
1200
1338
|
background: var(--tab-hover);
|
|
1201
1339
|
color: var(--text-primary);
|
|
1202
1340
|
}
|
|
1203
1341
|
|
|
1204
|
-
.
|
|
1342
|
+
.dashboard-section-content {
|
|
1205
1343
|
flex: 1;
|
|
1206
1344
|
overflow-y: auto;
|
|
1207
|
-
padding: 8px
|
|
1345
|
+
padding: 8px 12px;
|
|
1208
1346
|
}
|
|
1209
1347
|
|
|
1210
|
-
.
|
|
1211
|
-
display:
|
|
1212
|
-
align-items: center;
|
|
1213
|
-
padding: 4px 8px;
|
|
1214
|
-
cursor: pointer;
|
|
1215
|
-
user-select: none;
|
|
1348
|
+
.dashboard-section.collapsed .dashboard-section-content {
|
|
1349
|
+
display: none;
|
|
1216
1350
|
}
|
|
1217
1351
|
|
|
1218
|
-
|
|
1352
|
+
/* Legacy support */
|
|
1353
|
+
.dashboard-column {
|
|
1219
1354
|
background: var(--bg-secondary);
|
|
1355
|
+
border: 1px solid var(--border);
|
|
1356
|
+
border-radius: 8px;
|
|
1357
|
+
padding: 12px;
|
|
1358
|
+
overflow: hidden;
|
|
1359
|
+
display: flex;
|
|
1360
|
+
flex-direction: column;
|
|
1361
|
+
max-height: 280px;
|
|
1220
1362
|
}
|
|
1221
1363
|
|
|
1222
|
-
.
|
|
1223
|
-
background: var(--tab-active);
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
.tree-item-icon {
|
|
1227
|
-
width: 16px;
|
|
1228
|
-
height: 16px;
|
|
1229
|
-
margin-right: 4px;
|
|
1364
|
+
.dashboard-column-header {
|
|
1230
1365
|
display: flex;
|
|
1366
|
+
justify-content: space-between;
|
|
1231
1367
|
align-items: center;
|
|
1232
|
-
|
|
1233
|
-
|
|
1368
|
+
margin-bottom: 8px;
|
|
1369
|
+
flex-shrink: 0;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
.dashboard-column-header h3 {
|
|
1373
|
+
font-size: 12px;
|
|
1374
|
+
text-transform: uppercase;
|
|
1234
1375
|
color: var(--text-muted);
|
|
1376
|
+
letter-spacing: 0.5px;
|
|
1377
|
+
margin: 0;
|
|
1235
1378
|
}
|
|
1236
1379
|
|
|
1237
|
-
.
|
|
1238
|
-
|
|
1380
|
+
.dashboard-column-header .header-actions {
|
|
1381
|
+
display: flex;
|
|
1382
|
+
gap: 4px;
|
|
1239
1383
|
}
|
|
1240
1384
|
|
|
1241
|
-
.
|
|
1385
|
+
.dashboard-column-header .header-actions button {
|
|
1386
|
+
padding: 4px 8px;
|
|
1387
|
+
border-radius: 4px;
|
|
1388
|
+
border: 1px solid var(--border);
|
|
1389
|
+
background: var(--bg-tertiary);
|
|
1242
1390
|
color: var(--text-secondary);
|
|
1391
|
+
cursor: pointer;
|
|
1392
|
+
font-size: 11px;
|
|
1243
1393
|
}
|
|
1244
1394
|
|
|
1245
|
-
.
|
|
1395
|
+
.dashboard-column-header .header-actions button:hover {
|
|
1396
|
+
background: var(--tab-hover);
|
|
1397
|
+
color: var(--text-primary);
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
.dashboard-tabs-list {
|
|
1401
|
+
flex: 1;
|
|
1402
|
+
overflow-y: auto;
|
|
1403
|
+
margin-bottom: 8px;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
.dashboard-tab-item {
|
|
1407
|
+
display: flex;
|
|
1408
|
+
align-items: center;
|
|
1409
|
+
gap: 8px;
|
|
1410
|
+
padding: 6px 8px;
|
|
1411
|
+
border-radius: 4px;
|
|
1412
|
+
cursor: pointer;
|
|
1246
1413
|
font-size: 13px;
|
|
1247
1414
|
color: var(--text-secondary);
|
|
1248
|
-
overflow: hidden;
|
|
1249
|
-
text-overflow: ellipsis;
|
|
1250
|
-
white-space: nowrap;
|
|
1251
1415
|
}
|
|
1252
1416
|
|
|
1253
|
-
.
|
|
1254
|
-
|
|
1417
|
+
.dashboard-tab-item:hover {
|
|
1418
|
+
background: var(--bg-tertiary);
|
|
1255
1419
|
}
|
|
1256
1420
|
|
|
1257
|
-
.
|
|
1258
|
-
|
|
1421
|
+
.dashboard-tab-item.active {
|
|
1422
|
+
background: var(--accent);
|
|
1423
|
+
color: white;
|
|
1259
1424
|
}
|
|
1260
1425
|
|
|
1261
|
-
.
|
|
1262
|
-
|
|
1426
|
+
.dashboard-tab-item .tab-icon {
|
|
1427
|
+
font-size: 14px;
|
|
1428
|
+
flex-shrink: 0;
|
|
1263
1429
|
}
|
|
1264
1430
|
|
|
1265
|
-
.
|
|
1431
|
+
.dashboard-tab-item .tab-name {
|
|
1432
|
+
flex: 1;
|
|
1266
1433
|
overflow: hidden;
|
|
1434
|
+
text-overflow: ellipsis;
|
|
1435
|
+
white-space: nowrap;
|
|
1267
1436
|
}
|
|
1268
1437
|
|
|
1269
|
-
.
|
|
1270
|
-
|
|
1438
|
+
.dashboard-actions {
|
|
1439
|
+
flex-shrink: 0;
|
|
1440
|
+
display: flex;
|
|
1441
|
+
gap: 8px;
|
|
1271
1442
|
}
|
|
1272
1443
|
|
|
1273
|
-
.
|
|
1444
|
+
.dashboard-actions .btn-action {
|
|
1445
|
+
flex: 1;
|
|
1446
|
+
padding: 8px 12px;
|
|
1447
|
+
border-radius: 4px;
|
|
1448
|
+
border: 1px dashed var(--border);
|
|
1449
|
+
background: transparent;
|
|
1450
|
+
color: var(--text-muted);
|
|
1451
|
+
cursor: pointer;
|
|
1452
|
+
font-size: 12px;
|
|
1274
1453
|
display: flex;
|
|
1275
1454
|
align-items: center;
|
|
1276
1455
|
justify-content: center;
|
|
1277
|
-
|
|
1278
|
-
color: var(--text-muted);
|
|
1279
|
-
font-size: 13px;
|
|
1456
|
+
gap: 4px;
|
|
1280
1457
|
}
|
|
1281
1458
|
|
|
1282
|
-
.
|
|
1283
|
-
|
|
1284
|
-
margin: 8px;
|
|
1285
|
-
background: rgba(239, 68, 68, 0.1);
|
|
1286
|
-
border: 1px solid var(--status-error);
|
|
1287
|
-
border-radius: 6px;
|
|
1459
|
+
.dashboard-actions .btn-action:hover {
|
|
1460
|
+
border-style: solid;
|
|
1288
1461
|
color: var(--text-secondary);
|
|
1462
|
+
background: var(--bg-tertiary);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
.dashboard-files-list {
|
|
1466
|
+
flex: 1;
|
|
1467
|
+
overflow-y: auto;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
.dashboard-files-list .tree-item {
|
|
1471
|
+
padding: 3px 6px;
|
|
1472
|
+
font-size: 12px;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
.dashboard-files-list .tree-item-name {
|
|
1476
|
+
font-size: 12px;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
.dashboard-empty-state {
|
|
1480
|
+
color: var(--text-muted);
|
|
1289
1481
|
font-size: 13px;
|
|
1482
|
+
padding: 12px;
|
|
1483
|
+
text-align: center;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
/* Status indicators in dashboard tab list */
|
|
1487
|
+
.dashboard-status-indicator {
|
|
1488
|
+
width: 8px;
|
|
1489
|
+
height: 8px;
|
|
1490
|
+
border-radius: 50%;
|
|
1491
|
+
flex-shrink: 0;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
.dashboard-status-working {
|
|
1495
|
+
background: var(--status-active);
|
|
1496
|
+
animation: status-pulse 2s ease-in-out infinite;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
.dashboard-status-idle {
|
|
1500
|
+
background: var(--status-waiting);
|
|
1501
|
+
animation: status-blink-slow 3s ease-in-out infinite;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
.dashboard-status-blocked {
|
|
1505
|
+
background: var(--status-error);
|
|
1506
|
+
animation: status-blink-fast 0.8s ease-in-out infinite;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1510
|
+
.dashboard-status-working,
|
|
1511
|
+
.dashboard-status-idle,
|
|
1512
|
+
.dashboard-status-blocked {
|
|
1513
|
+
animation: none;
|
|
1514
|
+
}
|
|
1290
1515
|
}
|
|
1291
1516
|
</style>
|
|
1292
1517
|
</head>
|
|
@@ -1396,6 +1621,30 @@
|
|
|
1396
1621
|
let pendingCloseTabId = null;
|
|
1397
1622
|
let contextMenuTabId = null;
|
|
1398
1623
|
|
|
1624
|
+
// Collapsible section state (persisted to localStorage)
|
|
1625
|
+
const SECTION_STATE_KEY = 'codev-dashboard-sections';
|
|
1626
|
+
let sectionState = loadSectionState();
|
|
1627
|
+
|
|
1628
|
+
function loadSectionState() {
|
|
1629
|
+
try {
|
|
1630
|
+
const saved = localStorage.getItem(SECTION_STATE_KEY);
|
|
1631
|
+
if (saved) return JSON.parse(saved);
|
|
1632
|
+
} catch (e) { /* ignore */ }
|
|
1633
|
+
return { tabs: true, files: true, projects: true };
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
function saveSectionState() {
|
|
1637
|
+
try {
|
|
1638
|
+
localStorage.setItem(SECTION_STATE_KEY, JSON.stringify(sectionState));
|
|
1639
|
+
} catch (e) { /* ignore */ }
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
function toggleSection(section) {
|
|
1643
|
+
sectionState[section] = !sectionState[section];
|
|
1644
|
+
saveSectionState();
|
|
1645
|
+
renderDashboardTabContent();
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1399
1648
|
// Initialize
|
|
1400
1649
|
function init() {
|
|
1401
1650
|
buildTabsFromState();
|
|
@@ -1516,19 +1765,11 @@
|
|
|
1516
1765
|
const previousTabIds = new Set(tabs.map(t => t.id));
|
|
1517
1766
|
tabs = [];
|
|
1518
1767
|
|
|
1519
|
-
//
|
|
1768
|
+
// Dashboard tab is ALWAYS first and uncloseable (Spec 0045, 0057)
|
|
1520
1769
|
tabs.push({
|
|
1521
|
-
id: '
|
|
1522
|
-
type: '
|
|
1523
|
-
name: '
|
|
1524
|
-
closeable: false
|
|
1525
|
-
});
|
|
1526
|
-
|
|
1527
|
-
// Files tab is second and uncloseable (Spec 0055)
|
|
1528
|
-
tabs.push({
|
|
1529
|
-
id: 'files',
|
|
1530
|
-
type: 'files',
|
|
1531
|
-
name: 'Files',
|
|
1770
|
+
id: 'dashboard',
|
|
1771
|
+
type: 'dashboard',
|
|
1772
|
+
name: 'Dashboard',
|
|
1532
1773
|
closeable: false
|
|
1533
1774
|
});
|
|
1534
1775
|
|
|
@@ -1569,7 +1810,7 @@
|
|
|
1569
1810
|
|
|
1570
1811
|
// Detect new tabs and auto-switch to them (skip projects tab)
|
|
1571
1812
|
for (const tab of tabs) {
|
|
1572
|
-
if (tab.id !== '
|
|
1813
|
+
if (tab.id !== 'dashboard' && tab.id !== 'files' && !knownTabIds.has(tab.id) && previousTabIds.size > 0) {
|
|
1573
1814
|
// This is a new tab - switch to it
|
|
1574
1815
|
activeTabId = tab.id;
|
|
1575
1816
|
break;
|
|
@@ -1579,9 +1820,9 @@
|
|
|
1579
1820
|
// Update known tab IDs
|
|
1580
1821
|
knownTabIds = new Set(tabs.map(t => t.id));
|
|
1581
1822
|
|
|
1582
|
-
// Set active tab to
|
|
1823
|
+
// Set active tab to Dashboard on first load if none selected
|
|
1583
1824
|
if (!activeTabId) {
|
|
1584
|
-
activeTabId = '
|
|
1825
|
+
activeTabId = 'dashboard';
|
|
1585
1826
|
}
|
|
1586
1827
|
}
|
|
1587
1828
|
|
|
@@ -1663,7 +1904,7 @@
|
|
|
1663
1904
|
// Get tab icon
|
|
1664
1905
|
function getTabIcon(type) {
|
|
1665
1906
|
switch (type) {
|
|
1666
|
-
case '
|
|
1907
|
+
case 'dashboard': return '🏠';
|
|
1667
1908
|
case 'files': return '📁';
|
|
1668
1909
|
case 'file': return '📄';
|
|
1669
1910
|
case 'builder': return '🔨';
|
|
@@ -1762,22 +2003,12 @@
|
|
|
1762
2003
|
return;
|
|
1763
2004
|
}
|
|
1764
2005
|
|
|
1765
|
-
// Handle
|
|
1766
|
-
if (tab.type === '
|
|
1767
|
-
if (currentTabType !== '
|
|
1768
|
-
currentTabType = '
|
|
1769
|
-
currentTabPort = null;
|
|
1770
|
-
renderProjectsTab();
|
|
1771
|
-
}
|
|
1772
|
-
return;
|
|
1773
|
-
}
|
|
1774
|
-
|
|
1775
|
-
// Handle files tab specially (no iframe, inline content)
|
|
1776
|
-
if (tab.type === 'files') {
|
|
1777
|
-
if (currentTabType !== 'files') {
|
|
1778
|
-
currentTabType = 'files';
|
|
2006
|
+
// Handle dashboard tab specially (no iframe, inline content)
|
|
2007
|
+
if (tab.type === 'dashboard') {
|
|
2008
|
+
if (currentTabType !== 'dashboard') {
|
|
2009
|
+
currentTabType = 'dashboard';
|
|
1779
2010
|
currentTabPort = null;
|
|
1780
|
-
|
|
2011
|
+
renderDashboardTab();
|
|
1781
2012
|
}
|
|
1782
2013
|
return;
|
|
1783
2014
|
}
|
|
@@ -2987,53 +3218,6 @@
|
|
|
2987
3218
|
}
|
|
2988
3219
|
}
|
|
2989
3220
|
|
|
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
3221
|
// Escape a string for use inside a JavaScript string literal in onclick handlers
|
|
3038
3222
|
// This handles quotes, backslashes, and other special characters
|
|
3039
3223
|
function escapeJsString(str) {
|
|
@@ -3113,13 +3297,24 @@
|
|
|
3113
3297
|
} else {
|
|
3114
3298
|
filesTreeExpanded.add(path);
|
|
3115
3299
|
}
|
|
3116
|
-
|
|
3300
|
+
rerenderFilesBrowser();
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
// Re-render file browser in current context (dashboard or files tab)
|
|
3304
|
+
function rerenderFilesBrowser() {
|
|
3305
|
+
if (activeTabId === 'dashboard') {
|
|
3306
|
+
// Re-render just the files column in dashboard
|
|
3307
|
+
const filesListEl = document.getElementById('dashboard-files-list');
|
|
3308
|
+
if (filesListEl) {
|
|
3309
|
+
filesListEl.innerHTML = renderDashboardFilesBrowser();
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3117
3312
|
}
|
|
3118
3313
|
|
|
3119
3314
|
// Collapse all folders
|
|
3120
3315
|
function collapseAllFolders() {
|
|
3121
3316
|
filesTreeExpanded.clear();
|
|
3122
|
-
|
|
3317
|
+
rerenderFilesBrowser();
|
|
3123
3318
|
}
|
|
3124
3319
|
|
|
3125
3320
|
// Expand all folders
|
|
@@ -3135,13 +3330,13 @@
|
|
|
3135
3330
|
}
|
|
3136
3331
|
}
|
|
3137
3332
|
collectPaths(filesTreeData);
|
|
3138
|
-
|
|
3333
|
+
rerenderFilesBrowser();
|
|
3139
3334
|
}
|
|
3140
3335
|
|
|
3141
3336
|
// Refresh files tree
|
|
3142
3337
|
async function refreshFilesTree() {
|
|
3143
3338
|
await loadFilesTree();
|
|
3144
|
-
|
|
3339
|
+
rerenderFilesBrowser();
|
|
3145
3340
|
showToast('Files refreshed', 'success');
|
|
3146
3341
|
}
|
|
3147
3342
|
|
|
@@ -3189,55 +3384,222 @@
|
|
|
3189
3384
|
function renderInfoHeader() {
|
|
3190
3385
|
return `
|
|
3191
3386
|
<div class="projects-info">
|
|
3192
|
-
<h1 style="font-size: 20px; margin-bottom: 12px; color: var(--text-primary);">
|
|
3193
|
-
<p>
|
|
3194
|
-
<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>
|
|
3387
|
+
<h1 style="font-size: 20px; margin-bottom: 12px; color: var(--text-primary);">Agent Farm Dashboard</h1>
|
|
3388
|
+
<p>Coordinate AI builders working on your codebase. The left panel shows the Architect terminal – tell it what you want to build. <strong>Tabs</strong> shows open terminals (Architect, Builders, utility shells). <strong>Files</strong> lets you browse and open project files. <strong>Projects</strong> tracks work as it moves from conception to integration.</p>
|
|
3389
|
+
<p>Docs: <a href="#" onclick="openProjectFile('codev/resources/cheatsheet.md'); return false;">Cheatsheet</a> · <a href="#" onclick="openProjectFile('codev/resources/lifecycle.md'); return false;">Lifecycle</a> · <a href="#" onclick="openProjectFile('codev/resources/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> · <a href="https://discord.gg/mJ92DhDa6n" target="_blank">Discord</a></p>
|
|
3196
3390
|
</div>
|
|
3197
3391
|
`;
|
|
3198
3392
|
}
|
|
3199
3393
|
|
|
3200
|
-
// Render the
|
|
3201
|
-
function
|
|
3394
|
+
// Render the dashboard tab content (internal - called after data is loaded)
|
|
3395
|
+
function renderDashboardTabContent() {
|
|
3202
3396
|
const content = document.getElementById('tab-content');
|
|
3203
3397
|
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3398
|
+
content.innerHTML = `
|
|
3399
|
+
<div class="dashboard-container">
|
|
3400
|
+
${renderInfoHeader()}
|
|
3401
|
+
<div class="dashboard-header">
|
|
3402
|
+
<!-- Tabs Section -->
|
|
3403
|
+
<div class="dashboard-section section-tabs ${sectionState.tabs ? '' : 'collapsed'}">
|
|
3404
|
+
<div class="dashboard-section-header" onclick="toggleSection('tabs')">
|
|
3405
|
+
<h3><span class="collapse-icon">▼</span> Tabs</h3>
|
|
3406
|
+
</div>
|
|
3407
|
+
<div class="dashboard-section-content">
|
|
3408
|
+
<div class="dashboard-tabs-list" id="dashboard-tabs-list">
|
|
3409
|
+
${renderDashboardTabsList()}
|
|
3410
|
+
</div>
|
|
3411
|
+
</div>
|
|
3412
|
+
</div>
|
|
3413
|
+
<!-- Files Section -->
|
|
3414
|
+
<div class="dashboard-section section-files ${sectionState.files ? '' : 'collapsed'}">
|
|
3415
|
+
<div class="dashboard-section-header" onclick="toggleSection('files')">
|
|
3416
|
+
<h3><span class="collapse-icon">▼</span> Files</h3>
|
|
3417
|
+
<div class="header-actions" onclick="event.stopPropagation()">
|
|
3418
|
+
<button onclick="collapseAllFolders()" title="Collapse All">⊟</button>
|
|
3419
|
+
<button onclick="expandAllFolders()" title="Expand All">⊞</button>
|
|
3420
|
+
</div>
|
|
3421
|
+
</div>
|
|
3422
|
+
<div class="dashboard-section-content">
|
|
3423
|
+
<div class="dashboard-files-list" id="dashboard-files-list">
|
|
3424
|
+
${renderDashboardFilesBrowser()}
|
|
3425
|
+
</div>
|
|
3426
|
+
</div>
|
|
3427
|
+
</div>
|
|
3428
|
+
</div>
|
|
3429
|
+
<!-- Projects Section -->
|
|
3430
|
+
<div class="dashboard-section section-projects ${sectionState.projects ? '' : 'collapsed'}">
|
|
3431
|
+
<div class="dashboard-section-header" onclick="toggleSection('projects')">
|
|
3432
|
+
<h3><span class="collapse-icon">▼</span> Projects</h3>
|
|
3433
|
+
</div>
|
|
3434
|
+
<div class="dashboard-section-content" id="dashboard-projects">
|
|
3435
|
+
${renderDashboardProjectsSection()}
|
|
3436
|
+
</div>
|
|
3437
|
+
</div>
|
|
3438
|
+
</div>
|
|
3439
|
+
`;
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
// Render the tabs list for dashboard
|
|
3443
|
+
function renderDashboardTabsList() {
|
|
3444
|
+
// Filter to show terminal tabs only (not Dashboard/Files tabs)
|
|
3445
|
+
const terminalTabs = tabs.filter(t => t.type !== 'dashboard' && t.type !== 'files');
|
|
3446
|
+
|
|
3447
|
+
if (terminalTabs.length === 0) {
|
|
3448
|
+
return '<div class="dashboard-empty-state">No tabs open</div>';
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3451
|
+
return terminalTabs.map(tab => {
|
|
3452
|
+
const isActive = tab.id === activeTabId;
|
|
3453
|
+
const icon = getTabIcon(tab.type);
|
|
3454
|
+
const statusIndicator = getDashboardStatusIndicator(tab);
|
|
3455
|
+
|
|
3456
|
+
return `
|
|
3457
|
+
<div class="dashboard-tab-item ${isActive ? 'active' : ''}" onclick="selectTab('${tab.id}')">
|
|
3458
|
+
${statusIndicator}
|
|
3459
|
+
<span class="tab-icon">${icon}</span>
|
|
3460
|
+
<span class="tab-name">${escapeHtml(tab.name)}</span>
|
|
3208
3461
|
</div>
|
|
3209
3462
|
`;
|
|
3210
|
-
|
|
3463
|
+
}).join('');
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
// Get status indicator for dashboard tab list
|
|
3467
|
+
function getDashboardStatusIndicator(tab) {
|
|
3468
|
+
if (tab.type !== 'builder') return '';
|
|
3469
|
+
|
|
3470
|
+
// Use builder status from state
|
|
3471
|
+
const builderState = (state.builders || []).find(b => `builder-${b.id}` === tab.id);
|
|
3472
|
+
if (!builderState) return '';
|
|
3473
|
+
|
|
3474
|
+
const status = builderState.status;
|
|
3475
|
+
if (['spawning', 'implementing'].includes(status)) {
|
|
3476
|
+
return '<span class="dashboard-status-indicator dashboard-status-working" title="Working"></span>';
|
|
3477
|
+
}
|
|
3478
|
+
if (status === 'blocked') {
|
|
3479
|
+
return '<span class="dashboard-status-indicator dashboard-status-blocked" title="Blocked"></span>';
|
|
3480
|
+
}
|
|
3481
|
+
if (['pr-ready', 'complete'].includes(status)) {
|
|
3482
|
+
return '<span class="dashboard-status-indicator dashboard-status-idle" title="Idle"></span>';
|
|
3483
|
+
}
|
|
3484
|
+
return '';
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
// Render compact file browser for dashboard
|
|
3488
|
+
function renderDashboardFilesBrowser() {
|
|
3489
|
+
if (filesTreeError) {
|
|
3490
|
+
return `<div class="dashboard-empty-state">${escapeHtml(filesTreeError)}</div>`;
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
if (!filesTreeLoaded || filesTreeData.length === 0) {
|
|
3494
|
+
return '<div class="dashboard-empty-state">Loading files...</div>';
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
return renderTreeNodes(filesTreeData, 0);
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
// Render the projects section for dashboard
|
|
3501
|
+
function renderDashboardProjectsSection() {
|
|
3502
|
+
if (projectlistError) {
|
|
3503
|
+
return renderErrorBanner(projectlistError);
|
|
3211
3504
|
}
|
|
3212
3505
|
|
|
3213
3506
|
if (projectsData.length === 0) {
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3507
|
+
// No welcome screen - just a helpful message
|
|
3508
|
+
return `
|
|
3509
|
+
<div class="dashboard-empty-state" style="padding: 24px;">
|
|
3510
|
+
No projects yet. Ask the Architect to create your first project.
|
|
3217
3511
|
</div>
|
|
3218
3512
|
`;
|
|
3219
|
-
return;
|
|
3220
3513
|
}
|
|
3221
3514
|
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
${renderTerminalProjects(projectsData)}
|
|
3227
|
-
</div>
|
|
3515
|
+
// Render the existing project view
|
|
3516
|
+
return `
|
|
3517
|
+
${renderKanbanGrid(projectsData)}
|
|
3518
|
+
${renderTerminalProjects(projectsData)}
|
|
3228
3519
|
`;
|
|
3229
3520
|
}
|
|
3230
3521
|
|
|
3231
|
-
//
|
|
3232
|
-
async function
|
|
3522
|
+
// Create new utility shell (quick action button)
|
|
3523
|
+
async function createNewShell() {
|
|
3524
|
+
try {
|
|
3525
|
+
const response = await fetch('/api/tabs/shell', { method: 'POST' });
|
|
3526
|
+
const data = await response.json();
|
|
3527
|
+
if (!data.success && data.error) {
|
|
3528
|
+
showToast(data.error || 'Failed to create shell', 'error');
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3531
|
+
await refresh();
|
|
3532
|
+
if (data.id) {
|
|
3533
|
+
selectTab(`shell-${data.id}`);
|
|
3534
|
+
}
|
|
3535
|
+
showToast('Shell created', 'success');
|
|
3536
|
+
} catch (err) {
|
|
3537
|
+
showToast('Network error: ' + err.message, 'error');
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
// Create new worktree shell (quick action button)
|
|
3542
|
+
async function createNewWorktreeShell() {
|
|
3543
|
+
const branch = prompt('Branch name (leave empty for temp worktree):');
|
|
3544
|
+
if (branch === null) return; // User cancelled
|
|
3545
|
+
|
|
3546
|
+
try {
|
|
3547
|
+
const response = await fetch('/api/tabs/shell', {
|
|
3548
|
+
method: 'POST',
|
|
3549
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3550
|
+
body: JSON.stringify({ worktree: true, branch: branch || undefined })
|
|
3551
|
+
});
|
|
3552
|
+
const data = await response.json();
|
|
3553
|
+
if (!data.success && data.error) {
|
|
3554
|
+
showToast(data.error || 'Failed to create worktree shell', 'error');
|
|
3555
|
+
return;
|
|
3556
|
+
}
|
|
3557
|
+
await refresh();
|
|
3558
|
+
// Auto-select the newly created tab (consistent with createNewShell behavior)
|
|
3559
|
+
if (data.id) {
|
|
3560
|
+
selectTab(`shell-${data.id}`);
|
|
3561
|
+
}
|
|
3562
|
+
showToast('Worktree shell created', 'success');
|
|
3563
|
+
} catch (err) {
|
|
3564
|
+
showToast('Network error: ' + err.message, 'error');
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
// Render the dashboard tab (entry point - loads data first)
|
|
3569
|
+
async function renderDashboardTab() {
|
|
3233
3570
|
const content = document.getElementById('tab-content');
|
|
3234
|
-
content.innerHTML = '<div class="
|
|
3571
|
+
content.innerHTML = '<div class="dashboard-container"><p style="color: var(--text-muted); padding: 16px;">Loading dashboard...</p></div>';
|
|
3235
3572
|
|
|
3236
|
-
|
|
3237
|
-
|
|
3573
|
+
// Load both projectlist and files tree in parallel
|
|
3574
|
+
await Promise.all([
|
|
3575
|
+
loadProjectlist(),
|
|
3576
|
+
loadFilesTreeIfNeeded()
|
|
3577
|
+
]);
|
|
3578
|
+
|
|
3579
|
+
renderDashboardTabContent();
|
|
3238
3580
|
checkStarterMode(); // Update polling state after initial load
|
|
3239
3581
|
}
|
|
3240
3582
|
|
|
3583
|
+
// Load files tree if not already loaded
|
|
3584
|
+
async function loadFilesTreeIfNeeded() {
|
|
3585
|
+
if (!filesTreeLoaded) {
|
|
3586
|
+
await loadFilesTree();
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3590
|
+
// Legacy function for backward compatibility (still used by polling)
|
|
3591
|
+
function renderProjectsTabContent() {
|
|
3592
|
+
// If dashboard tab is active, re-render dashboard instead
|
|
3593
|
+
if (activeTabId === 'dashboard') {
|
|
3594
|
+
renderDashboardTabContent();
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
// Legacy function alias
|
|
3599
|
+
async function renderProjectsTab() {
|
|
3600
|
+
await renderDashboardTab();
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3241
3603
|
// Load projectlist.md from disk
|
|
3242
3604
|
async function loadProjectlist() {
|
|
3243
3605
|
try {
|
|
@@ -3282,8 +3644,8 @@
|
|
|
3282
3644
|
|
|
3283
3645
|
// Poll projectlist for changes (every 5 seconds)
|
|
3284
3646
|
async function pollProjectlist() {
|
|
3285
|
-
// Only poll if
|
|
3286
|
-
if (activeTabId !== '
|
|
3647
|
+
// Only poll if dashboard tab is active
|
|
3648
|
+
if (activeTabId !== 'dashboard') return;
|
|
3287
3649
|
|
|
3288
3650
|
try {
|
|
3289
3651
|
const response = await fetch('/file?path=codev/projectlist.md');
|