droid-patch 0.7.1 → 0.8.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/README.md +64 -3
- package/README.zh-CN.md +64 -3
- package/dist/alias-Bhigcbue.mjs.map +1 -1
- package/dist/cli.mjs +1422 -1006
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -690,6 +690,7 @@ const MIN_RENDER_INTERVAL_MS = IS_APPLE_TERMINAL ? 1000 : 500;
|
|
|
690
690
|
const START_MS = Date.now();
|
|
691
691
|
const ARGS = process.argv.slice(2);
|
|
692
692
|
const PGID = Number(process.env.DROID_STATUSLINE_PGID || '');
|
|
693
|
+
const SESSION_ID_RE = /"sessionId":"([0-9a-f-]{36})"/i;
|
|
693
694
|
|
|
694
695
|
function sleep(ms) {
|
|
695
696
|
return new Promise((r) => setTimeout(r, ms));
|
|
@@ -699,6 +700,28 @@ function isPositiveInt(n) {
|
|
|
699
700
|
return Number.isFinite(n) && n > 0;
|
|
700
701
|
}
|
|
701
702
|
|
|
703
|
+
function extractSessionIdFromLine(line) {
|
|
704
|
+
if (!line) return null;
|
|
705
|
+
const m = String(line).match(SESSION_ID_RE);
|
|
706
|
+
return m ? m[1] : null;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function nextCompactionState(line, current) {
|
|
710
|
+
if (!line) return current;
|
|
711
|
+
if (line.includes('[Compaction] Start')) return true;
|
|
712
|
+
if (
|
|
713
|
+
line.includes('[Compaction] End') ||
|
|
714
|
+
line.includes('[Compaction] Done') ||
|
|
715
|
+
line.includes('[Compaction] Finish') ||
|
|
716
|
+
line.includes('[Compaction] Finished') ||
|
|
717
|
+
line.includes('[Compaction] Complete') ||
|
|
718
|
+
line.includes('[Compaction] Completed')
|
|
719
|
+
) {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
return current;
|
|
723
|
+
}
|
|
724
|
+
|
|
702
725
|
function firstNonNull(promises) {
|
|
703
726
|
const list = Array.isArray(promises) ? promises : [];
|
|
704
727
|
if (list.length === 0) return Promise.resolve(null);
|
|
@@ -1208,6 +1231,7 @@ async function main() {
|
|
|
1208
1231
|
}
|
|
1209
1232
|
|
|
1210
1233
|
if (!sessionId || !workspaceDir) return;
|
|
1234
|
+
const sessionIdLower = String(sessionId).toLowerCase();
|
|
1211
1235
|
|
|
1212
1236
|
const { settingsPath, settings } = resolveSessionSettings(workspaceDir, sessionId);
|
|
1213
1237
|
const modelId =
|
|
@@ -1257,14 +1281,34 @@ async function main() {
|
|
|
1257
1281
|
} catch {}
|
|
1258
1282
|
}, 0).unref();
|
|
1259
1283
|
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1284
|
+
let reseedInProgress = false;
|
|
1285
|
+
let reseedQueued = false;
|
|
1286
|
+
|
|
1287
|
+
function updateLastFromContext(ctx, updateOutputTokens) {
|
|
1288
|
+
const cacheRead = Number(ctx?.cacheReadInputTokens);
|
|
1289
|
+
const contextCount = Number(ctx?.contextCount);
|
|
1290
|
+
const out = Number(ctx?.outputTokens);
|
|
1291
|
+
if (Number.isFinite(cacheRead)) last.cacheReadInputTokens = cacheRead;
|
|
1292
|
+
if (Number.isFinite(contextCount)) last.contextCount = contextCount;
|
|
1293
|
+
if (updateOutputTokens && Number.isFinite(out)) last.outputTokens = out;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
function seedLastContextFromLog(options) {
|
|
1297
|
+
const opts = options || {};
|
|
1298
|
+
const maxScanBytes = Number.isFinite(opts.maxScanBytes) ? opts.maxScanBytes : 64 * 1024 * 1024;
|
|
1299
|
+
const preferStreaming = !!opts.preferStreaming;
|
|
1300
|
+
|
|
1301
|
+
if (reseedInProgress) {
|
|
1302
|
+
reseedQueued = true;
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
reseedInProgress = true;
|
|
1306
|
+
|
|
1263
1307
|
setTimeout(() => {
|
|
1264
1308
|
try {
|
|
1265
|
-
// Backward scan to find the most recent
|
|
1266
|
-
//
|
|
1267
|
-
|
|
1309
|
+
// Backward scan to find the most recent context entry for this session.
|
|
1310
|
+
// Prefer streaming context if requested; otherwise accept any context line
|
|
1311
|
+
// that includes cacheReadInputTokens/contextCount fields.
|
|
1268
1312
|
const CHUNK_BYTES = 1024 * 1024; // 1 MiB
|
|
1269
1313
|
|
|
1270
1314
|
const fd = fs.openSync(LOG_PATH, 'r');
|
|
@@ -1276,7 +1320,7 @@ async function main() {
|
|
|
1276
1320
|
let remainder = '';
|
|
1277
1321
|
let seeded = false;
|
|
1278
1322
|
|
|
1279
|
-
while (pos > 0 && scanned <
|
|
1323
|
+
while (pos > 0 && scanned < maxScanBytes && !seeded) {
|
|
1280
1324
|
const readSize = Math.min(CHUNK_BYTES, pos);
|
|
1281
1325
|
const start = pos - readSize;
|
|
1282
1326
|
const buf = Buffer.alloc(readSize);
|
|
@@ -1296,8 +1340,12 @@ async function main() {
|
|
|
1296
1340
|
const line = String(lines[i] || '').trimEnd();
|
|
1297
1341
|
if (!line) continue;
|
|
1298
1342
|
if (!line.includes('Context:')) continue;
|
|
1299
|
-
|
|
1300
|
-
if (!
|
|
1343
|
+
const sid = extractSessionIdFromLine(line);
|
|
1344
|
+
if (!sid || String(sid).toLowerCase() !== sessionIdLower) continue;
|
|
1345
|
+
|
|
1346
|
+
const isStreaming = line.includes('[Agent] Streaming result');
|
|
1347
|
+
if (preferStreaming && !isStreaming) continue;
|
|
1348
|
+
|
|
1301
1349
|
const ctxIndex = line.indexOf('Context: ');
|
|
1302
1350
|
if (ctxIndex === -1) continue;
|
|
1303
1351
|
const jsonStr = line.slice(ctxIndex + 'Context: '.length).trim();
|
|
@@ -1307,12 +1355,13 @@ async function main() {
|
|
|
1307
1355
|
} catch {
|
|
1308
1356
|
continue;
|
|
1309
1357
|
}
|
|
1310
|
-
|
|
1311
|
-
const
|
|
1312
|
-
const
|
|
1313
|
-
|
|
1314
|
-
if (
|
|
1315
|
-
|
|
1358
|
+
|
|
1359
|
+
const cacheRead = Number(ctx?.cacheReadInputTokens);
|
|
1360
|
+
const contextCount = Number(ctx?.contextCount);
|
|
1361
|
+
const hasUsage = Number.isFinite(cacheRead) || Number.isFinite(contextCount);
|
|
1362
|
+
if (!hasUsage) continue;
|
|
1363
|
+
|
|
1364
|
+
updateLastFromContext(ctx, isStreaming);
|
|
1316
1365
|
seeded = true;
|
|
1317
1366
|
break;
|
|
1318
1367
|
}
|
|
@@ -1324,16 +1373,31 @@ async function main() {
|
|
|
1324
1373
|
fs.closeSync(fd);
|
|
1325
1374
|
} catch {}
|
|
1326
1375
|
}
|
|
1327
|
-
|
|
1328
|
-
renderNow();
|
|
1329
1376
|
} catch {
|
|
1330
1377
|
// ignore
|
|
1378
|
+
} finally {
|
|
1379
|
+
reseedInProgress = false;
|
|
1380
|
+
if (reseedQueued) {
|
|
1381
|
+
reseedQueued = false;
|
|
1382
|
+
seedLastContextFromLog({ maxScanBytes, preferStreaming });
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
renderNow();
|
|
1331
1386
|
}
|
|
1332
1387
|
}, 0).unref();
|
|
1333
1388
|
}
|
|
1334
1389
|
|
|
1390
|
+
// Seed prompt-context usage from existing logs (important for resumed sessions).
|
|
1391
|
+
// Do this asynchronously to avoid delaying the first statusline frame.
|
|
1392
|
+
let initialSeedDone = false;
|
|
1393
|
+
if (resumeFlag || resumeId) {
|
|
1394
|
+
initialSeedDone = true;
|
|
1395
|
+
seedLastContextFromLog({ maxScanBytes: 64 * 1024 * 1024, preferStreaming: true });
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1335
1398
|
// Watch session settings for autonomy/reasoning changes (cheap polling with mtime).
|
|
1336
1399
|
let settingsMtimeMs = 0;
|
|
1400
|
+
let lastCtxPollMs = 0;
|
|
1337
1401
|
setInterval(() => {
|
|
1338
1402
|
try {
|
|
1339
1403
|
const stat = fs.statSync(settingsPath);
|
|
@@ -1356,6 +1420,17 @@ async function main() {
|
|
|
1356
1420
|
}
|
|
1357
1421
|
}, 750).unref();
|
|
1358
1422
|
|
|
1423
|
+
// Fallback: periodically rescan log if context is still zero after startup.
|
|
1424
|
+
// This handles cases where tail misses early log entries.
|
|
1425
|
+
setInterval(() => {
|
|
1426
|
+
const now = Date.now();
|
|
1427
|
+
if (now - START_MS < 3000) return; // wait 3s after startup
|
|
1428
|
+
if (last.contextCount > 0 || last.cacheReadInputTokens > 0) return; // already have data
|
|
1429
|
+
if (now - lastCtxPollMs < 5000) return; // throttle to every 5s
|
|
1430
|
+
lastCtxPollMs = now;
|
|
1431
|
+
seedLastContextFromLog({ maxScanBytes: 4 * 1024 * 1024, preferStreaming: false });
|
|
1432
|
+
}, 2000).unref();
|
|
1433
|
+
|
|
1359
1434
|
// Follow the Factory log and update based on session-scoped events.
|
|
1360
1435
|
const tail = spawn('tail', ['-n', '0', '-F', LOG_PATH], {
|
|
1361
1436
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
@@ -1370,8 +1445,51 @@ async function main() {
|
|
|
1370
1445
|
const line = buffer.slice(0, idx).trimEnd();
|
|
1371
1446
|
buffer = buffer.slice(idx + 1);
|
|
1372
1447
|
|
|
1373
|
-
|
|
1374
|
-
|
|
1448
|
+
const lineSessionId = extractSessionIdFromLine(line);
|
|
1449
|
+
const isSessionLine =
|
|
1450
|
+
lineSessionId && String(lineSessionId).toLowerCase() === sessionIdLower;
|
|
1451
|
+
|
|
1452
|
+
let compactionChanged = false;
|
|
1453
|
+
let compactionEnded = false;
|
|
1454
|
+
if (line.includes('[Compaction]')) {
|
|
1455
|
+
// Accept session-scoped compaction lines; allow end markers to clear even
|
|
1456
|
+
// if the line lacks a session id (some builds omit Context on end lines).
|
|
1457
|
+
if (isSessionLine || (compacting && !lineSessionId)) {
|
|
1458
|
+
const next = nextCompactionState(line, compacting);
|
|
1459
|
+
if (next !== compacting) {
|
|
1460
|
+
compacting = next;
|
|
1461
|
+
compactionChanged = true;
|
|
1462
|
+
if (!compacting) compactionEnded = true;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
if (!line.includes('Context:')) {
|
|
1468
|
+
if (compactionChanged) {
|
|
1469
|
+
lastRenderAt = Date.now();
|
|
1470
|
+
renderNow();
|
|
1471
|
+
}
|
|
1472
|
+
if (compactionEnded) {
|
|
1473
|
+
// Compaction often completes between turns. Refresh ctx numbers promptly
|
|
1474
|
+
// by rescanning the most recent Context entry for this session.
|
|
1475
|
+
setTimeout(() => {
|
|
1476
|
+
seedLastContextFromLog({ maxScanBytes: 8 * 1024 * 1024, preferStreaming: false });
|
|
1477
|
+
}, 250).unref();
|
|
1478
|
+
}
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
if (!isSessionLine) {
|
|
1482
|
+
if (compactionChanged) {
|
|
1483
|
+
lastRenderAt = Date.now();
|
|
1484
|
+
renderNow();
|
|
1485
|
+
}
|
|
1486
|
+
if (compactionEnded) {
|
|
1487
|
+
setTimeout(() => {
|
|
1488
|
+
seedLastContextFromLog({ maxScanBytes: 8 * 1024 * 1024, preferStreaming: false });
|
|
1489
|
+
}, 250).unref();
|
|
1490
|
+
}
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1375
1493
|
|
|
1376
1494
|
const ctxIndex = line.indexOf('Context: ');
|
|
1377
1495
|
if (ctxIndex === -1) continue;
|
|
@@ -1380,28 +1498,41 @@ async function main() {
|
|
|
1380
1498
|
try {
|
|
1381
1499
|
ctx = JSON.parse(jsonStr);
|
|
1382
1500
|
} catch {
|
|
1501
|
+
if (compactionChanged) {
|
|
1502
|
+
lastRenderAt = Date.now();
|
|
1503
|
+
renderNow();
|
|
1504
|
+
}
|
|
1383
1505
|
continue;
|
|
1384
1506
|
}
|
|
1385
1507
|
|
|
1386
|
-
//
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1508
|
+
// Context usage can appear on multiple session-scoped log lines; update whenever present.
|
|
1509
|
+
// (Streaming is still the best source for outputTokens / LastOut.)
|
|
1510
|
+
updateLastFromContext(ctx, false);
|
|
1511
|
+
|
|
1512
|
+
// For new sessions: if this is the first valid Context line and ctx is still 0,
|
|
1513
|
+
// trigger a reseed to catch any earlier log entries we might have missed.
|
|
1514
|
+
if (!initialSeedDone && last.contextCount === 0) {
|
|
1515
|
+
initialSeedDone = true;
|
|
1516
|
+
setTimeout(() => {
|
|
1517
|
+
seedLastContextFromLog({ maxScanBytes: 8 * 1024 * 1024, preferStreaming: false });
|
|
1518
|
+
}, 100).unref();
|
|
1394
1519
|
}
|
|
1395
1520
|
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1521
|
+
if (line.includes('[Agent] Streaming result')) {
|
|
1522
|
+
updateLastFromContext(ctx, true);
|
|
1523
|
+
}
|
|
1399
1524
|
|
|
1400
1525
|
const now = Date.now();
|
|
1401
|
-
if (now - lastRenderAt >= MIN_RENDER_INTERVAL_MS) {
|
|
1526
|
+
if (compactionChanged || now - lastRenderAt >= MIN_RENDER_INTERVAL_MS) {
|
|
1402
1527
|
lastRenderAt = now;
|
|
1403
1528
|
renderNow();
|
|
1404
1529
|
}
|
|
1530
|
+
|
|
1531
|
+
if (compactionEnded) {
|
|
1532
|
+
setTimeout(() => {
|
|
1533
|
+
seedLastContextFromLog({ maxScanBytes: 8 * 1024 * 1024, preferStreaming: false });
|
|
1534
|
+
}, 250).unref();
|
|
1535
|
+
}
|
|
1405
1536
|
}
|
|
1406
1537
|
});
|
|
1407
1538
|
|
|
@@ -1418,976 +1549,959 @@ async function main() {
|
|
|
1418
1549
|
main().catch(() => {});
|
|
1419
1550
|
`;
|
|
1420
1551
|
}
|
|
1421
|
-
function generateStatuslineWrapperScript(execTargetPath, monitorScriptPath) {
|
|
1422
|
-
return
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
if
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
except Exception:
|
|
2366
|
-
pass
|
|
2367
|
-
try:
|
|
2368
|
-
os.close(master_fd)
|
|
2369
|
-
except Exception:
|
|
2370
|
-
pass
|
|
2371
|
-
if monitor is not None:
|
|
2372
|
-
try:
|
|
2373
|
-
monitor.terminate()
|
|
2374
|
-
except Exception:
|
|
2375
|
-
pass
|
|
2376
|
-
|
|
2377
|
-
sys.exit(child.returncode or 0)
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
if __name__ == "__main__":
|
|
2381
|
-
main()
|
|
1552
|
+
function generateStatuslineWrapperScript(execTargetPath, monitorScriptPath, sessionsScriptPath) {
|
|
1553
|
+
return generateStatuslineWrapperScriptBun(execTargetPath, monitorScriptPath, sessionsScriptPath);
|
|
1554
|
+
}
|
|
1555
|
+
function generateStatuslineWrapperScriptBun(execTargetPath, monitorScriptPath, sessionsScriptPath) {
|
|
1556
|
+
return `#!/usr/bin/env bun
|
|
1557
|
+
// Droid with Statusline (Bun PTY proxy)
|
|
1558
|
+
// Auto-generated by droid-patch --statusline
|
|
1559
|
+
|
|
1560
|
+
const EXEC_TARGET = ${JSON.stringify(execTargetPath)};
|
|
1561
|
+
const STATUSLINE_MONITOR = ${JSON.stringify(monitorScriptPath)};
|
|
1562
|
+
const SESSIONS_SCRIPT = ${sessionsScriptPath ? JSON.stringify(sessionsScriptPath) : "null"};
|
|
1563
|
+
|
|
1564
|
+
const IS_APPLE_TERMINAL = process.env.TERM_PROGRAM === "Apple_Terminal";
|
|
1565
|
+
const MIN_RENDER_INTERVAL_MS = IS_APPLE_TERMINAL ? 800 : 400;
|
|
1566
|
+
const QUIET_MS = 50;
|
|
1567
|
+
const FORCE_REPAINT_INTERVAL_MS = 2000;
|
|
1568
|
+
const RESERVED_ROWS = 1;
|
|
1569
|
+
|
|
1570
|
+
const BYPASS_FLAGS = new Set(["--help", "-h", "--version", "-V"]);
|
|
1571
|
+
const BYPASS_COMMANDS = new Set(["help", "version", "completion", "completions", "exec"]);
|
|
1572
|
+
|
|
1573
|
+
function shouldPassthrough(argv) {
|
|
1574
|
+
for (const a of argv) {
|
|
1575
|
+
if (a === "--") break;
|
|
1576
|
+
if (BYPASS_FLAGS.has(a)) return true;
|
|
1577
|
+
}
|
|
1578
|
+
let endOpts = false;
|
|
1579
|
+
let cmd = null;
|
|
1580
|
+
for (const a of argv) {
|
|
1581
|
+
if (a === "--") {
|
|
1582
|
+
endOpts = true;
|
|
1583
|
+
continue;
|
|
1584
|
+
}
|
|
1585
|
+
if (!endOpts && a.startsWith("-")) continue;
|
|
1586
|
+
cmd = a;
|
|
1587
|
+
break;
|
|
1588
|
+
}
|
|
1589
|
+
return cmd && BYPASS_COMMANDS.has(cmd);
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
function isSessionsCommand(argv) {
|
|
1593
|
+
for (const a of argv) {
|
|
1594
|
+
if (a === "--") return false;
|
|
1595
|
+
if (a === "--sessions") return true;
|
|
1596
|
+
}
|
|
1597
|
+
return false;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
async function execPassthrough(argv) {
|
|
1601
|
+
const proc = Bun.spawn([EXEC_TARGET, ...argv], {
|
|
1602
|
+
stdin: "inherit",
|
|
1603
|
+
stdout: "inherit",
|
|
1604
|
+
stderr: "inherit",
|
|
1605
|
+
});
|
|
1606
|
+
const code = await proc.exited;
|
|
1607
|
+
process.exit(code ?? 0);
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
async function runSessions() {
|
|
1611
|
+
if (SESSIONS_SCRIPT) {
|
|
1612
|
+
const proc = Bun.spawn(["node", String(SESSIONS_SCRIPT)], {
|
|
1613
|
+
stdin: "inherit",
|
|
1614
|
+
stdout: "inherit",
|
|
1615
|
+
stderr: "inherit",
|
|
1616
|
+
});
|
|
1617
|
+
const code = await proc.exited;
|
|
1618
|
+
process.exit(code ?? 0);
|
|
1619
|
+
}
|
|
1620
|
+
process.stderr.write("[statusline] sessions script not found\\n");
|
|
1621
|
+
process.exit(1);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
function writeStdout(s) {
|
|
1625
|
+
try {
|
|
1626
|
+
process.stdout.write(s);
|
|
1627
|
+
} catch {
|
|
1628
|
+
// ignore
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
function termSize() {
|
|
1633
|
+
const rows = Number(process.stdout.rows || 24);
|
|
1634
|
+
const cols = Number(process.stdout.columns || 80);
|
|
1635
|
+
return { rows: Number.isFinite(rows) ? rows : 24, cols: Number.isFinite(cols) ? cols : 80 };
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
const ANSI_RE = /\\x1b\\[[0-9;]*m/g;
|
|
1639
|
+
const RESET_SGR = "\\x1b[0m";
|
|
1640
|
+
|
|
1641
|
+
function visibleWidth(text) {
|
|
1642
|
+
return String(text || "").replace(ANSI_RE, "").length;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
function clampAnsi(text, cols) {
|
|
1646
|
+
if (!cols || cols <= 0) return String(text || "");
|
|
1647
|
+
cols = cols > 1 ? cols - 1 : cols; // avoid last-column wrap
|
|
1648
|
+
if (cols < 10) return String(text || "");
|
|
1649
|
+
const s = String(text || "");
|
|
1650
|
+
let visible = 0;
|
|
1651
|
+
let i = 0;
|
|
1652
|
+
const out = [];
|
|
1653
|
+
while (i < s.length) {
|
|
1654
|
+
const ch = s[i];
|
|
1655
|
+
if (ch === "\\x1b") {
|
|
1656
|
+
const m = s.indexOf("m", i);
|
|
1657
|
+
if (m !== -1) {
|
|
1658
|
+
out.push(s.slice(i, m + 1));
|
|
1659
|
+
i = m + 1;
|
|
1660
|
+
continue;
|
|
1661
|
+
}
|
|
1662
|
+
out.push(ch);
|
|
1663
|
+
i += 1;
|
|
1664
|
+
continue;
|
|
1665
|
+
}
|
|
1666
|
+
if (visible >= cols) break;
|
|
1667
|
+
out.push(ch);
|
|
1668
|
+
i += 1;
|
|
1669
|
+
visible += 1;
|
|
1670
|
+
}
|
|
1671
|
+
if (i < s.length && cols >= 1) {
|
|
1672
|
+
if (visible >= cols) {
|
|
1673
|
+
if (out.length) out[out.length - 1] = "…";
|
|
1674
|
+
else out.push("…");
|
|
1675
|
+
} else {
|
|
1676
|
+
out.push("…");
|
|
1677
|
+
}
|
|
1678
|
+
out.push(RESET_SGR);
|
|
1679
|
+
}
|
|
1680
|
+
return out.join("");
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
function splitSegments(text) {
|
|
1684
|
+
if (!text) return [];
|
|
1685
|
+
const s = String(text);
|
|
1686
|
+
const segments = [];
|
|
1687
|
+
let start = 0;
|
|
1688
|
+
while (true) {
|
|
1689
|
+
const idx = s.indexOf(RESET_SGR, start);
|
|
1690
|
+
if (idx === -1) {
|
|
1691
|
+
const tail = s.slice(start);
|
|
1692
|
+
if (tail) segments.push(tail);
|
|
1693
|
+
break;
|
|
1694
|
+
}
|
|
1695
|
+
const seg = s.slice(start, idx + RESET_SGR.length);
|
|
1696
|
+
if (seg) segments.push(seg);
|
|
1697
|
+
start = idx + RESET_SGR.length;
|
|
1698
|
+
}
|
|
1699
|
+
return segments;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
function wrapSegments(segments, cols) {
|
|
1703
|
+
if (!segments || segments.length === 0) return [""];
|
|
1704
|
+
if (!cols || cols <= 0) return [segments.join("")];
|
|
1705
|
+
|
|
1706
|
+
const lines = [];
|
|
1707
|
+
let cur = [];
|
|
1708
|
+
let curW = 0;
|
|
1709
|
+
|
|
1710
|
+
for (let seg of segments) {
|
|
1711
|
+
let segW = visibleWidth(seg);
|
|
1712
|
+
if (segW <= 0) continue;
|
|
1713
|
+
|
|
1714
|
+
if (cur.length === 0) {
|
|
1715
|
+
if (segW > cols) {
|
|
1716
|
+
seg = clampAnsi(seg, cols);
|
|
1717
|
+
segW = visibleWidth(seg);
|
|
1718
|
+
}
|
|
1719
|
+
cur = [seg];
|
|
1720
|
+
curW = segW;
|
|
1721
|
+
continue;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
if (curW + segW <= cols) {
|
|
1725
|
+
cur.push(seg);
|
|
1726
|
+
curW += segW;
|
|
1727
|
+
} else {
|
|
1728
|
+
lines.push(cur.join(""));
|
|
1729
|
+
if (segW > cols) {
|
|
1730
|
+
seg = clampAnsi(seg, cols);
|
|
1731
|
+
segW = visibleWidth(seg);
|
|
1732
|
+
}
|
|
1733
|
+
cur = [seg];
|
|
1734
|
+
curW = segW;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
if (cur.length) lines.push(cur.join(""));
|
|
1739
|
+
return lines.length ? lines : [""];
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
class StatusRenderer {
|
|
1743
|
+
constructor() {
|
|
1744
|
+
this.raw = "";
|
|
1745
|
+
this.segments = [];
|
|
1746
|
+
this.lines = [""];
|
|
1747
|
+
this.activeReservedRows = RESERVED_ROWS;
|
|
1748
|
+
this.force = false;
|
|
1749
|
+
this.urgent = false;
|
|
1750
|
+
this.lastRenderMs = 0;
|
|
1751
|
+
this.lastChildOutMs = 0;
|
|
1752
|
+
this.cursorVisible = true;
|
|
1753
|
+
}
|
|
1754
|
+
noteChildOutput() {
|
|
1755
|
+
this.lastChildOutMs = Date.now();
|
|
1756
|
+
}
|
|
1757
|
+
setCursorVisible(v) {
|
|
1758
|
+
this.cursorVisible = !!v;
|
|
1759
|
+
}
|
|
1760
|
+
forceRepaint(urgent = false) {
|
|
1761
|
+
this.force = true;
|
|
1762
|
+
if (urgent) this.urgent = true;
|
|
1763
|
+
}
|
|
1764
|
+
setActiveReservedRows(n) {
|
|
1765
|
+
const v = Number(n || 1);
|
|
1766
|
+
this.activeReservedRows = Number.isFinite(v) ? Math.max(1, Math.trunc(v)) : 1;
|
|
1767
|
+
}
|
|
1768
|
+
setLine(line) {
|
|
1769
|
+
const next = String(line || "");
|
|
1770
|
+
if (next !== this.raw) {
|
|
1771
|
+
this.raw = next;
|
|
1772
|
+
this.segments = splitSegments(next);
|
|
1773
|
+
this.force = true;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
desiredReservedRows(physicalRows, cols, minReserved) {
|
|
1777
|
+
let rows = Number(physicalRows || 24);
|
|
1778
|
+
rows = Number.isFinite(rows) ? rows : 24;
|
|
1779
|
+
cols = Number(cols || 80);
|
|
1780
|
+
cols = Number.isFinite(cols) ? cols : 80;
|
|
1781
|
+
|
|
1782
|
+
const maxReserved = Math.max(1, rows - 4);
|
|
1783
|
+
const segs = this.segments.length ? this.segments : (this.raw ? [this.raw] : []);
|
|
1784
|
+
let lines = segs.length ? wrapSegments(segs, cols) : [""];
|
|
1785
|
+
|
|
1786
|
+
const needed = Math.min(lines.length, maxReserved);
|
|
1787
|
+
let desired = Math.max(Number(minReserved || 1), needed);
|
|
1788
|
+
desired = Math.min(desired, maxReserved);
|
|
1789
|
+
|
|
1790
|
+
if (lines.length < desired) lines = new Array(desired - lines.length).fill("").concat(lines);
|
|
1791
|
+
if (lines.length > desired) lines = lines.slice(-desired);
|
|
1792
|
+
|
|
1793
|
+
this.lines = lines;
|
|
1794
|
+
return desired;
|
|
1795
|
+
}
|
|
1796
|
+
clearReservedArea(physicalRows, cols, reservedRows, restoreRow = 1, restoreCol = 1) {
|
|
1797
|
+
let rows = Number(physicalRows || 24);
|
|
1798
|
+
rows = Number.isFinite(rows) ? rows : 24;
|
|
1799
|
+
cols = Number(cols || 80);
|
|
1800
|
+
cols = Number.isFinite(cols) ? cols : 80;
|
|
1801
|
+
let reserved = Number(reservedRows || 1);
|
|
1802
|
+
reserved = Number.isFinite(reserved) ? Math.max(1, Math.trunc(reserved)) : 1;
|
|
1803
|
+
|
|
1804
|
+
reserved = Math.min(reserved, rows);
|
|
1805
|
+
const startRow = rows - reserved + 1;
|
|
1806
|
+
const parts = ["\\x1b[?2026h", "\\x1b[?25l", RESET_SGR];
|
|
1807
|
+
for (let i = 0; i < reserved; i++) parts.push("\\x1b[" + (startRow + i) + ";1H\\x1b[2K");
|
|
1808
|
+
parts.push("\\x1b[" + restoreRow + ";" + restoreCol + "H");
|
|
1809
|
+
parts.push(this.cursorVisible ? "\\x1b[?25h" : "\\x1b[?25l");
|
|
1810
|
+
parts.push("\\x1b[?2026l");
|
|
1811
|
+
writeStdout(parts.join(""));
|
|
1812
|
+
}
|
|
1813
|
+
render(physicalRows, cols, restoreRow = 1, restoreCol = 1) {
|
|
1814
|
+
if (!this.force) return;
|
|
1815
|
+
if (!this.raw) {
|
|
1816
|
+
this.force = false;
|
|
1817
|
+
this.urgent = false;
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
const now = Date.now();
|
|
1821
|
+
if (!this.urgent && now - this.lastRenderMs < MIN_RENDER_INTERVAL_MS) return;
|
|
1822
|
+
if (!this.urgent && QUIET_MS > 0 && now - this.lastChildOutMs < QUIET_MS) return;
|
|
1823
|
+
|
|
1824
|
+
let rows = Number(physicalRows || 24);
|
|
1825
|
+
rows = Number.isFinite(rows) ? rows : 24;
|
|
1826
|
+
cols = Number(cols || 80);
|
|
1827
|
+
cols = Number.isFinite(cols) ? cols : 80;
|
|
1828
|
+
if (cols <= 0) cols = 80;
|
|
1829
|
+
|
|
1830
|
+
const reserved = Math.max(1, Math.min(this.activeReservedRows, Math.max(1, rows - 4)));
|
|
1831
|
+
const startRow = rows - reserved + 1;
|
|
1832
|
+
const childRows = rows - reserved;
|
|
1833
|
+
|
|
1834
|
+
let lines = this.lines.length ? this.lines.slice() : [""];
|
|
1835
|
+
if (lines.length < reserved) lines = new Array(reserved - lines.length).fill("").concat(lines);
|
|
1836
|
+
if (lines.length > reserved) lines = lines.slice(-reserved);
|
|
1837
|
+
|
|
1838
|
+
const parts = ["\\x1b[?2026h", "\\x1b[?25l"];
|
|
1839
|
+
parts.push("\\x1b[1;" + childRows + "r");
|
|
1840
|
+
for (let i = 0; i < reserved; i++) {
|
|
1841
|
+
const row = startRow + i;
|
|
1842
|
+
const text = clampAnsi(lines[i], cols);
|
|
1843
|
+
parts.push("\\x1b[" + row + ";1H" + RESET_SGR + "\\x1b[2K");
|
|
1844
|
+
parts.push("\\x1b[" + row + ";1H" + text + RESET_SGR);
|
|
1845
|
+
}
|
|
1846
|
+
parts.push("\\x1b[" + restoreRow + ";" + restoreCol + "H");
|
|
1847
|
+
parts.push(this.cursorVisible ? "\\x1b[?25h" : "\\x1b[?25l");
|
|
1848
|
+
parts.push("\\x1b[?2026l");
|
|
1849
|
+
writeStdout(parts.join(""));
|
|
1850
|
+
|
|
1851
|
+
this.lastRenderMs = now;
|
|
1852
|
+
this.force = false;
|
|
1853
|
+
this.urgent = false;
|
|
1854
|
+
}
|
|
1855
|
+
clear() {
|
|
1856
|
+
const { rows, cols } = termSize();
|
|
1857
|
+
this.clearReservedArea(rows, cols, Math.max(this.activeReservedRows, RESERVED_ROWS));
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
class OutputRewriter {
|
|
1862
|
+
constructor() {
|
|
1863
|
+
this.buf = new Uint8Array(0);
|
|
1864
|
+
}
|
|
1865
|
+
feed(chunk, maxRow) {
|
|
1866
|
+
if (!chunk || chunk.length === 0) return chunk;
|
|
1867
|
+
const merged = new Uint8Array(this.buf.length + chunk.length);
|
|
1868
|
+
merged.set(this.buf, 0);
|
|
1869
|
+
merged.set(chunk, this.buf.length);
|
|
1870
|
+
this.buf = new Uint8Array(0);
|
|
1871
|
+
|
|
1872
|
+
const out = [];
|
|
1873
|
+
let i = 0;
|
|
1874
|
+
|
|
1875
|
+
const isFinal = (v) => v >= 0x40 && v <= 0x7e;
|
|
1876
|
+
|
|
1877
|
+
while (i < merged.length) {
|
|
1878
|
+
const b = merged[i];
|
|
1879
|
+
if (b !== 0x1b) {
|
|
1880
|
+
out.push(b);
|
|
1881
|
+
i += 1;
|
|
1882
|
+
continue;
|
|
1883
|
+
}
|
|
1884
|
+
if (i + 1 >= merged.length) {
|
|
1885
|
+
this.buf = merged.slice(i);
|
|
1886
|
+
break;
|
|
1887
|
+
}
|
|
1888
|
+
const nxt = merged[i + 1];
|
|
1889
|
+
if (nxt !== 0x5b) {
|
|
1890
|
+
out.push(b);
|
|
1891
|
+
i += 1;
|
|
1892
|
+
continue;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
let j = i + 2;
|
|
1896
|
+
while (j < merged.length && !isFinal(merged[j])) j += 1;
|
|
1897
|
+
if (j >= merged.length) {
|
|
1898
|
+
this.buf = merged.slice(i);
|
|
1899
|
+
break;
|
|
1900
|
+
}
|
|
1901
|
+
const final = merged[j];
|
|
1902
|
+
let seq = merged.slice(i, j + 1);
|
|
1903
|
+
|
|
1904
|
+
if ((final === 0x48 || final === 0x66) && maxRow > 0) {
|
|
1905
|
+
const params = merged.slice(i + 2, j);
|
|
1906
|
+
const s = new TextDecoder().decode(params);
|
|
1907
|
+
if (!s || /^[0-9;]/.test(s)) {
|
|
1908
|
+
const parts = s ? s.split(";") : [];
|
|
1909
|
+
const row = Number(parts[0] || 1);
|
|
1910
|
+
const col = Number(parts[1] || 1);
|
|
1911
|
+
let r = Number.isFinite(row) ? row : 1;
|
|
1912
|
+
let c = Number.isFinite(col) ? col : 1;
|
|
1913
|
+
if (r === 999 || r > maxRow) r = maxRow;
|
|
1914
|
+
if (r < 1) r = 1;
|
|
1915
|
+
if (c < 1) c = 1;
|
|
1916
|
+
const newParams = new TextEncoder().encode(String(r) + ";" + String(c));
|
|
1917
|
+
const ns = new Uint8Array(2 + newParams.length + 1);
|
|
1918
|
+
ns[0] = 0x1b;
|
|
1919
|
+
ns[1] = 0x5b;
|
|
1920
|
+
ns.set(newParams, 2);
|
|
1921
|
+
ns[ns.length - 1] = final;
|
|
1922
|
+
seq = ns;
|
|
1923
|
+
}
|
|
1924
|
+
} else if (final === 0x72 && maxRow > 0) {
|
|
1925
|
+
const params = merged.slice(i + 2, j);
|
|
1926
|
+
const s = new TextDecoder().decode(params);
|
|
1927
|
+
if (!s || /^[0-9;]/.test(s)) {
|
|
1928
|
+
const parts = s ? s.split(";") : [];
|
|
1929
|
+
const top = Number(parts[0] || 1);
|
|
1930
|
+
const bottom = Number(parts[1] || maxRow);
|
|
1931
|
+
let t = Number.isFinite(top) ? top : 1;
|
|
1932
|
+
let btm = Number.isFinite(bottom) ? bottom : maxRow;
|
|
1933
|
+
if (t <= 0) t = 1;
|
|
1934
|
+
if (btm <= 0 || btm === 999 || btm > maxRow) btm = maxRow;
|
|
1935
|
+
if (t > btm) t = 1;
|
|
1936
|
+
const str = "\\x1b[" + String(t) + ";" + String(btm) + "r";
|
|
1937
|
+
seq = new TextEncoder().encode(str);
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
for (const bb of seq) out.push(bb);
|
|
1942
|
+
i = j + 1;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
return new Uint8Array(out);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
class CursorTracker {
|
|
1950
|
+
constructor() {
|
|
1951
|
+
this.row = 1;
|
|
1952
|
+
this.col = 1;
|
|
1953
|
+
this.savedRow = 1;
|
|
1954
|
+
this.savedCol = 1;
|
|
1955
|
+
this.buf = new Uint8Array(0);
|
|
1956
|
+
this.inOsc = false;
|
|
1957
|
+
this.utf8Cont = 0;
|
|
1958
|
+
this.wrapPending = false;
|
|
1959
|
+
}
|
|
1960
|
+
position() {
|
|
1961
|
+
return { row: this.row, col: this.col };
|
|
1962
|
+
}
|
|
1963
|
+
feed(chunk, maxRow, maxCol) {
|
|
1964
|
+
if (!chunk || chunk.length === 0) return;
|
|
1965
|
+
maxRow = Math.max(1, Number(maxRow || 1));
|
|
1966
|
+
maxCol = Math.max(1, Number(maxCol || 1));
|
|
1967
|
+
|
|
1968
|
+
const merged = new Uint8Array(this.buf.length + chunk.length);
|
|
1969
|
+
merged.set(this.buf, 0);
|
|
1970
|
+
merged.set(chunk, this.buf.length);
|
|
1971
|
+
this.buf = new Uint8Array(0);
|
|
1972
|
+
|
|
1973
|
+
const clamp = () => {
|
|
1974
|
+
if (this.row < 1) this.row = 1;
|
|
1975
|
+
else if (this.row > maxRow) this.row = maxRow;
|
|
1976
|
+
if (this.col < 1) this.col = 1;
|
|
1977
|
+
else if (this.col > maxCol) this.col = maxCol;
|
|
1978
|
+
};
|
|
1979
|
+
|
|
1980
|
+
const parseIntDefault = (v, d) => {
|
|
1981
|
+
const n = Number(v);
|
|
1982
|
+
return Number.isFinite(n) && n > 0 ? Math.trunc(n) : d;
|
|
1983
|
+
};
|
|
1984
|
+
|
|
1985
|
+
let i = 0;
|
|
1986
|
+
const isFinal = (v) => v >= 0x40 && v <= 0x7e;
|
|
1987
|
+
|
|
1988
|
+
while (i < merged.length) {
|
|
1989
|
+
const b = merged[i];
|
|
1990
|
+
|
|
1991
|
+
if (this.inOsc) {
|
|
1992
|
+
if (b === 0x07) {
|
|
1993
|
+
this.inOsc = false;
|
|
1994
|
+
i += 1;
|
|
1995
|
+
continue;
|
|
1996
|
+
}
|
|
1997
|
+
if (b === 0x1b) {
|
|
1998
|
+
if (i + 1 >= merged.length) {
|
|
1999
|
+
this.buf = merged.slice(i);
|
|
2000
|
+
break;
|
|
2001
|
+
}
|
|
2002
|
+
if (merged[i + 1] === 0x5c) {
|
|
2003
|
+
this.inOsc = false;
|
|
2004
|
+
i += 2;
|
|
2005
|
+
continue;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
i += 1;
|
|
2009
|
+
continue;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
if (this.utf8Cont > 0) {
|
|
2013
|
+
if (b >= 0x80 && b <= 0xbf) {
|
|
2014
|
+
this.utf8Cont -= 1;
|
|
2015
|
+
i += 1;
|
|
2016
|
+
continue;
|
|
2017
|
+
}
|
|
2018
|
+
this.utf8Cont = 0;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
if (b === 0x1b) {
|
|
2022
|
+
this.wrapPending = false;
|
|
2023
|
+
if (i + 1 >= merged.length) {
|
|
2024
|
+
this.buf = merged.slice(i);
|
|
2025
|
+
break;
|
|
2026
|
+
}
|
|
2027
|
+
const nxt = merged[i + 1];
|
|
2028
|
+
|
|
2029
|
+
if (nxt === 0x5b) {
|
|
2030
|
+
let j = i + 2;
|
|
2031
|
+
while (j < merged.length && !isFinal(merged[j])) j += 1;
|
|
2032
|
+
if (j >= merged.length) {
|
|
2033
|
+
this.buf = merged.slice(i);
|
|
2034
|
+
break;
|
|
2035
|
+
}
|
|
2036
|
+
const final = merged[j];
|
|
2037
|
+
const params = merged.slice(i + 2, j);
|
|
2038
|
+
const s = new TextDecoder().decode(params);
|
|
2039
|
+
if (s && !/^[0-9;]/.test(s)) {
|
|
2040
|
+
i = j + 1;
|
|
2041
|
+
continue;
|
|
2042
|
+
}
|
|
2043
|
+
const parts = s ? s.split(";") : [];
|
|
2044
|
+
const p0 = parseIntDefault(parts[0] || "", 1);
|
|
2045
|
+
const p1 = parseIntDefault(parts[1] || "", 1);
|
|
2046
|
+
|
|
2047
|
+
if (final === 0x48 || final === 0x66) {
|
|
2048
|
+
this.row = p0;
|
|
2049
|
+
this.col = p1;
|
|
2050
|
+
clamp();
|
|
2051
|
+
} else if (final === 0x41) {
|
|
2052
|
+
this.row = Math.max(1, this.row - p0);
|
|
2053
|
+
} else if (final === 0x42) {
|
|
2054
|
+
this.row = Math.min(maxRow, this.row + p0);
|
|
2055
|
+
} else if (final === 0x43) {
|
|
2056
|
+
this.col = Math.min(maxCol, this.col + p0);
|
|
2057
|
+
} else if (final === 0x44) {
|
|
2058
|
+
this.col = Math.max(1, this.col - p0);
|
|
2059
|
+
} else if (final === 0x45) {
|
|
2060
|
+
this.row = Math.min(maxRow, this.row + p0);
|
|
2061
|
+
this.col = 1;
|
|
2062
|
+
} else if (final === 0x46) {
|
|
2063
|
+
this.row = Math.max(1, this.row - p0);
|
|
2064
|
+
this.col = 1;
|
|
2065
|
+
} else if (final === 0x47) {
|
|
2066
|
+
this.col = p0;
|
|
2067
|
+
clamp();
|
|
2068
|
+
} else if (final === 0x64) {
|
|
2069
|
+
this.row = p0;
|
|
2070
|
+
clamp();
|
|
2071
|
+
} else if (final === 0x72) {
|
|
2072
|
+
this.row = 1;
|
|
2073
|
+
this.col = 1;
|
|
2074
|
+
} else if (final === 0x73) {
|
|
2075
|
+
this.savedRow = this.row;
|
|
2076
|
+
this.savedCol = this.col;
|
|
2077
|
+
} else if (final === 0x75) {
|
|
2078
|
+
this.row = this.savedRow;
|
|
2079
|
+
this.col = this.savedCol;
|
|
2080
|
+
clamp();
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
i = j + 1;
|
|
2084
|
+
continue;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
if (nxt === 0x5d || nxt === 0x50 || nxt === 0x5e || nxt === 0x5f || nxt === 0x58) {
|
|
2088
|
+
this.inOsc = true;
|
|
2089
|
+
i += 2;
|
|
2090
|
+
continue;
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
if (nxt === 0x37) {
|
|
2094
|
+
this.savedRow = this.row;
|
|
2095
|
+
this.savedCol = this.col;
|
|
2096
|
+
i += 2;
|
|
2097
|
+
continue;
|
|
2098
|
+
}
|
|
2099
|
+
if (nxt === 0x38) {
|
|
2100
|
+
this.row = this.savedRow;
|
|
2101
|
+
this.col = this.savedCol;
|
|
2102
|
+
clamp();
|
|
2103
|
+
i += 2;
|
|
2104
|
+
continue;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
i += 2;
|
|
2108
|
+
continue;
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
if (b === 0x0d) {
|
|
2112
|
+
this.col = 1;
|
|
2113
|
+
this.wrapPending = false;
|
|
2114
|
+
i += 1;
|
|
2115
|
+
continue;
|
|
2116
|
+
}
|
|
2117
|
+
if (b === 0x0a || b === 0x0b || b === 0x0c) {
|
|
2118
|
+
this.row = Math.min(maxRow, this.row + 1);
|
|
2119
|
+
this.wrapPending = false;
|
|
2120
|
+
i += 1;
|
|
2121
|
+
continue;
|
|
2122
|
+
}
|
|
2123
|
+
if (b === 0x08) {
|
|
2124
|
+
this.col = Math.max(1, this.col - 1);
|
|
2125
|
+
this.wrapPending = false;
|
|
2126
|
+
i += 1;
|
|
2127
|
+
continue;
|
|
2128
|
+
}
|
|
2129
|
+
if (b === 0x09) {
|
|
2130
|
+
const nextStop = Math.floor((this.col - 1) / 8 + 1) * 8 + 1;
|
|
2131
|
+
this.col = Math.min(maxCol, nextStop);
|
|
2132
|
+
this.wrapPending = false;
|
|
2133
|
+
i += 1;
|
|
2134
|
+
continue;
|
|
2135
|
+
}
|
|
2136
|
+
if (b < 0x20 || b === 0x7f) {
|
|
2137
|
+
i += 1;
|
|
2138
|
+
continue;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
if (this.wrapPending) {
|
|
2142
|
+
this.row = Math.min(maxRow, this.row + 1);
|
|
2143
|
+
this.col = 1;
|
|
2144
|
+
this.wrapPending = false;
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
if (b >= 0x80) {
|
|
2148
|
+
if ((b & 0xe0) === 0xc0) this.utf8Cont = 1;
|
|
2149
|
+
else if ((b & 0xf0) === 0xe0) this.utf8Cont = 2;
|
|
2150
|
+
else if ((b & 0xf8) === 0xf0) this.utf8Cont = 3;
|
|
2151
|
+
else this.utf8Cont = 0;
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
if (this.col < maxCol) this.col += 1;
|
|
2155
|
+
else {
|
|
2156
|
+
this.col = maxCol;
|
|
2157
|
+
this.wrapPending = true;
|
|
2158
|
+
}
|
|
2159
|
+
i += 1;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
async function main() {
|
|
2165
|
+
const argv = process.argv.slice(2);
|
|
2166
|
+
|
|
2167
|
+
if (isSessionsCommand(argv)) await runSessions();
|
|
2168
|
+
|
|
2169
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || shouldPassthrough(argv)) {
|
|
2170
|
+
await execPassthrough(argv);
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
// Clean viewport.
|
|
2175
|
+
writeStdout("\\x1b[?2026h\\x1b[0m\\x1b[r\\x1b[2J\\x1b[H\\x1b[?2026l");
|
|
2176
|
+
|
|
2177
|
+
const renderer = new StatusRenderer();
|
|
2178
|
+
renderer.setLine("\\x1b[48;5;238m\\x1b[38;5;15m Statusline: starting… \\x1b[0m");
|
|
2179
|
+
renderer.forceRepaint(true);
|
|
2180
|
+
|
|
2181
|
+
let { rows: physicalRows, cols: physicalCols } = termSize();
|
|
2182
|
+
let effectiveReservedRows = renderer.desiredReservedRows(physicalRows, physicalCols, RESERVED_ROWS);
|
|
2183
|
+
renderer.setActiveReservedRows(effectiveReservedRows);
|
|
2184
|
+
let childRows = Math.max(4, physicalRows - effectiveReservedRows);
|
|
2185
|
+
let childCols = Math.max(10, physicalCols);
|
|
2186
|
+
|
|
2187
|
+
// Reserve the bottom rows early, before the child starts writing.
|
|
2188
|
+
writeStdout(
|
|
2189
|
+
"\\x1b[?2026h\\x1b[?25l\\x1b[1;" + childRows + "r\\x1b[1;1H\\x1b[?25h\\x1b[?2026l",
|
|
2190
|
+
);
|
|
2191
|
+
renderer.forceRepaint(true);
|
|
2192
|
+
renderer.render(physicalRows, physicalCols, 1, 1);
|
|
2193
|
+
|
|
2194
|
+
// Spawn child with terminal support.
|
|
2195
|
+
let child;
|
|
2196
|
+
try {
|
|
2197
|
+
child = Bun.spawn([EXEC_TARGET, ...argv], {
|
|
2198
|
+
cwd: process.cwd(),
|
|
2199
|
+
env: process.env,
|
|
2200
|
+
detached: true,
|
|
2201
|
+
terminal: {
|
|
2202
|
+
cols: childCols,
|
|
2203
|
+
rows: childRows,
|
|
2204
|
+
data(_terminal, data) {
|
|
2205
|
+
onChildData(data);
|
|
2206
|
+
},
|
|
2207
|
+
},
|
|
2208
|
+
onExit(_proc, exitCode, signal, _error) {
|
|
2209
|
+
onChildExit(exitCode, signal);
|
|
2210
|
+
},
|
|
2211
|
+
});
|
|
2212
|
+
} catch (e) {
|
|
2213
|
+
process.stderr.write("[statusline] failed to spawn child: " + String(e?.message || e) + "\\n");
|
|
2214
|
+
process.exit(1);
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
const terminal = child.terminal;
|
|
2218
|
+
|
|
2219
|
+
// Best-effort PGID resolution (matches Python wrapper behavior).
|
|
2220
|
+
// This improves session resolution (ps/lsof scanning) and signal forwarding.
|
|
2221
|
+
let pgid = child.pid;
|
|
2222
|
+
try {
|
|
2223
|
+
const res = Bun.spawnSync(["ps", "-o", "pgid=", "-p", String(child.pid)], {
|
|
2224
|
+
stdin: "ignore",
|
|
2225
|
+
stdout: "pipe",
|
|
2226
|
+
stderr: "ignore",
|
|
2227
|
+
});
|
|
2228
|
+
if (res && res.exitCode === 0 && res.stdout) {
|
|
2229
|
+
const text = new TextDecoder().decode(res.stdout).trim();
|
|
2230
|
+
const n = Number(text);
|
|
2231
|
+
if (Number.isFinite(n) && n > 0) pgid = Math.trunc(n);
|
|
2232
|
+
}
|
|
2233
|
+
} catch {}
|
|
2234
|
+
|
|
2235
|
+
// Spawn monitor (Node).
|
|
2236
|
+
const monitorEnv = { ...process.env, DROID_STATUSLINE_PGID: String(pgid) };
|
|
2237
|
+
const monitor = Bun.spawn(["node", STATUSLINE_MONITOR, ...argv], {
|
|
2238
|
+
stdin: "ignore",
|
|
2239
|
+
stdout: "pipe",
|
|
2240
|
+
stderr: "ignore",
|
|
2241
|
+
env: monitorEnv,
|
|
2242
|
+
});
|
|
2243
|
+
|
|
2244
|
+
let shouldStop = false;
|
|
2245
|
+
const rewriter = new OutputRewriter();
|
|
2246
|
+
const cursor = new CursorTracker();
|
|
2247
|
+
|
|
2248
|
+
let detectBuf = new Uint8Array(0);
|
|
2249
|
+
let detectStr = "";
|
|
2250
|
+
let cursorVisible = true;
|
|
2251
|
+
let scrollRegionDirty = true;
|
|
2252
|
+
let lastForceRepaintMs = Date.now();
|
|
2253
|
+
let lastPhysicalRows = 0;
|
|
2254
|
+
let lastPhysicalCols = 0;
|
|
2255
|
+
|
|
2256
|
+
function appendDetect(chunk) {
|
|
2257
|
+
const max = 128;
|
|
2258
|
+
const merged = new Uint8Array(Math.min(max, detectBuf.length + chunk.length));
|
|
2259
|
+
const takePrev = Math.max(0, merged.length - chunk.length);
|
|
2260
|
+
if (takePrev > 0) merged.set(detectBuf.slice(Math.max(0, detectBuf.length - takePrev)), 0);
|
|
2261
|
+
merged.set(chunk.slice(Math.max(0, chunk.length - (merged.length - takePrev))), takePrev);
|
|
2262
|
+
detectBuf = merged;
|
|
2263
|
+
try {
|
|
2264
|
+
detectStr = Buffer.from(detectBuf).toString("latin1");
|
|
2265
|
+
} catch {
|
|
2266
|
+
detectStr = "";
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
function includesBytes(needle) {
|
|
2271
|
+
return detectStr.includes(needle);
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
function lastIndexOfBytes(needle) {
|
|
2275
|
+
return detectStr.lastIndexOf(needle);
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
function includesScrollRegionCSI() {
|
|
2279
|
+
// Equivalent to Python: re.search(b"\\x1b\\\\[[0-9]*;?[0-9]*r", detect_buf)
|
|
2280
|
+
try {
|
|
2281
|
+
return /\\x1b\\[[0-9]*;?[0-9]*r/.test(detectStr);
|
|
2282
|
+
} catch {
|
|
2283
|
+
return false;
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
function updateCursorVisibility() {
|
|
2288
|
+
const show = includesBytes("\\x1b[?25h");
|
|
2289
|
+
const hide = includesBytes("\\x1b[?25l");
|
|
2290
|
+
if (show || hide) {
|
|
2291
|
+
// best-effort: if both present, whichever appears later "wins"
|
|
2292
|
+
const h = lastIndexOfBytes("\\x1b[?25h");
|
|
2293
|
+
const l = lastIndexOfBytes("\\x1b[?25l");
|
|
2294
|
+
cursorVisible = h > l;
|
|
2295
|
+
renderer.setCursorVisible(cursorVisible);
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
function needsScrollRegionReset() {
|
|
2300
|
+
return (
|
|
2301
|
+
includesBytes("\\x1b[?1049") ||
|
|
2302
|
+
includesBytes("\\x1b[?1047") ||
|
|
2303
|
+
includesBytes("\\x1b[?47") ||
|
|
2304
|
+
includesBytes("\\x1b[J") ||
|
|
2305
|
+
includesBytes("\\x1b[0J") ||
|
|
2306
|
+
includesBytes("\\x1b[1J") ||
|
|
2307
|
+
includesBytes("\\x1b[2J") ||
|
|
2308
|
+
includesBytes("\\x1b[3J") ||
|
|
2309
|
+
includesBytes("\\x1b[r") ||
|
|
2310
|
+
includesScrollRegionCSI()
|
|
2311
|
+
);
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
function onChildData(data) {
|
|
2315
|
+
if (shouldStop) return;
|
|
2316
|
+
const chunk = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
2317
|
+
appendDetect(chunk);
|
|
2318
|
+
if (needsScrollRegionReset()) scrollRegionDirty = true;
|
|
2319
|
+
updateCursorVisibility();
|
|
2320
|
+
|
|
2321
|
+
renderer.noteChildOutput();
|
|
2322
|
+
const rewritten = rewriter.feed(chunk, childRows);
|
|
2323
|
+
cursor.feed(rewritten, childRows, childCols);
|
|
2324
|
+
writeStdout(Buffer.from(rewritten));
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
function onChildExit(exitCode, signal) {
|
|
2328
|
+
if (shouldStop) return;
|
|
2329
|
+
shouldStop = true;
|
|
2330
|
+
const code = exitCode ?? (signal != null ? 128 + signal : 0);
|
|
2331
|
+
cleanup().finally(() => process.exit(code));
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
async function readMonitor() {
|
|
2335
|
+
if (!monitor.stdout) return;
|
|
2336
|
+
const reader = monitor.stdout.getReader();
|
|
2337
|
+
let buf = "";
|
|
2338
|
+
while (!shouldStop) {
|
|
2339
|
+
const { value, done } = await reader.read();
|
|
2340
|
+
if (done || !value) break;
|
|
2341
|
+
buf += new TextDecoder().decode(value);
|
|
2342
|
+
while (true) {
|
|
2343
|
+
const idx = buf.indexOf("\\n");
|
|
2344
|
+
if (idx === -1) break;
|
|
2345
|
+
const line = buf.slice(0, idx).replace(/\\r$/, "");
|
|
2346
|
+
buf = buf.slice(idx + 1);
|
|
2347
|
+
if (!line) continue;
|
|
2348
|
+
renderer.setLine(line);
|
|
2349
|
+
renderer.forceRepaint(false);
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
readMonitor().catch(() => {});
|
|
2354
|
+
|
|
2355
|
+
function repaintStatusline(forceUrgent = false) {
|
|
2356
|
+
const { row, col } = cursor.position();
|
|
2357
|
+
let r = Math.max(1, Math.min(childRows, row));
|
|
2358
|
+
let c = Math.max(1, Math.min(childCols, col));
|
|
2359
|
+
|
|
2360
|
+
if (scrollRegionDirty) {
|
|
2361
|
+
const seq =
|
|
2362
|
+
"\\x1b[?2026h\\x1b[?25l\\x1b[1;" +
|
|
2363
|
+
childRows +
|
|
2364
|
+
"r\\x1b[" +
|
|
2365
|
+
r +
|
|
2366
|
+
";" +
|
|
2367
|
+
c +
|
|
2368
|
+
"H" +
|
|
2369
|
+
(cursorVisible ? "\\x1b[?25h" : "\\x1b[?25l") +
|
|
2370
|
+
"\\x1b[?2026l";
|
|
2371
|
+
writeStdout(seq);
|
|
2372
|
+
scrollRegionDirty = false;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
renderer.forceRepaint(forceUrgent);
|
|
2376
|
+
renderer.render(physicalRows, physicalCols, r, c);
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
function handleSizeChange(nextRows, nextCols, forceUrgent = false) {
|
|
2380
|
+
physicalRows = nextRows;
|
|
2381
|
+
physicalCols = nextCols;
|
|
2382
|
+
|
|
2383
|
+
const desired = renderer.desiredReservedRows(physicalRows, physicalCols, RESERVED_ROWS);
|
|
2384
|
+
const { row, col } = cursor.position();
|
|
2385
|
+
if (desired < effectiveReservedRows) {
|
|
2386
|
+
renderer.clearReservedArea(physicalRows, physicalCols, effectiveReservedRows, row, col);
|
|
2387
|
+
}
|
|
2388
|
+
effectiveReservedRows = desired;
|
|
2389
|
+
renderer.setActiveReservedRows(effectiveReservedRows);
|
|
2390
|
+
|
|
2391
|
+
childRows = Math.max(4, physicalRows - effectiveReservedRows);
|
|
2392
|
+
childCols = Math.max(10, physicalCols);
|
|
2393
|
+
try {
|
|
2394
|
+
terminal.resize(childCols, childRows);
|
|
2395
|
+
} catch {}
|
|
2396
|
+
try {
|
|
2397
|
+
process.kill(-child.pid, "SIGWINCH");
|
|
2398
|
+
} catch {
|
|
2399
|
+
try { process.kill(child.pid, "SIGWINCH"); } catch {}
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
scrollRegionDirty = true;
|
|
2403
|
+
renderer.forceRepaint(true);
|
|
2404
|
+
repaintStatusline(forceUrgent);
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
process.on("SIGWINCH", () => {
|
|
2408
|
+
const next = termSize();
|
|
2409
|
+
handleSizeChange(next.rows, next.cols, true);
|
|
2410
|
+
});
|
|
2411
|
+
|
|
2412
|
+
// Forward signals to child's process group when possible.
|
|
2413
|
+
const forward = (sig) => {
|
|
2414
|
+
try {
|
|
2415
|
+
process.kill(-pgid, sig);
|
|
2416
|
+
} catch {
|
|
2417
|
+
try {
|
|
2418
|
+
process.kill(child.pid, sig);
|
|
2419
|
+
} catch {}
|
|
2420
|
+
}
|
|
2421
|
+
};
|
|
2422
|
+
for (const s of ["SIGTERM", "SIGINT", "SIGHUP"]) {
|
|
2423
|
+
try {
|
|
2424
|
+
process.on(s, () => forward(s));
|
|
2425
|
+
} catch {}
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
// Raw stdin -> PTY.
|
|
2429
|
+
try {
|
|
2430
|
+
process.stdin.setRawMode(true);
|
|
2431
|
+
} catch {}
|
|
2432
|
+
process.stdin.resume();
|
|
2433
|
+
process.stdin.on("data", (buf) => {
|
|
2434
|
+
try {
|
|
2435
|
+
if (typeof buf === "string") terminal.write(buf);
|
|
2436
|
+
else {
|
|
2437
|
+
// Prefer bytes when supported; fall back to UTF-8 decoding.
|
|
2438
|
+
try {
|
|
2439
|
+
// Bun.Terminal.write may accept Uint8Array in newer versions.
|
|
2440
|
+
terminal.write(buf);
|
|
2441
|
+
} catch {
|
|
2442
|
+
terminal.write(new TextDecoder().decode(buf));
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
} catch {}
|
|
2446
|
+
});
|
|
2447
|
+
|
|
2448
|
+
const tick = setInterval(() => {
|
|
2449
|
+
if (shouldStop) return;
|
|
2450
|
+
const next = termSize();
|
|
2451
|
+
const sizeChanged = next.rows !== lastPhysicalRows || next.cols !== lastPhysicalCols;
|
|
2452
|
+
const desired = renderer.desiredReservedRows(next.rows, next.cols, RESERVED_ROWS);
|
|
2453
|
+
if (sizeChanged || desired !== effectiveReservedRows) {
|
|
2454
|
+
handleSizeChange(next.rows, next.cols, true);
|
|
2455
|
+
lastPhysicalRows = next.rows;
|
|
2456
|
+
lastPhysicalCols = next.cols;
|
|
2457
|
+
lastForceRepaintMs = Date.now();
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2460
|
+
const now = Date.now();
|
|
2461
|
+
if (now - lastForceRepaintMs >= FORCE_REPAINT_INTERVAL_MS) {
|
|
2462
|
+
repaintStatusline(false);
|
|
2463
|
+
lastForceRepaintMs = now;
|
|
2464
|
+
} else {
|
|
2465
|
+
const { row, col } = cursor.position();
|
|
2466
|
+
renderer.render(physicalRows, physicalCols, row, col);
|
|
2467
|
+
}
|
|
2468
|
+
}, 50);
|
|
2469
|
+
|
|
2470
|
+
async function cleanup() {
|
|
2471
|
+
clearInterval(tick);
|
|
2472
|
+
try {
|
|
2473
|
+
process.stdin.setRawMode(false);
|
|
2474
|
+
} catch {}
|
|
2475
|
+
try {
|
|
2476
|
+
const { row, col } = cursor.position();
|
|
2477
|
+
renderer.clearReservedArea(physicalRows, physicalCols, effectiveReservedRows, row, col);
|
|
2478
|
+
} catch {}
|
|
2479
|
+
try {
|
|
2480
|
+
writeStdout("\\x1b[r\\x1b[0m\\x1b[?25h");
|
|
2481
|
+
} catch {}
|
|
2482
|
+
try {
|
|
2483
|
+
monitor.kill();
|
|
2484
|
+
} catch {}
|
|
2485
|
+
try {
|
|
2486
|
+
terminal.close();
|
|
2487
|
+
} catch {}
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
// Keep process alive until child exits.
|
|
2491
|
+
await child.exited;
|
|
2492
|
+
await cleanup();
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
main().catch(() => process.exit(1));
|
|
2382
2496
|
`;
|
|
2383
2497
|
}
|
|
2384
|
-
async function createStatuslineFiles(outputDir, execTargetPath, aliasName) {
|
|
2498
|
+
async function createStatuslineFiles(outputDir, execTargetPath, aliasName, sessionsScriptPath) {
|
|
2385
2499
|
if (!existsSync(outputDir)) await mkdir(outputDir, { recursive: true });
|
|
2386
2500
|
const monitorScriptPath = join(outputDir, `${aliasName}-statusline.js`);
|
|
2387
2501
|
const wrapperScriptPath = join(outputDir, aliasName);
|
|
2388
2502
|
await writeFile(monitorScriptPath, generateStatuslineMonitorScript());
|
|
2389
2503
|
await chmod(monitorScriptPath, 493);
|
|
2390
|
-
await writeFile(wrapperScriptPath, generateStatuslineWrapperScript(execTargetPath, monitorScriptPath));
|
|
2504
|
+
await writeFile(wrapperScriptPath, generateStatuslineWrapperScript(execTargetPath, monitorScriptPath, sessionsScriptPath));
|
|
2391
2505
|
await chmod(wrapperScriptPath, 493);
|
|
2392
2506
|
return {
|
|
2393
2507
|
wrapperScript: wrapperScriptPath,
|
|
@@ -2395,6 +2509,294 @@ async function createStatuslineFiles(outputDir, execTargetPath, aliasName) {
|
|
|
2395
2509
|
};
|
|
2396
2510
|
}
|
|
2397
2511
|
|
|
2512
|
+
//#endregion
|
|
2513
|
+
//#region src/sessions-patch.ts
|
|
2514
|
+
/**
|
|
2515
|
+
* Generate sessions browser script (Node.js)
|
|
2516
|
+
*/
|
|
2517
|
+
function generateSessionsBrowserScript(aliasName) {
|
|
2518
|
+
return `#!/usr/bin/env node
|
|
2519
|
+
// Droid Sessions Browser - Interactive selector
|
|
2520
|
+
// Auto-generated by droid-patch
|
|
2521
|
+
|
|
2522
|
+
const fs = require('fs');
|
|
2523
|
+
const path = require('path');
|
|
2524
|
+
const readline = require('readline');
|
|
2525
|
+
const { execSync, spawn } = require('child_process');
|
|
2526
|
+
|
|
2527
|
+
const FACTORY_HOME = path.join(require('os').homedir(), '.factory');
|
|
2528
|
+
const SESSIONS_ROOT = path.join(FACTORY_HOME, 'sessions');
|
|
2529
|
+
const ALIAS_NAME = ${JSON.stringify(aliasName)};
|
|
2530
|
+
|
|
2531
|
+
// ANSI
|
|
2532
|
+
const CYAN = '\\x1b[36m';
|
|
2533
|
+
const GREEN = '\\x1b[32m';
|
|
2534
|
+
const YELLOW = '\\x1b[33m';
|
|
2535
|
+
const RED = '\\x1b[31m';
|
|
2536
|
+
const DIM = '\\x1b[2m';
|
|
2537
|
+
const RESET = '\\x1b[0m';
|
|
2538
|
+
const BOLD = '\\x1b[1m';
|
|
2539
|
+
const CLEAR = '\\x1b[2J\\x1b[H';
|
|
2540
|
+
const HIDE_CURSOR = '\\x1b[?25l';
|
|
2541
|
+
const SHOW_CURSOR = '\\x1b[?25h';
|
|
2542
|
+
|
|
2543
|
+
function sanitizePath(p) {
|
|
2544
|
+
return p.replace(/:/g, '').replace(/[\\\\/]/g, '-');
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
function parseSessionFile(jsonlPath, settingsPath) {
|
|
2548
|
+
const sessionId = path.basename(jsonlPath, '.jsonl');
|
|
2549
|
+
const stats = fs.statSync(jsonlPath);
|
|
2550
|
+
|
|
2551
|
+
const result = {
|
|
2552
|
+
id: sessionId,
|
|
2553
|
+
title: '',
|
|
2554
|
+
mtime: stats.mtimeMs,
|
|
2555
|
+
model: '',
|
|
2556
|
+
firstUserMsg: '',
|
|
2557
|
+
lastUserMsg: '',
|
|
2558
|
+
messageCount: 0,
|
|
2559
|
+
lastTimestamp: '',
|
|
2560
|
+
};
|
|
2561
|
+
|
|
2562
|
+
try {
|
|
2563
|
+
const content = fs.readFileSync(jsonlPath, 'utf-8');
|
|
2564
|
+
const lines = content.split('\\n').filter(l => l.trim());
|
|
2565
|
+
const userMessages = [];
|
|
2566
|
+
|
|
2567
|
+
for (const line of lines) {
|
|
2568
|
+
try {
|
|
2569
|
+
const obj = JSON.parse(line);
|
|
2570
|
+
if (obj.type === 'session_start') {
|
|
2571
|
+
result.title = obj.title || '';
|
|
2572
|
+
} else if (obj.type === 'message') {
|
|
2573
|
+
result.messageCount++;
|
|
2574
|
+
if (obj.timestamp) result.lastTimestamp = obj.timestamp;
|
|
2575
|
+
|
|
2576
|
+
const msg = obj.message || {};
|
|
2577
|
+
if (msg.role === 'user' && Array.isArray(msg.content)) {
|
|
2578
|
+
for (const c of msg.content) {
|
|
2579
|
+
if (c && c.type === 'text' && c.text && !c.text.startsWith('<system-reminder>')) {
|
|
2580
|
+
userMessages.push(c.text.slice(0, 150).replace(/\\n/g, ' ').trim());
|
|
2581
|
+
break;
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
} catch {}
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
if (userMessages.length > 0) {
|
|
2590
|
+
result.firstUserMsg = userMessages[0];
|
|
2591
|
+
result.lastUserMsg = userMessages.length > 1 ? userMessages[userMessages.length - 1] : '';
|
|
2592
|
+
}
|
|
2593
|
+
} catch {}
|
|
2594
|
+
|
|
2595
|
+
if (fs.existsSync(settingsPath)) {
|
|
2596
|
+
try {
|
|
2597
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
2598
|
+
result.model = settings.model || '';
|
|
2599
|
+
} catch {}
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
return result;
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
function collectSessions() {
|
|
2606
|
+
const cwd = process.cwd();
|
|
2607
|
+
const cwdSanitized = sanitizePath(cwd);
|
|
2608
|
+
const sessions = [];
|
|
2609
|
+
|
|
2610
|
+
if (!fs.existsSync(SESSIONS_ROOT)) return sessions;
|
|
2611
|
+
|
|
2612
|
+
for (const wsDir of fs.readdirSync(SESSIONS_ROOT)) {
|
|
2613
|
+
if (wsDir !== cwdSanitized) continue;
|
|
2614
|
+
|
|
2615
|
+
const wsPath = path.join(SESSIONS_ROOT, wsDir);
|
|
2616
|
+
if (!fs.statSync(wsPath).isDirectory()) continue;
|
|
2617
|
+
|
|
2618
|
+
for (const file of fs.readdirSync(wsPath)) {
|
|
2619
|
+
if (!file.endsWith('.jsonl')) continue;
|
|
2620
|
+
|
|
2621
|
+
const sessionId = file.slice(0, -6);
|
|
2622
|
+
const jsonlPath = path.join(wsPath, file);
|
|
2623
|
+
const settingsPath = path.join(wsPath, sessionId + '.settings.json');
|
|
2624
|
+
|
|
2625
|
+
try {
|
|
2626
|
+
const session = parseSessionFile(jsonlPath, settingsPath);
|
|
2627
|
+
if (session.messageCount === 0 || !session.firstUserMsg) continue;
|
|
2628
|
+
sessions.push(session);
|
|
2629
|
+
} catch {}
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
sessions.sort((a, b) => b.mtime - a.mtime);
|
|
2634
|
+
return sessions.slice(0, 50);
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
function formatTime(ts) {
|
|
2638
|
+
if (!ts) return '';
|
|
2639
|
+
try {
|
|
2640
|
+
const d = new Date(ts);
|
|
2641
|
+
return d.toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
|
|
2642
|
+
} catch {
|
|
2643
|
+
return ts.slice(0, 16);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
function truncate(s, len) {
|
|
2648
|
+
if (!s) return '';
|
|
2649
|
+
s = s.replace(/\\n/g, ' ');
|
|
2650
|
+
return s.length > len ? s.slice(0, len - 3) + '...' : s;
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
function render(sessions, selected, offset, rows) {
|
|
2654
|
+
const cwd = process.cwd();
|
|
2655
|
+
const pageSize = rows - 6;
|
|
2656
|
+
const visible = sessions.slice(offset, offset + pageSize);
|
|
2657
|
+
|
|
2658
|
+
let out = CLEAR;
|
|
2659
|
+
out += BOLD + 'Sessions: ' + RESET + DIM + cwd + RESET + '\\n';
|
|
2660
|
+
out += DIM + '[↑/↓] Select [Enter] Resume [q] Quit' + RESET + '\\n\\n';
|
|
2661
|
+
|
|
2662
|
+
for (let i = 0; i < visible.length; i++) {
|
|
2663
|
+
const s = visible[i];
|
|
2664
|
+
const idx = offset + i;
|
|
2665
|
+
const isSelected = idx === selected;
|
|
2666
|
+
const prefix = isSelected ? GREEN + '▶ ' + RESET : ' ';
|
|
2667
|
+
|
|
2668
|
+
const title = truncate(s.title || '(no title)', 35);
|
|
2669
|
+
const time = formatTime(s.lastTimestamp);
|
|
2670
|
+
const model = truncate(s.model, 20);
|
|
2671
|
+
|
|
2672
|
+
if (isSelected) {
|
|
2673
|
+
out += prefix + YELLOW + title + RESET + '\\n';
|
|
2674
|
+
out += ' ' + DIM + 'ID: ' + RESET + CYAN + s.id + RESET + '\\n';
|
|
2675
|
+
out += ' ' + DIM + 'Last: ' + time + ' | Model: ' + model + ' | ' + s.messageCount + ' msgs' + RESET + '\\n';
|
|
2676
|
+
out += ' ' + DIM + 'First input: ' + RESET + truncate(s.firstUserMsg, 60) + '\\n';
|
|
2677
|
+
if (s.lastUserMsg && s.lastUserMsg !== s.firstUserMsg) {
|
|
2678
|
+
out += ' ' + DIM + 'Last input: ' + RESET + truncate(s.lastUserMsg, 60) + '\\n';
|
|
2679
|
+
}
|
|
2680
|
+
} else {
|
|
2681
|
+
out += prefix + title + DIM + ' (' + time + ')' + RESET + '\\n';
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
out += '\\n' + DIM + 'Page ' + (Math.floor(offset / pageSize) + 1) + '/' + Math.ceil(sessions.length / pageSize) + ' (' + sessions.length + ' sessions)' + RESET;
|
|
2686
|
+
|
|
2687
|
+
process.stdout.write(out);
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
async function main() {
|
|
2691
|
+
const sessions = collectSessions();
|
|
2692
|
+
|
|
2693
|
+
if (sessions.length === 0) {
|
|
2694
|
+
console.log(RED + 'No sessions with interactions found in current directory' + RESET);
|
|
2695
|
+
process.exit(0);
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
if (!process.stdin.isTTY) {
|
|
2699
|
+
for (const s of sessions) {
|
|
2700
|
+
console.log(s.id + ' ' + (s.title || '') + ' ' + formatTime(s.lastTimestamp));
|
|
2701
|
+
}
|
|
2702
|
+
process.exit(0);
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
const rows = process.stdout.rows || 24;
|
|
2706
|
+
const pageSize = rows - 6;
|
|
2707
|
+
let selected = 0;
|
|
2708
|
+
let offset = 0;
|
|
2709
|
+
|
|
2710
|
+
function restoreTerminal() {
|
|
2711
|
+
try { process.stdout.write(SHOW_CURSOR); } catch {}
|
|
2712
|
+
try { process.stdin.setRawMode(false); } catch {}
|
|
2713
|
+
try { process.stdin.pause(); } catch {}
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
function clearScreen() {
|
|
2717
|
+
try { process.stdout.write(CLEAR); } catch {}
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
process.stdin.setRawMode(true);
|
|
2721
|
+
process.stdin.resume();
|
|
2722
|
+
process.stdout.write(HIDE_CURSOR);
|
|
2723
|
+
|
|
2724
|
+
render(sessions, selected, offset, rows);
|
|
2725
|
+
|
|
2726
|
+
const onKey = (key) => {
|
|
2727
|
+
const k = key.toString();
|
|
2728
|
+
|
|
2729
|
+
if (k === 'q' || k === '\\x03') { // q or Ctrl+C
|
|
2730
|
+
restoreTerminal();
|
|
2731
|
+
clearScreen();
|
|
2732
|
+
process.exit(0);
|
|
2733
|
+
}
|
|
2734
|
+
|
|
2735
|
+
if (k === '\\r' || k === '\\n') { // Enter
|
|
2736
|
+
// Stop reading input / stop reacting to arrow keys before handing off to droid.
|
|
2737
|
+
process.stdin.off('data', onKey);
|
|
2738
|
+
restoreTerminal();
|
|
2739
|
+
clearScreen();
|
|
2740
|
+
const session = sessions[selected];
|
|
2741
|
+
console.log(GREEN + 'Resuming session: ' + session.id + RESET);
|
|
2742
|
+
console.log(DIM + 'Using: ' + ALIAS_NAME + ' --resume ' + session.id + RESET + '\\n');
|
|
2743
|
+
|
|
2744
|
+
// Avoid the sessions browser reacting to signals while droid is running.
|
|
2745
|
+
try { process.removeAllListeners('SIGINT'); } catch {}
|
|
2746
|
+
try { process.removeAllListeners('SIGTERM'); } catch {}
|
|
2747
|
+
try { process.on('SIGINT', () => {}); } catch {}
|
|
2748
|
+
try { process.on('SIGTERM', () => {}); } catch {}
|
|
2749
|
+
|
|
2750
|
+
const child = spawn(ALIAS_NAME, ['--resume', session.id], { stdio: 'inherit' });
|
|
2751
|
+
child.on('exit', (code) => process.exit(code || 0));
|
|
2752
|
+
child.on('error', () => process.exit(1));
|
|
2753
|
+
return;
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
if (k === '\\x1b[A' || k === 'k') { // Up
|
|
2757
|
+
if (selected > 0) {
|
|
2758
|
+
selected--;
|
|
2759
|
+
if (selected < offset) offset = Math.max(0, offset - 1);
|
|
2760
|
+
}
|
|
2761
|
+
} else if (k === '\\x1b[B' || k === 'j') { // Down
|
|
2762
|
+
if (selected < sessions.length - 1) {
|
|
2763
|
+
selected++;
|
|
2764
|
+
if (selected >= offset + pageSize) offset++;
|
|
2765
|
+
}
|
|
2766
|
+
} else if (k === '\\x1b[5~') { // Page Up
|
|
2767
|
+
selected = Math.max(0, selected - pageSize);
|
|
2768
|
+
offset = Math.max(0, offset - pageSize);
|
|
2769
|
+
} else if (k === '\\x1b[6~') { // Page Down
|
|
2770
|
+
selected = Math.min(sessions.length - 1, selected + pageSize);
|
|
2771
|
+
offset = Math.min(Math.max(0, sessions.length - pageSize), offset + pageSize);
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
render(sessions, selected, offset, rows);
|
|
2775
|
+
};
|
|
2776
|
+
|
|
2777
|
+
process.stdin.on('data', onKey);
|
|
2778
|
+
|
|
2779
|
+
process.on('SIGINT', () => {
|
|
2780
|
+
restoreTerminal();
|
|
2781
|
+
clearScreen();
|
|
2782
|
+
process.exit(0);
|
|
2783
|
+
});
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
main();
|
|
2787
|
+
`;
|
|
2788
|
+
}
|
|
2789
|
+
/**
|
|
2790
|
+
* Create sessions browser script file
|
|
2791
|
+
*/
|
|
2792
|
+
async function createSessionsScript(outputDir, aliasName) {
|
|
2793
|
+
if (!existsSync(outputDir)) await mkdir(outputDir, { recursive: true });
|
|
2794
|
+
const sessionsScriptPath = join(outputDir, `${aliasName}-sessions.js`);
|
|
2795
|
+
await writeFile(sessionsScriptPath, generateSessionsBrowserScript(aliasName));
|
|
2796
|
+
await chmod(sessionsScriptPath, 493);
|
|
2797
|
+
return { sessionsScript: sessionsScriptPath };
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2398
2800
|
//#endregion
|
|
2399
2801
|
//#region src/cli.ts
|
|
2400
2802
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -2447,13 +2849,14 @@ function findDefaultDroidPath() {
|
|
|
2447
2849
|
for (const p of paths) if (existsSync(p)) return p;
|
|
2448
2850
|
return join(home, ".droid", "bin", "droid");
|
|
2449
2851
|
}
|
|
2450
|
-
bin("droid-patch", "CLI tool to patch droid binary with various modifications").package("droid-patch", version).option("--is-custom", "Patch isCustom:!0 to isCustom:!1 (enable context compression for custom models)").option("--skip-login", "Inject a fake FACTORY_API_KEY to bypass login requirement (no real key needed)").option("--api-base <url>", "Replace API URL (standalone: binary patch, max 22 chars; with --websearch: proxy forward target, no limit)").option("--websearch", "Enable local WebSearch proxy (each instance runs own proxy, auto-cleanup on exit)").option("--statusline", "Enable a Claude-style statusline (terminal UI)").option("--standalone", "Standalone mode: mock non-LLM Factory APIs (use with --websearch)").option("--reasoning-effort", "Enable reasoning effort for custom models (set to high, enable UI selector)").option("--disable-telemetry", "Disable telemetry and Sentry error reporting (block data uploads)").option("--dry-run", "Verify patches without actually modifying the binary").option("-p, --path <path>", "Path to the droid binary").option("-o, --output <dir>", "Output directory for patched binary").option("--no-backup", "Do not create backup of original binary").option("-v, --verbose", "Enable verbose output").argument("[alias]", "Alias name for the patched binary").action(async (options, args) => {
|
|
2852
|
+
bin("droid-patch", "CLI tool to patch droid binary with various modifications").package("droid-patch", version).option("--is-custom", "Patch isCustom:!0 to isCustom:!1 (enable context compression for custom models)").option("--skip-login", "Inject a fake FACTORY_API_KEY to bypass login requirement (no real key needed)").option("--api-base <url>", "Replace API URL (standalone: binary patch, max 22 chars; with --websearch: proxy forward target, no limit)").option("--websearch", "Enable local WebSearch proxy (each instance runs own proxy, auto-cleanup on exit)").option("--statusline", "Enable a Claude-style statusline (terminal UI)").option("--sessions", "Enable sessions browser (--sessions flag in alias)").option("--standalone", "Standalone mode: mock non-LLM Factory APIs (use with --websearch)").option("--reasoning-effort", "Enable reasoning effort for custom models (set to high, enable UI selector)").option("--disable-telemetry", "Disable telemetry and Sentry error reporting (block data uploads)").option("--dry-run", "Verify patches without actually modifying the binary").option("-p, --path <path>", "Path to the droid binary").option("-o, --output <dir>", "Output directory for patched binary").option("--no-backup", "Do not create backup of original binary").option("-v, --verbose", "Enable verbose output").argument("[alias]", "Alias name for the patched binary").action(async (options, args) => {
|
|
2451
2853
|
const alias = args?.[0];
|
|
2452
2854
|
const isCustom = options["is-custom"];
|
|
2453
2855
|
const skipLogin = options["skip-login"];
|
|
2454
2856
|
const apiBase = options["api-base"];
|
|
2455
2857
|
const websearch = options["websearch"];
|
|
2456
2858
|
const statusline = options["statusline"];
|
|
2859
|
+
const sessions = options["sessions"];
|
|
2457
2860
|
const standalone = options["standalone"];
|
|
2458
2861
|
const websearchTarget = websearch ? apiBase || "https://api.factory.ai" : void 0;
|
|
2459
2862
|
const reasoningEffort = options["reasoning-effort"];
|
|
@@ -2464,7 +2867,9 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
2464
2867
|
const backup = options.backup !== false;
|
|
2465
2868
|
const verbose = options.verbose;
|
|
2466
2869
|
const outputPath = outputDir && alias ? join(outputDir, alias) : void 0;
|
|
2467
|
-
|
|
2870
|
+
const needsBinaryPatch = !!isCustom || !!skipLogin || !!reasoningEffort || !!noTelemetry || !!apiBase && !websearch;
|
|
2871
|
+
const statuslineEnabled = statusline;
|
|
2872
|
+
if (!needsBinaryPatch && (websearch || statuslineEnabled)) {
|
|
2468
2873
|
if (!alias) {
|
|
2469
2874
|
console.log(styleText("red", "Error: Alias name required for --websearch/--statusline"));
|
|
2470
2875
|
console.log(styleText("gray", "Usage: npx droid-patch --websearch <alias>"));
|
|
@@ -2480,15 +2885,18 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
2480
2885
|
console.log(styleText("white", `Forward target: ${websearchTarget}`));
|
|
2481
2886
|
if (standalone) console.log(styleText("white", `Standalone mode: enabled`));
|
|
2482
2887
|
}
|
|
2483
|
-
if (
|
|
2888
|
+
if (statuslineEnabled) console.log(styleText("white", `Statusline: enabled`));
|
|
2484
2889
|
console.log();
|
|
2485
2890
|
let execTargetPath = path;
|
|
2486
2891
|
if (websearch) {
|
|
2487
2892
|
const { wrapperScript } = await createWebSearchUnifiedFiles(join(homedir(), ".droid-patch", "proxy"), execTargetPath, alias, websearchTarget, standalone);
|
|
2488
2893
|
execTargetPath = wrapperScript;
|
|
2489
2894
|
}
|
|
2490
|
-
if (
|
|
2491
|
-
const
|
|
2895
|
+
if (statuslineEnabled) {
|
|
2896
|
+
const statuslineDir = join(homedir(), ".droid-patch", "statusline");
|
|
2897
|
+
let sessionsScript;
|
|
2898
|
+
if (sessions) sessionsScript = (await createSessionsScript(statuslineDir, alias)).sessionsScript;
|
|
2899
|
+
const { wrapperScript } = await createStatuslineFiles(statuslineDir, execTargetPath, alias, sessionsScript);
|
|
2492
2900
|
execTargetPath = wrapperScript;
|
|
2493
2901
|
}
|
|
2494
2902
|
const aliasResult = await createAliasForWrapper(execTargetPath, alias, verbose);
|
|
@@ -2498,7 +2906,8 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
2498
2906
|
skipLogin: false,
|
|
2499
2907
|
apiBase: apiBase || null,
|
|
2500
2908
|
websearch: !!websearch,
|
|
2501
|
-
statusline: !!
|
|
2909
|
+
statusline: !!statuslineEnabled,
|
|
2910
|
+
sessions: !!sessions,
|
|
2502
2911
|
reasoningEffort: false,
|
|
2503
2912
|
noTelemetry: false,
|
|
2504
2913
|
standalone
|
|
@@ -2534,7 +2943,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
2534
2943
|
}
|
|
2535
2944
|
return;
|
|
2536
2945
|
}
|
|
2537
|
-
if (!isCustom && !skipLogin && !apiBase && !websearch && !
|
|
2946
|
+
if (!isCustom && !skipLogin && !apiBase && !websearch && !statuslineEnabled && !reasoningEffort && !noTelemetry) {
|
|
2538
2947
|
console.log(styleText("yellow", "No patch flags specified. Available patches:"));
|
|
2539
2948
|
console.log(styleText("gray", " --is-custom Patch isCustom for custom models"));
|
|
2540
2949
|
console.log(styleText("gray", " --skip-login Bypass login by injecting a fake API key"));
|
|
@@ -2693,14 +3102,17 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
2693
3102
|
console.log(styleText("white", ` Forward target: ${websearchTarget}`));
|
|
2694
3103
|
if (standalone) console.log(styleText("white", ` Standalone mode: enabled`));
|
|
2695
3104
|
}
|
|
2696
|
-
if (
|
|
2697
|
-
const
|
|
3105
|
+
if (statuslineEnabled) {
|
|
3106
|
+
const statuslineDir = join(homedir(), ".droid-patch", "statusline");
|
|
3107
|
+
let sessionsScript;
|
|
3108
|
+
if (sessions) sessionsScript = (await createSessionsScript(statuslineDir, alias)).sessionsScript;
|
|
3109
|
+
const { wrapperScript } = await createStatuslineFiles(statuslineDir, execTargetPath, alias, sessionsScript);
|
|
2698
3110
|
execTargetPath = wrapperScript;
|
|
2699
3111
|
console.log();
|
|
2700
3112
|
console.log(styleText("cyan", "Statusline enabled"));
|
|
2701
3113
|
}
|
|
2702
3114
|
let aliasResult;
|
|
2703
|
-
if (websearch ||
|
|
3115
|
+
if (websearch || statuslineEnabled) aliasResult = await createAliasForWrapper(execTargetPath, alias, verbose);
|
|
2704
3116
|
else aliasResult = await createAlias(result.outputPath, alias, verbose);
|
|
2705
3117
|
const droidVersion = getDroidVersion(path);
|
|
2706
3118
|
await saveAliasMetadata(createMetadata(alias, path, {
|
|
@@ -2708,7 +3120,8 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
2708
3120
|
skipLogin: !!skipLogin,
|
|
2709
3121
|
apiBase: apiBase || null,
|
|
2710
3122
|
websearch: !!websearch,
|
|
2711
|
-
statusline: !!
|
|
3123
|
+
statusline: !!statuslineEnabled,
|
|
3124
|
+
sessions: !!sessions,
|
|
2712
3125
|
reasoningEffort: !!reasoningEffort,
|
|
2713
3126
|
noTelemetry: !!noTelemetry,
|
|
2714
3127
|
standalone: !!standalone
|
|
@@ -2925,7 +3338,10 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
2925
3338
|
}
|
|
2926
3339
|
}
|
|
2927
3340
|
if (meta.patches.statusline) {
|
|
2928
|
-
const
|
|
3341
|
+
const statuslineDir = join(homedir(), ".droid-patch", "statusline");
|
|
3342
|
+
let sessionsScript;
|
|
3343
|
+
if (meta.patches.sessions) sessionsScript = (await createSessionsScript(statuslineDir, meta.name)).sessionsScript;
|
|
3344
|
+
const { wrapperScript } = await createStatuslineFiles(statuslineDir, execTargetPath, meta.name, sessionsScript);
|
|
2929
3345
|
execTargetPath = wrapperScript;
|
|
2930
3346
|
if (verbose) console.log(styleText("gray", ` Regenerated statusline wrapper`));
|
|
2931
3347
|
}
|