terminfo.dev 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminfo.dev",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Test your terminal's feature support and contribute to terminfo.dev",
5
5
  "keywords": [
6
6
  "ansi",
@@ -1391,10 +1391,7 @@ export const ALL_PROBES: Probe[] = [
1391
1391
  // The terminal responds with APC G if it supports the protocol
1392
1392
  const payload = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" // 1x1 red PNG
1393
1393
  // Send payload, then use DA1 sentinel to detect support quickly
1394
- const match = await queryWithSentinel(
1395
- `\x1b_Ga=T,f=100,s=1,v=1,t=d;${payload}\x1b\\`,
1396
- /\x1b_G([^\x1b]*)\x1b\\/,
1397
- )
1394
+ const match = await queryWithSentinel(`\x1b_Ga=T,f=100,s=1,v=1,t=d;${payload}\x1b\\`, /\x1b_G([^\x1b]*)\x1b\\/)
1398
1395
  if (match) return { pass: true, response: match[1] }
1399
1396
  return { pass: false, note: "No kitty graphics acknowledgment" }
1400
1397
  },
package/src/serve.ts CHANGED
@@ -164,6 +164,65 @@ export async function startDaemon(port = 0): Promise<void> {
164
164
  return
165
165
  }
166
166
 
167
+ if (url.pathname === "/exec" && req.method === "POST") {
168
+ // Execute raw escape sequence commands in this terminal
169
+ // POST body: { commands: [{ write: "\\x1b[6n", read: "\\x1b\\[(\\d+);(\\d+)R", timeout?: 1000 }, ...] }
170
+ const body = await readBody(req)
171
+ try {
172
+ const { commands } = JSON.parse(body) as {
173
+ commands: Array<{ write?: string; read?: string; timeout?: number; measure?: string }>
174
+ }
175
+ if (!Array.isArray(commands)) {
176
+ res.statusCode = 400
177
+ res.end(JSON.stringify({ error: "commands must be an array" }))
178
+ return
179
+ }
180
+
181
+ const results: Array<{ response?: string | null; width?: number | null; error?: string }> = []
182
+
183
+ await withRawMode(async () => {
184
+ process.stdout.write("\x1b[0m\x1b[2J\x1b[H")
185
+ for (const cmd of commands) {
186
+ try {
187
+ if (cmd.measure) {
188
+ // Measure rendered width of a string
189
+ const { measureRenderedWidth } = await import("./tty.ts")
190
+ const width = await measureRenderedWidth(cmd.measure)
191
+ results.push({ width })
192
+ } else if (cmd.write && cmd.read) {
193
+ // Write sequence, read response
194
+ const { query } = await import("./tty.ts")
195
+ const match = await query(
196
+ unescapeSequence(cmd.write),
197
+ new RegExp(cmd.read),
198
+ cmd.timeout ?? 1000,
199
+ )
200
+ results.push({ response: match ? match[0] : null })
201
+ } else if (cmd.write) {
202
+ // Just write, no response expected
203
+ process.stdout.write(unescapeSequence(cmd.write))
204
+ results.push({ response: "ok" })
205
+ } else {
206
+ results.push({ error: "command needs write, read, or measure" })
207
+ }
208
+ } catch (err) {
209
+ results.push({ error: err instanceof Error ? err.message : String(err) })
210
+ }
211
+ }
212
+ process.stdout.write("\x1b[0m\x1b[2J\x1b[H")
213
+ await drainStdin(500)
214
+ })
215
+ process.stdout.write("\x1bc")
216
+
217
+ console.log(`\x1b[2m[${new Date().toISOString()}] Executed ${commands.length} commands\x1b[0m`)
218
+ res.end(JSON.stringify({ terminal: terminal.name, results }))
219
+ } catch (err) {
220
+ res.statusCode = 400
221
+ res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }))
222
+ }
223
+ return
224
+ }
225
+
167
226
  // Default: show help
168
227
  res.end(
169
228
  JSON.stringify({
@@ -171,6 +230,7 @@ export async function startDaemon(port = 0): Promise<void> {
171
230
  "/info": "Terminal info",
172
231
  "/probe": "Run all probes",
173
232
  "/probe/single?id=sgr.bold": "Run single probe",
233
+ "/exec": "POST — execute raw escape sequence commands",
174
234
  },
175
235
  terminal: terminal.name,
176
236
  version: terminal.version,
@@ -217,3 +277,22 @@ export async function startDaemon(port = 0): Promise<void> {
217
277
  process.on("SIGTERM", cleanup)
218
278
  })
219
279
  }
280
+
281
+ /** Read full request body */
282
+ function readBody(req: IncomingMessage): Promise<string> {
283
+ return new Promise((resolve, reject) => {
284
+ const chunks: Buffer[] = []
285
+ req.on("data", (chunk: Buffer) => chunks.push(chunk))
286
+ req.on("end", () => resolve(Buffer.concat(chunks).toString()))
287
+ req.on("error", reject)
288
+ })
289
+ }
290
+
291
+ /** Convert \\x1b notation to actual escape characters */
292
+ function unescapeSequence(s: string): string {
293
+ return s.replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
294
+ .replace(/\\e/g, "\x1b")
295
+ .replace(/\\n/g, "\n")
296
+ .replace(/\\r/g, "\r")
297
+ .replace(/\\t/g, "\t")
298
+ }