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 +1 -1
- package/src/probes/index.ts +1 -4
- package/src/serve.ts +79 -0
package/package.json
CHANGED
package/src/probes/index.ts
CHANGED
|
@@ -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
|
+
}
|