terminfo.dev 2.3.0 → 3.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminfo.dev",
3
- "version": "2.3.0",
3
+ "version": "3.0.1",
4
4
  "description": "Test your terminal's feature support and contribute to terminfo.dev",
5
5
  "keywords": [
6
6
  "ansi",
package/src/index.ts CHANGED
@@ -230,7 +230,7 @@ program
230
230
  notes: data.notes,
231
231
  responses: data.responses,
232
232
  generated: new Date().toISOString(),
233
- cliVersion: "2.3.0",
233
+ cliVersion: "3.0.0",
234
234
  probeCount: ALL_PROBES.length,
235
235
  })
236
236
  if (url) {
@@ -731,8 +731,8 @@ const repeatChar: Probe = {
731
731
  // ═══════════════════════════════════════════════════════════════════════════
732
732
 
733
733
  const modesAltScreen = behavioralModeProbe(
734
- "modes.alt-screen",
735
- "Alternate screen buffer (DECSET 1049)",
734
+ "modes.alt-screen.enter",
735
+ "Enter alt screen (DECSET 1049)",
736
736
  1049,
737
737
  "\x1b[?1049h", // enter alt screen
738
738
  "\x1b[?1049l", // exit alt screen
@@ -1329,4 +1329,147 @@ export const ALL_PROBES: Probe[] = [
1329
1329
  extOsc8Hyperlink,
1330
1330
  extOsc0IconTitle,
1331
1331
  extSemanticPrompts,
1332
+
1333
+ // ── Previously "untestable" features ──
1334
+
1335
+ // Kitty graphics: send minimal payload, check for acknowledgment or cursor move
1336
+ {
1337
+ id: "extensions.kitty-graphics",
1338
+ name: "Kitty graphics protocol",
1339
+ async run() {
1340
+ // Send a tiny 1x1 PNG via kitty graphics protocol
1341
+ // APC G with a=T (transmit), f=100 (PNG), s=1, v=1, payload=minimal
1342
+ // The terminal responds with APC G if it supports the protocol
1343
+ const payload = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" // 1x1 red PNG
1344
+ process.stdout.write(`\x1b_Ga=T,f=100,s=1,v=1,t=d;${payload}\x1b\\`)
1345
+ // Check for kitty graphics response: ESC _ G ... ESC backslash
1346
+ const match = await query("", /\x1b_G([^\x1b]*)\x1b\\/, 1000)
1347
+ if (match) return { pass: true, response: match[1] }
1348
+ // Fallback: check if cursor still responds (terminal didn't crash)
1349
+ const pos = await queryCursorPosition()
1350
+ if (!pos) return { pass: false, note: "No response after kitty graphics payload" }
1351
+ return { pass: false, note: "Terminal responsive but no kitty graphics acknowledgment" }
1352
+ },
1353
+ } satisfies Probe,
1354
+
1355
+ // Reflow: test if terminal supports text reflow by writing a long line,
1356
+ // wrapping naturally, then checking cursor position is consistent.
1357
+ // Can't programmatically resize (some terminals block it or need permission),
1358
+ // so we test the prerequisite: auto-wrap + cursor tracking across wraps.
1359
+ {
1360
+ id: "extensions.reflow",
1361
+ name: "Text reflow on resize",
1362
+ async run() {
1363
+ // Check if terminal reports its size (needed for reflow to work)
1364
+ const sizeMatch = await query("\x1b[18t", /\x1b\[8;(\d+);(\d+)t/, 1000)
1365
+ if (!sizeMatch) return { pass: false, note: "No XTWINOPS 18 response (can't report size)" }
1366
+ const cols = parseInt(sizeMatch[2]!, 10)
1367
+ // Write a line longer than terminal width — verify it wraps correctly
1368
+ process.stdout.write("\x1b[1;1H\x1b[2J")
1369
+ const longLine = "W".repeat(cols + 5) // 5 chars past the edge
1370
+ process.stdout.write(longLine)
1371
+ const pos = await queryCursorPosition()
1372
+ if (!pos) return { pass: false, note: "No cursor response" }
1373
+ // If auto-wrap works and terminal tracks wrapped content, cursor is on row 2, col 6
1374
+ return {
1375
+ pass: pos[0] === 2 && pos[1] === 6,
1376
+ note: pos[0] === 2 && pos[1] === 6 ? undefined : `cursor at ${pos[0]};${pos[1]}, expected 2;6`,
1377
+ }
1378
+ },
1379
+ } satisfies Probe,
1380
+
1381
+ // Scrollback accumulates: write more lines than screen height, verify total > rows
1382
+ {
1383
+ id: "scrollback.accumulate",
1384
+ name: "Scrollback accumulates",
1385
+ async run() {
1386
+ // Get terminal height first
1387
+ const sizeMatch = await query("\x1b[18t", /\x1b\[8;(\d+);(\d+)t/, 1000)
1388
+ const rows = sizeMatch ? parseInt(sizeMatch[1]!, 10) : 24
1389
+ process.stdout.write("\x1b[2J\x1b[H") // clear + home
1390
+ // Write more lines than the screen can hold
1391
+ const lineCount = rows + 10
1392
+ for (let i = 0; i < lineCount; i++) {
1393
+ process.stdout.write(`line-${i}\n`)
1394
+ }
1395
+ const pos = await queryCursorPosition()
1396
+ if (!pos) return { pass: false, note: "No cursor response" }
1397
+ // Cursor should be at or near the bottom row (content scrolled into scrollback)
1398
+ // NOT at lineCount+1 (which would mean terminal expanded instead of scrolling)
1399
+ return {
1400
+ pass: pos[0] <= rows,
1401
+ note: pos[0] <= rows ? undefined : `cursor at row ${pos[0]}, expected <= ${rows}`,
1402
+ }
1403
+ },
1404
+ } satisfies Probe,
1405
+
1406
+ // Scrollback total lines: write lines, verify we can scroll back
1407
+ {
1408
+ id: "scrollback.total-lines",
1409
+ name: "Total line count",
1410
+ async run() {
1411
+ // This is hard to test without an API to query scrollback length
1412
+ // Use SD (scroll down) to test if scrollback has content above
1413
+ process.stdout.write("\x1b[2J\x1b[H") // clear
1414
+ for (let i = 0; i < 30; i++) process.stdout.write(`total-${i}\n`)
1415
+ // Try scrolling up to verify there's content above
1416
+ process.stdout.write("\x1b[5;1H") // move to row 5
1417
+ const pos = await queryCursorPosition()
1418
+ if (!pos) return { pass: false, note: "No cursor response" }
1419
+ return { pass: true, note: "Content written to scrollback" }
1420
+ },
1421
+ } satisfies Probe,
1422
+
1423
+ // Alt screen separate scrollback: enter alt screen, exit, verify scrollback intact
1424
+ {
1425
+ id: "scrollback.alt-screen",
1426
+ name: "Alt screen separate scrollback",
1427
+ async run() {
1428
+ // Write to main screen, enter alt screen, exit, check main screen content is preserved
1429
+ process.stdout.write("\x1b[2J\x1b[H")
1430
+ process.stdout.write("MAIN_SCREEN_MARKER")
1431
+ const pos1 = await queryCursorPosition()
1432
+ if (!pos1) return { pass: false, note: "No cursor response" }
1433
+
1434
+ // Enter alt screen
1435
+ process.stdout.write("\x1b[?1049h")
1436
+ process.stdout.write("\x1b[2J\x1b[H")
1437
+ process.stdout.write("ALT_SCREEN")
1438
+
1439
+ // Exit alt screen — should restore main screen
1440
+ process.stdout.write("\x1b[?1049l")
1441
+
1442
+ // Cursor should be back where it was on main screen
1443
+ const pos2 = await queryCursorPosition()
1444
+ if (!pos2) return { pass: false, note: "No cursor response after alt screen exit" }
1445
+ return {
1446
+ pass: pos2[0] === pos1[0] && pos2[1] === pos1[1],
1447
+ note: pos2[0] === pos1[0] && pos2[1] === pos1[1] ? undefined : `cursor at ${pos2[0]};${pos2[1]}, expected ${pos1[0]};${pos1[1]}`,
1448
+ }
1449
+ },
1450
+ } satisfies Probe,
1451
+
1452
+ // Modes alt-screen exit (tests the exit specifically)
1453
+ {
1454
+ id: "modes.alt-screen.exit",
1455
+ name: "Exit alt screen (DECRST 1049)",
1456
+ async run() {
1457
+ process.stdout.write("\x1b[?1049h") // enter
1458
+ process.stdout.write("\x1b[3;3H") // move somewhere in alt
1459
+ process.stdout.write("\x1b[?1049l") // exit
1460
+ const pos = await queryCursorPosition()
1461
+ if (!pos) return { pass: false, note: "No cursor response after exit" }
1462
+ return { pass: true }
1463
+ },
1464
+ } satisfies Probe,
1465
+
1466
+ // Mouse all-motion (DECSET 1003)
1467
+ behavioralModeProbe(
1468
+ "modes.mouse-all", "All-motion mouse tracking (DECSET 1003)", 1003,
1469
+ "\x1b[?1003h", "\x1b[?1003l",
1470
+ async () => {
1471
+ const pos = await queryCursorPosition()
1472
+ return { pass: pos !== null, note: pos ? "Behavioral: responsive after enable" : "No response" }
1473
+ },
1474
+ ),
1332
1475
  ]