droid-patch 0.8.0 → 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 +3 -3
- package/README.zh-CN.md +3 -3
- package/dist/cli.mjs +1145 -1024
- 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
|
|
|
@@ -1419,986 +1550,949 @@ main().catch(() => {});
|
|
|
1419
1550
|
`;
|
|
1420
1551
|
}
|
|
1421
1552
|
function generateStatuslineWrapperScript(execTargetPath, monitorScriptPath, sessionsScriptPath) {
|
|
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
|
-
if
|
|
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
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
if
|
|
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
|
-
# Periodic force repaint to ensure statusline doesn't disappear
|
|
2366
|
-
now_ms = int(time.time() * 1000)
|
|
2367
|
-
if now_ms - last_force_repaint_ms >= FORCE_REPAINT_INTERVAL_MS:
|
|
2368
|
-
renderer.force_repaint(False)
|
|
2369
|
-
last_force_repaint_ms = now_ms
|
|
2370
|
-
|
|
2371
|
-
renderer.render(physical_rows, physical_cols, cr, cc)
|
|
2372
|
-
|
|
2373
|
-
finally:
|
|
2374
|
-
try:
|
|
2375
|
-
termios.tcsetattr(stdin_fd, termios.TCSADRAIN, old_tty)
|
|
2376
|
-
except Exception:
|
|
2377
|
-
pass
|
|
2378
|
-
try:
|
|
2379
|
-
renderer.clear()
|
|
2380
|
-
except Exception:
|
|
2381
|
-
pass
|
|
2382
|
-
try:
|
|
2383
|
-
# Restore terminal scroll region and attributes.
|
|
2384
|
-
os.write(stdout_fd, b"\\x1b[r\\x1b[0m\\x1b[?25h")
|
|
2385
|
-
except Exception:
|
|
2386
|
-
pass
|
|
2387
|
-
try:
|
|
2388
|
-
os.close(master_fd)
|
|
2389
|
-
except Exception:
|
|
2390
|
-
pass
|
|
2391
|
-
if monitor is not None:
|
|
2392
|
-
try:
|
|
2393
|
-
monitor.terminate()
|
|
2394
|
-
except Exception:
|
|
2395
|
-
pass
|
|
2396
|
-
|
|
2397
|
-
sys.exit(child.returncode or 0)
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
if __name__ == "__main__":
|
|
2401
|
-
main()
|
|
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));
|
|
2402
2496
|
`;
|
|
2403
2497
|
}
|
|
2404
2498
|
async function createStatuslineFiles(outputDir, execTargetPath, aliasName, sessionsScriptPath) {
|
|
@@ -2613,27 +2707,49 @@ async function main() {
|
|
|
2613
2707
|
let selected = 0;
|
|
2614
2708
|
let offset = 0;
|
|
2615
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
|
+
|
|
2616
2720
|
process.stdin.setRawMode(true);
|
|
2617
2721
|
process.stdin.resume();
|
|
2618
2722
|
process.stdout.write(HIDE_CURSOR);
|
|
2619
2723
|
|
|
2620
2724
|
render(sessions, selected, offset, rows);
|
|
2621
2725
|
|
|
2622
|
-
|
|
2726
|
+
const onKey = (key) => {
|
|
2623
2727
|
const k = key.toString();
|
|
2624
2728
|
|
|
2625
2729
|
if (k === 'q' || k === '\\x03') { // q or Ctrl+C
|
|
2626
|
-
|
|
2730
|
+
restoreTerminal();
|
|
2731
|
+
clearScreen();
|
|
2627
2732
|
process.exit(0);
|
|
2628
2733
|
}
|
|
2629
2734
|
|
|
2630
2735
|
if (k === '\\r' || k === '\\n') { // Enter
|
|
2631
|
-
|
|
2736
|
+
// Stop reading input / stop reacting to arrow keys before handing off to droid.
|
|
2737
|
+
process.stdin.off('data', onKey);
|
|
2738
|
+
restoreTerminal();
|
|
2739
|
+
clearScreen();
|
|
2632
2740
|
const session = sessions[selected];
|
|
2633
2741
|
console.log(GREEN + 'Resuming session: ' + session.id + RESET);
|
|
2634
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
|
+
|
|
2635
2750
|
const child = spawn(ALIAS_NAME, ['--resume', session.id], { stdio: 'inherit' });
|
|
2636
2751
|
child.on('exit', (code) => process.exit(code || 0));
|
|
2752
|
+
child.on('error', () => process.exit(1));
|
|
2637
2753
|
return;
|
|
2638
2754
|
}
|
|
2639
2755
|
|
|
@@ -2656,10 +2772,13 @@ async function main() {
|
|
|
2656
2772
|
}
|
|
2657
2773
|
|
|
2658
2774
|
render(sessions, selected, offset, rows);
|
|
2659
|
-
}
|
|
2775
|
+
};
|
|
2776
|
+
|
|
2777
|
+
process.stdin.on('data', onKey);
|
|
2660
2778
|
|
|
2661
2779
|
process.on('SIGINT', () => {
|
|
2662
|
-
|
|
2780
|
+
restoreTerminal();
|
|
2781
|
+
clearScreen();
|
|
2663
2782
|
process.exit(0);
|
|
2664
2783
|
});
|
|
2665
2784
|
}
|
|
@@ -2748,7 +2867,9 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
2748
2867
|
const backup = options.backup !== false;
|
|
2749
2868
|
const verbose = options.verbose;
|
|
2750
2869
|
const outputPath = outputDir && alias ? join(outputDir, alias) : void 0;
|
|
2751
|
-
|
|
2870
|
+
const needsBinaryPatch = !!isCustom || !!skipLogin || !!reasoningEffort || !!noTelemetry || !!apiBase && !websearch;
|
|
2871
|
+
const statuslineEnabled = statusline;
|
|
2872
|
+
if (!needsBinaryPatch && (websearch || statuslineEnabled)) {
|
|
2752
2873
|
if (!alias) {
|
|
2753
2874
|
console.log(styleText("red", "Error: Alias name required for --websearch/--statusline"));
|
|
2754
2875
|
console.log(styleText("gray", "Usage: npx droid-patch --websearch <alias>"));
|
|
@@ -2764,14 +2885,14 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
2764
2885
|
console.log(styleText("white", `Forward target: ${websearchTarget}`));
|
|
2765
2886
|
if (standalone) console.log(styleText("white", `Standalone mode: enabled`));
|
|
2766
2887
|
}
|
|
2767
|
-
if (
|
|
2888
|
+
if (statuslineEnabled) console.log(styleText("white", `Statusline: enabled`));
|
|
2768
2889
|
console.log();
|
|
2769
2890
|
let execTargetPath = path;
|
|
2770
2891
|
if (websearch) {
|
|
2771
2892
|
const { wrapperScript } = await createWebSearchUnifiedFiles(join(homedir(), ".droid-patch", "proxy"), execTargetPath, alias, websearchTarget, standalone);
|
|
2772
2893
|
execTargetPath = wrapperScript;
|
|
2773
2894
|
}
|
|
2774
|
-
if (
|
|
2895
|
+
if (statuslineEnabled) {
|
|
2775
2896
|
const statuslineDir = join(homedir(), ".droid-patch", "statusline");
|
|
2776
2897
|
let sessionsScript;
|
|
2777
2898
|
if (sessions) sessionsScript = (await createSessionsScript(statuslineDir, alias)).sessionsScript;
|
|
@@ -2785,7 +2906,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
2785
2906
|
skipLogin: false,
|
|
2786
2907
|
apiBase: apiBase || null,
|
|
2787
2908
|
websearch: !!websearch,
|
|
2788
|
-
statusline: !!
|
|
2909
|
+
statusline: !!statuslineEnabled,
|
|
2789
2910
|
sessions: !!sessions,
|
|
2790
2911
|
reasoningEffort: false,
|
|
2791
2912
|
noTelemetry: false,
|
|
@@ -2822,7 +2943,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
2822
2943
|
}
|
|
2823
2944
|
return;
|
|
2824
2945
|
}
|
|
2825
|
-
if (!isCustom && !skipLogin && !apiBase && !websearch && !
|
|
2946
|
+
if (!isCustom && !skipLogin && !apiBase && !websearch && !statuslineEnabled && !reasoningEffort && !noTelemetry) {
|
|
2826
2947
|
console.log(styleText("yellow", "No patch flags specified. Available patches:"));
|
|
2827
2948
|
console.log(styleText("gray", " --is-custom Patch isCustom for custom models"));
|
|
2828
2949
|
console.log(styleText("gray", " --skip-login Bypass login by injecting a fake API key"));
|
|
@@ -2981,7 +3102,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
2981
3102
|
console.log(styleText("white", ` Forward target: ${websearchTarget}`));
|
|
2982
3103
|
if (standalone) console.log(styleText("white", ` Standalone mode: enabled`));
|
|
2983
3104
|
}
|
|
2984
|
-
if (
|
|
3105
|
+
if (statuslineEnabled) {
|
|
2985
3106
|
const statuslineDir = join(homedir(), ".droid-patch", "statusline");
|
|
2986
3107
|
let sessionsScript;
|
|
2987
3108
|
if (sessions) sessionsScript = (await createSessionsScript(statuslineDir, alias)).sessionsScript;
|
|
@@ -2991,7 +3112,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
2991
3112
|
console.log(styleText("cyan", "Statusline enabled"));
|
|
2992
3113
|
}
|
|
2993
3114
|
let aliasResult;
|
|
2994
|
-
if (websearch ||
|
|
3115
|
+
if (websearch || statuslineEnabled) aliasResult = await createAliasForWrapper(execTargetPath, alias, verbose);
|
|
2995
3116
|
else aliasResult = await createAlias(result.outputPath, alias, verbose);
|
|
2996
3117
|
const droidVersion = getDroidVersion(path);
|
|
2997
3118
|
await saveAliasMetadata(createMetadata(alias, path, {
|
|
@@ -2999,7 +3120,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
2999
3120
|
skipLogin: !!skipLogin,
|
|
3000
3121
|
apiBase: apiBase || null,
|
|
3001
3122
|
websearch: !!websearch,
|
|
3002
|
-
statusline: !!
|
|
3123
|
+
statusline: !!statuslineEnabled,
|
|
3003
3124
|
sessions: !!sessions,
|
|
3004
3125
|
reasoningEffort: !!reasoningEffort,
|
|
3005
3126
|
noTelemetry: !!noTelemetry,
|