gsd-pi 2.36.0-dev.d612764 → 2.36.0-dev.f887f4e
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/dist/resources/extensions/gsd/auto-dashboard.js +104 -334
- package/dist/resources/extensions/gsd/auto-loop.js +0 -11
- package/dist/resources/extensions/gsd/auto.js +0 -16
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +1 -51
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -25
- package/dist/resources/extensions/gsd/index.js +0 -5
- package/dist/resources/extensions/gsd/notifications.js +1 -10
- package/dist/resources/extensions/gsd/preferences-types.js +0 -2
- package/dist/resources/extensions/gsd/preferences-validation.js +0 -29
- package/dist/resources/extensions/gsd/preferences.js +0 -3
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +3 -4
- package/dist/resources/extensions/gsd/prompts/research-slice.md +2 -3
- package/dist/resources/extensions/gsd/templates/preferences.md +0 -6
- package/dist/resources/extensions/search-the-web/native-search.js +4 -45
- package/dist/resources/extensions/shared/terminal.js +0 -5
- package/dist/resources/extensions/subagent/index.js +60 -180
- package/package.json +1 -1
- package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal-image.js +0 -4
- package/packages/pi-tui/dist/terminal-image.js.map +1 -1
- package/packages/pi-tui/src/terminal-image.ts +0 -5
- package/src/resources/extensions/gsd/auto-dashboard.ts +116 -363
- package/src/resources/extensions/gsd/auto-loop.ts +0 -42
- package/src/resources/extensions/gsd/auto.ts +0 -21
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +1 -54
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -25
- package/src/resources/extensions/gsd/index.ts +0 -8
- package/src/resources/extensions/gsd/notifications.ts +1 -10
- package/src/resources/extensions/gsd/preferences-types.ts +0 -13
- package/src/resources/extensions/gsd/preferences-validation.ts +0 -26
- package/src/resources/extensions/gsd/preferences.ts +0 -4
- package/src/resources/extensions/gsd/prompts/research-milestone.md +3 -4
- package/src/resources/extensions/gsd/prompts/research-slice.md +2 -3
- package/src/resources/extensions/gsd/templates/preferences.md +0 -6
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +0 -2
- package/src/resources/extensions/gsd/tests/preferences.test.ts +0 -23
- package/src/resources/extensions/search-the-web/native-search.ts +4 -50
- package/src/resources/extensions/shared/terminal.ts +0 -5
- package/src/resources/extensions/subagent/index.ts +79 -236
- package/dist/resources/extensions/cmux/index.js +0 -321
- package/dist/resources/extensions/gsd/commands-cmux.js +0 -120
- package/src/resources/extensions/cmux/index.ts +0 -384
- package/src/resources/extensions/gsd/commands-cmux.ts +0 -143
- package/src/resources/extensions/gsd/tests/cmux.test.ts +0 -98
|
@@ -24,8 +24,6 @@ import { formatTokenCount } from "../shared/mod.js";
|
|
|
24
24
|
import { discoverAgents } from "./agents.js";
|
|
25
25
|
import { createIsolation, mergeDeltaPatches, readIsolationMode, } from "./isolation.js";
|
|
26
26
|
import { registerWorker, updateWorker } from "./worker-registry.js";
|
|
27
|
-
import { loadEffectiveGSDPreferences } from "../gsd/preferences.js";
|
|
28
|
-
import { CmuxClient, shellEscape } from "../cmux/index.js";
|
|
29
27
|
const MAX_PARALLEL_TASKS = 8;
|
|
30
28
|
const MAX_CONCURRENCY = 4;
|
|
31
29
|
const COLLAPSED_ITEM_COUNT = 10;
|
|
@@ -193,66 +191,6 @@ function writePromptToTempFile(agentName, prompt) {
|
|
|
193
191
|
fs.writeFileSync(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
|
|
194
192
|
return { dir: tmpDir, filePath };
|
|
195
193
|
}
|
|
196
|
-
function buildSubagentProcessArgs(agent, task, tmpPromptPath) {
|
|
197
|
-
const args = ["--mode", "json", "-p", "--no-session"];
|
|
198
|
-
if (agent.model)
|
|
199
|
-
args.push("--model", agent.model);
|
|
200
|
-
if (agent.tools && agent.tools.length > 0)
|
|
201
|
-
args.push("--tools", agent.tools.join(","));
|
|
202
|
-
if (tmpPromptPath)
|
|
203
|
-
args.push("--append-system-prompt", tmpPromptPath);
|
|
204
|
-
args.push(`Task: ${task}`);
|
|
205
|
-
return args;
|
|
206
|
-
}
|
|
207
|
-
function processSubagentEventLine(line, currentResult, emitUpdate) {
|
|
208
|
-
if (!line.trim())
|
|
209
|
-
return;
|
|
210
|
-
let event;
|
|
211
|
-
try {
|
|
212
|
-
event = JSON.parse(line);
|
|
213
|
-
}
|
|
214
|
-
catch {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
if (event.type === "message_end" && event.message) {
|
|
218
|
-
const msg = event.message;
|
|
219
|
-
currentResult.messages.push(msg);
|
|
220
|
-
if (msg.role === "assistant") {
|
|
221
|
-
currentResult.usage.turns++;
|
|
222
|
-
const usage = msg.usage;
|
|
223
|
-
if (usage) {
|
|
224
|
-
currentResult.usage.input += usage.input || 0;
|
|
225
|
-
currentResult.usage.output += usage.output || 0;
|
|
226
|
-
currentResult.usage.cacheRead += usage.cacheRead || 0;
|
|
227
|
-
currentResult.usage.cacheWrite += usage.cacheWrite || 0;
|
|
228
|
-
currentResult.usage.cost += usage.cost?.total || 0;
|
|
229
|
-
currentResult.usage.contextTokens = usage.totalTokens || 0;
|
|
230
|
-
}
|
|
231
|
-
if (!currentResult.model && msg.model)
|
|
232
|
-
currentResult.model = msg.model;
|
|
233
|
-
if (msg.stopReason)
|
|
234
|
-
currentResult.stopReason = msg.stopReason;
|
|
235
|
-
if (msg.errorMessage)
|
|
236
|
-
currentResult.errorMessage = msg.errorMessage;
|
|
237
|
-
}
|
|
238
|
-
emitUpdate();
|
|
239
|
-
}
|
|
240
|
-
if (event.type === "tool_result_end" && event.message) {
|
|
241
|
-
currentResult.messages.push(event.message);
|
|
242
|
-
emitUpdate();
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
async function waitForFile(filePath, signal, timeoutMs = 30 * 60 * 1000) {
|
|
246
|
-
const started = Date.now();
|
|
247
|
-
while (Date.now() - started < timeoutMs) {
|
|
248
|
-
if (signal?.aborted)
|
|
249
|
-
return false;
|
|
250
|
-
if (fs.existsSync(filePath))
|
|
251
|
-
return true;
|
|
252
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
253
|
-
}
|
|
254
|
-
return false;
|
|
255
|
-
}
|
|
256
194
|
async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails) {
|
|
257
195
|
const agent = agents.find((a) => a.name === agentName);
|
|
258
196
|
if (!agent) {
|
|
@@ -268,6 +206,11 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
268
206
|
step,
|
|
269
207
|
};
|
|
270
208
|
}
|
|
209
|
+
const args = ["--mode", "json", "-p", "--no-session"];
|
|
210
|
+
if (agent.model)
|
|
211
|
+
args.push("--model", agent.model);
|
|
212
|
+
if (agent.tools && agent.tools.length > 0)
|
|
213
|
+
args.push("--tools", agent.tools.join(","));
|
|
271
214
|
let tmpPromptDir = null;
|
|
272
215
|
let tmpPromptPath = null;
|
|
273
216
|
const currentResult = {
|
|
@@ -294,8 +237,9 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
294
237
|
const tmp = writePromptToTempFile(agent.name, agent.systemPrompt);
|
|
295
238
|
tmpPromptDir = tmp.dir;
|
|
296
239
|
tmpPromptPath = tmp.filePath;
|
|
240
|
+
args.push("--append-system-prompt", tmpPromptPath);
|
|
297
241
|
}
|
|
298
|
-
|
|
242
|
+
args.push(`Task: ${task}`);
|
|
299
243
|
let wasAborted = false;
|
|
300
244
|
const exitCode = await new Promise((resolve) => {
|
|
301
245
|
const bundledPaths = (process.env.GSD_BUNDLED_EXTENSION_PATHS ?? "").split(path.delimiter).map(s => s.trim()).filter(Boolean);
|
|
@@ -303,12 +247,50 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
303
247
|
const proc = spawn(process.execPath, [process.env.GSD_BIN_PATH, ...extensionArgs, ...args], { cwd: cwd ?? defaultCwd, shell: false, stdio: ["ignore", "pipe", "pipe"] });
|
|
304
248
|
liveSubagentProcesses.add(proc);
|
|
305
249
|
let buffer = "";
|
|
250
|
+
const processLine = (line) => {
|
|
251
|
+
if (!line.trim())
|
|
252
|
+
return;
|
|
253
|
+
let event;
|
|
254
|
+
try {
|
|
255
|
+
event = JSON.parse(line);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (event.type === "message_end" && event.message) {
|
|
261
|
+
const msg = event.message;
|
|
262
|
+
currentResult.messages.push(msg);
|
|
263
|
+
if (msg.role === "assistant") {
|
|
264
|
+
currentResult.usage.turns++;
|
|
265
|
+
const usage = msg.usage;
|
|
266
|
+
if (usage) {
|
|
267
|
+
currentResult.usage.input += usage.input || 0;
|
|
268
|
+
currentResult.usage.output += usage.output || 0;
|
|
269
|
+
currentResult.usage.cacheRead += usage.cacheRead || 0;
|
|
270
|
+
currentResult.usage.cacheWrite += usage.cacheWrite || 0;
|
|
271
|
+
currentResult.usage.cost += usage.cost?.total || 0;
|
|
272
|
+
currentResult.usage.contextTokens = usage.totalTokens || 0;
|
|
273
|
+
}
|
|
274
|
+
if (!currentResult.model && msg.model)
|
|
275
|
+
currentResult.model = msg.model;
|
|
276
|
+
if (msg.stopReason)
|
|
277
|
+
currentResult.stopReason = msg.stopReason;
|
|
278
|
+
if (msg.errorMessage)
|
|
279
|
+
currentResult.errorMessage = msg.errorMessage;
|
|
280
|
+
}
|
|
281
|
+
emitUpdate();
|
|
282
|
+
}
|
|
283
|
+
if (event.type === "tool_result_end" && event.message) {
|
|
284
|
+
currentResult.messages.push(event.message);
|
|
285
|
+
emitUpdate();
|
|
286
|
+
}
|
|
287
|
+
};
|
|
306
288
|
proc.stdout.on("data", (data) => {
|
|
307
289
|
buffer += data.toString();
|
|
308
290
|
const lines = buffer.split("\n");
|
|
309
291
|
buffer = lines.pop() || "";
|
|
310
292
|
for (const line of lines)
|
|
311
|
-
|
|
293
|
+
processLine(line);
|
|
312
294
|
});
|
|
313
295
|
proc.stderr.on("data", (data) => {
|
|
314
296
|
currentResult.stderr += data.toString();
|
|
@@ -316,7 +298,7 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
316
298
|
proc.on("close", (code) => {
|
|
317
299
|
liveSubagentProcesses.delete(proc);
|
|
318
300
|
if (buffer.trim())
|
|
319
|
-
|
|
301
|
+
processLine(buffer);
|
|
320
302
|
resolve(code ?? 0);
|
|
321
303
|
});
|
|
322
304
|
proc.on("error", () => {
|
|
@@ -360,103 +342,6 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
360
342
|
}
|
|
361
343
|
}
|
|
362
344
|
}
|
|
363
|
-
async function runSingleAgentInCmuxSplit(cmuxClient, direction, defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails) {
|
|
364
|
-
const agent = agents.find((a) => a.name === agentName);
|
|
365
|
-
if (!agent) {
|
|
366
|
-
return runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails);
|
|
367
|
-
}
|
|
368
|
-
let tmpPromptDir = null;
|
|
369
|
-
let tmpPromptPath = null;
|
|
370
|
-
let tmpOutputDir = null;
|
|
371
|
-
const currentResult = {
|
|
372
|
-
agent: agentName,
|
|
373
|
-
agentSource: agent.source,
|
|
374
|
-
task,
|
|
375
|
-
exitCode: 0,
|
|
376
|
-
messages: [],
|
|
377
|
-
stderr: "",
|
|
378
|
-
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
379
|
-
model: agent.model,
|
|
380
|
-
step,
|
|
381
|
-
};
|
|
382
|
-
const emitUpdate = () => {
|
|
383
|
-
if (onUpdate) {
|
|
384
|
-
onUpdate({
|
|
385
|
-
content: [{ type: "text", text: getFinalOutput(currentResult.messages) || "(running...)" }],
|
|
386
|
-
details: makeDetails([currentResult]),
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
};
|
|
390
|
-
try {
|
|
391
|
-
if (agent.systemPrompt.trim()) {
|
|
392
|
-
const tmp = writePromptToTempFile(agent.name, agent.systemPrompt);
|
|
393
|
-
tmpPromptDir = tmp.dir;
|
|
394
|
-
tmpPromptPath = tmp.filePath;
|
|
395
|
-
}
|
|
396
|
-
tmpOutputDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-cmux-"));
|
|
397
|
-
const stdoutPath = path.join(tmpOutputDir, "stdout.jsonl");
|
|
398
|
-
const stderrPath = path.join(tmpOutputDir, "stderr.log");
|
|
399
|
-
const exitPath = path.join(tmpOutputDir, "exit.code");
|
|
400
|
-
const cmuxSurfaceId = await cmuxClient.createSplit(direction);
|
|
401
|
-
if (!cmuxSurfaceId) {
|
|
402
|
-
return runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails);
|
|
403
|
-
}
|
|
404
|
-
const bundledPaths = (process.env.GSD_BUNDLED_EXTENSION_PATHS ?? "").split(path.delimiter).map((s) => s.trim()).filter(Boolean);
|
|
405
|
-
const extensionArgs = bundledPaths.flatMap((p) => ["--extension", p]);
|
|
406
|
-
const processArgs = [process.env.GSD_BIN_PATH, ...extensionArgs, ...buildSubagentProcessArgs(agent, task, tmpPromptPath)];
|
|
407
|
-
const innerScript = [
|
|
408
|
-
`cd ${shellEscape(cwd ?? defaultCwd)}`,
|
|
409
|
-
"set -o pipefail",
|
|
410
|
-
`${shellEscape(process.execPath)} ${processArgs.map(shellEscape).join(" ")} 2> >(tee ${shellEscape(stderrPath)} >&2) | tee ${shellEscape(stdoutPath)}`,
|
|
411
|
-
"status=${PIPESTATUS[0]}",
|
|
412
|
-
`printf '%s' "$status" > ${shellEscape(exitPath)}`,
|
|
413
|
-
].join("; ");
|
|
414
|
-
const sent = await cmuxClient.sendSurface(cmuxSurfaceId, `bash -lc ${shellEscape(innerScript)}`);
|
|
415
|
-
if (!sent) {
|
|
416
|
-
return runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails);
|
|
417
|
-
}
|
|
418
|
-
const finished = await waitForFile(exitPath, signal);
|
|
419
|
-
if (!finished) {
|
|
420
|
-
currentResult.exitCode = 1;
|
|
421
|
-
currentResult.stderr = "cmux split execution timed out or was aborted";
|
|
422
|
-
return currentResult;
|
|
423
|
-
}
|
|
424
|
-
if (fs.existsSync(stdoutPath)) {
|
|
425
|
-
const stdout = fs.readFileSync(stdoutPath, "utf-8");
|
|
426
|
-
for (const line of stdout.split("\n")) {
|
|
427
|
-
processSubagentEventLine(line, currentResult, emitUpdate);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
if (fs.existsSync(stderrPath)) {
|
|
431
|
-
currentResult.stderr = fs.readFileSync(stderrPath, "utf-8");
|
|
432
|
-
}
|
|
433
|
-
currentResult.exitCode = Number.parseInt(fs.readFileSync(exitPath, "utf-8").trim() || "1", 10) || 0;
|
|
434
|
-
return currentResult;
|
|
435
|
-
}
|
|
436
|
-
finally {
|
|
437
|
-
if (tmpPromptPath)
|
|
438
|
-
try {
|
|
439
|
-
fs.unlinkSync(tmpPromptPath);
|
|
440
|
-
}
|
|
441
|
-
catch {
|
|
442
|
-
/* ignore */
|
|
443
|
-
}
|
|
444
|
-
if (tmpPromptDir)
|
|
445
|
-
try {
|
|
446
|
-
fs.rmdirSync(tmpPromptDir);
|
|
447
|
-
}
|
|
448
|
-
catch {
|
|
449
|
-
/* ignore */
|
|
450
|
-
}
|
|
451
|
-
if (tmpOutputDir)
|
|
452
|
-
try {
|
|
453
|
-
fs.rmSync(tmpOutputDir, { recursive: true, force: true });
|
|
454
|
-
}
|
|
455
|
-
catch {
|
|
456
|
-
/* ignore */
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
345
|
const TaskItem = Type.Object({
|
|
461
346
|
agent: Type.String({ description: "Name of the agent to invoke" }),
|
|
462
347
|
task: Type.String({ description: "Task to delegate to the agent" }),
|
|
@@ -527,8 +412,6 @@ export default function (pi) {
|
|
|
527
412
|
const discovery = discoverAgents(ctx.cwd, agentScope);
|
|
528
413
|
const agents = discovery.agents;
|
|
529
414
|
const confirmProjectAgents = params.confirmProjectAgents ?? false;
|
|
530
|
-
const cmuxClient = CmuxClient.fromPreferences(loadEffectiveGSDPreferences()?.preferences);
|
|
531
|
-
const cmuxSplitsEnabled = cmuxClient.getConfig().splits;
|
|
532
415
|
// Resolve isolation mode
|
|
533
416
|
const isolationMode = readIsolationMode();
|
|
534
417
|
const useIsolation = Boolean(params.isolated) && isolationMode !== "none";
|
|
@@ -658,24 +541,23 @@ export default function (pi) {
|
|
|
658
541
|
const batchSize = params.tasks.length;
|
|
659
542
|
const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
|
|
660
543
|
const workerId = registerWorker(t.agent, t.task, index, batchSize, batchId);
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}
|
|
668
|
-
|
|
544
|
+
let result = await runSingleAgent(ctx.cwd, agents, t.agent, t.task, t.cwd, undefined, signal,
|
|
545
|
+
// Per-task update callback
|
|
546
|
+
(partial) => {
|
|
547
|
+
if (partial.details?.results[0]) {
|
|
548
|
+
allResults[index] = partial.details.results[0];
|
|
549
|
+
emitParallelUpdate();
|
|
550
|
+
}
|
|
551
|
+
}, makeDetails("parallel"));
|
|
552
|
+
// Auto-retry failed tasks (likely API rate limit or transient error)
|
|
553
|
+
const isFailed = result.exitCode !== 0 || (result.messages.length === 0 && !signal?.aborted);
|
|
554
|
+
if (isFailed && MAX_RETRIES > 0 && !signal?.aborted) {
|
|
555
|
+
result = await runSingleAgent(ctx.cwd, agents, t.agent, t.task, t.cwd, undefined, signal, (partial) => {
|
|
669
556
|
if (partial.details?.results[0]) {
|
|
670
557
|
allResults[index] = partial.details.results[0];
|
|
671
558
|
emitParallelUpdate();
|
|
672
559
|
}
|
|
673
560
|
}, makeDetails("parallel"));
|
|
674
|
-
let result = await runTask();
|
|
675
|
-
// Auto-retry failed tasks (likely API rate limit or transient error)
|
|
676
|
-
const isFailed = result.exitCode !== 0 || (result.messages.length === 0 && !signal?.aborted);
|
|
677
|
-
if (isFailed && MAX_RETRIES > 0 && !signal?.aborted) {
|
|
678
|
-
result = await runTask();
|
|
679
561
|
}
|
|
680
562
|
updateWorker(workerId, result.exitCode === 0 ? "completed" : "failed");
|
|
681
563
|
allResults[index] = result;
|
|
@@ -709,9 +591,7 @@ export default function (pi) {
|
|
|
709
591
|
const taskId = crypto.randomUUID();
|
|
710
592
|
isolation = await createIsolation(effectiveCwd, taskId, isolationMode);
|
|
711
593
|
}
|
|
712
|
-
const result =
|
|
713
|
-
? await runSingleAgentInCmuxSplit(cmuxClient, "right", ctx.cwd, agents, params.agent, params.task, isolation ? isolation.workDir : params.cwd, undefined, signal, onUpdate, makeDetails("single"))
|
|
714
|
-
: await runSingleAgent(ctx.cwd, agents, params.agent, params.task, isolation ? isolation.workDir : params.cwd, undefined, signal, onUpdate, makeDetails("single"));
|
|
594
|
+
const result = await runSingleAgent(ctx.cwd, agents, params.agent, params.task, isolation ? isolation.workDir : params.cwd, undefined, signal, onUpdate, makeDetails("single"));
|
|
715
595
|
// Capture and merge delta if isolated
|
|
716
596
|
if (isolation) {
|
|
717
597
|
const patches = await isolation.captureDelta();
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal-image.d.ts","sourceRoot":"","sources":["../src/terminal-image.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEtD,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD,wBAAgB,iBAAiB,IAAI,cAAc,CAElD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAE5D;AAED,wBAAgB,kBAAkB,IAAI,oBAAoB,
|
|
1
|
+
{"version":3,"file":"terminal-image.d.ts","sourceRoot":"","sources":["../src/terminal-image.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEtD,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD,wBAAgB,iBAAiB,IAAI,cAAc,CAElD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAE5D;AAED,wBAAgB,kBAAkB,IAAI,oBAAoB,CA+BzD;AAED,wBAAgB,eAAe,IAAI,oBAAoB,CAKtD;AAED,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAKD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOjD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAGxC;AAED,wBAAgB,WAAW,CAC1B,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;IACR,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CACZ,GACJ,MAAM,CAkCR;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,YAAY,CAC3B,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;IACR,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;CACZ,GACJ,MAAM,CAcR;AAED,wBAAgB,kBAAkB,CACjC,eAAe,EAAE,eAAe,EAChC,gBAAgB,EAAE,MAAM,EACxB,cAAc,GAAE,cAA6C,GAC3D,MAAM,CAMR;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAS5F;AAED,wBAAgB,WAAW,CAC1B,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,eAAe,EAChC,OAAO,GAAE,kBAAuB,GAC9B;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA0B7D;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAMvG"}
|
|
@@ -11,13 +11,9 @@ export function detectCapabilities() {
|
|
|
11
11
|
const termProgram = process.env.TERM_PROGRAM?.toLowerCase() || "";
|
|
12
12
|
const term = process.env.TERM?.toLowerCase() || "";
|
|
13
13
|
const colorTerm = process.env.COLORTERM?.toLowerCase() || "";
|
|
14
|
-
const isCmux = Boolean(process.env.CMUX_WORKSPACE_ID && process.env.CMUX_SURFACE_ID);
|
|
15
14
|
if (process.env.KITTY_WINDOW_ID || termProgram === "kitty") {
|
|
16
15
|
return { images: "kitty", trueColor: true, hyperlinks: true };
|
|
17
16
|
}
|
|
18
|
-
if (isCmux) {
|
|
19
|
-
return { images: "kitty", trueColor: true, hyperlinks: true };
|
|
20
|
-
}
|
|
21
17
|
if (termProgram === "ghostty" || term.includes("ghostty") || process.env.GHOSTTY_RESOURCES_DIR) {
|
|
22
18
|
return { images: "kitty", trueColor: true, hyperlinks: true };
|
|
23
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal-image.js","sourceRoot":"","sources":["../src/terminal-image.ts"],"names":[],"mappings":"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","sourcesContent":["export type ImageProtocol = \"kitty\" | \"iterm2\" | null;\n\nexport interface TerminalCapabilities {\n\timages: ImageProtocol;\n\ttrueColor: boolean;\n\thyperlinks: boolean;\n}\n\nexport interface CellDimensions {\n\twidthPx: number;\n\theightPx: number;\n}\n\nexport interface ImageDimensions {\n\twidthPx: number;\n\theightPx: number;\n}\n\nexport interface ImageRenderOptions {\n\tmaxWidthCells?: number;\n\tmaxHeightCells?: number;\n\tpreserveAspectRatio?: boolean;\n\t/** Kitty image ID. If provided, reuses/replaces existing image with this ID. */\n\timageId?: number;\n}\n\nlet cachedCapabilities: TerminalCapabilities | null = null;\n\n// Default cell dimensions - updated by TUI when terminal responds to query\nlet cellDimensions: CellDimensions = { widthPx: 9, heightPx: 18 };\n\nexport function getCellDimensions(): CellDimensions {\n\treturn cellDimensions;\n}\n\nexport function setCellDimensions(dims: CellDimensions): void {\n\tcellDimensions = dims;\n}\n\nexport function detectCapabilities(): TerminalCapabilities {\n\tconst termProgram = process.env.TERM_PROGRAM?.toLowerCase() || \"\";\n\tconst term = process.env.TERM?.toLowerCase() || \"\";\n\tconst colorTerm = process.env.COLORTERM?.toLowerCase() || \"\";\n\tconst isCmux = Boolean(process.env.CMUX_WORKSPACE_ID && process.env.CMUX_SURFACE_ID);\n\n\tif (process.env.KITTY_WINDOW_ID || termProgram === \"kitty\") {\n\t\treturn { images: \"kitty\", trueColor: true, hyperlinks: true };\n\t}\n\n\tif (isCmux) {\n\t\treturn { images: \"kitty\", trueColor: true, hyperlinks: true };\n\t}\n\n\tif (termProgram === \"ghostty\" || term.includes(\"ghostty\") || process.env.GHOSTTY_RESOURCES_DIR) {\n\t\treturn { images: \"kitty\", trueColor: true, hyperlinks: true };\n\t}\n\n\tif (process.env.WEZTERM_PANE || termProgram === \"wezterm\") {\n\t\treturn { images: \"kitty\", trueColor: true, hyperlinks: true };\n\t}\n\n\tif (process.env.ITERM_SESSION_ID || termProgram === \"iterm.app\") {\n\t\treturn { images: \"iterm2\", trueColor: true, hyperlinks: true };\n\t}\n\n\tif (termProgram === \"vscode\") {\n\t\treturn { images: null, trueColor: true, hyperlinks: true };\n\t}\n\n\tif (termProgram === \"alacritty\") {\n\t\treturn { images: null, trueColor: true, hyperlinks: true };\n\t}\n\n\tconst trueColor = colorTerm === \"truecolor\" || colorTerm === \"24bit\";\n\treturn { images: null, trueColor, hyperlinks: true };\n}\n\nexport function getCapabilities(): TerminalCapabilities {\n\tif (!cachedCapabilities) {\n\t\tcachedCapabilities = detectCapabilities();\n\t}\n\treturn cachedCapabilities;\n}\n\nexport function resetCapabilitiesCache(): void {\n\tcachedCapabilities = null;\n}\n\nconst KITTY_PREFIX = \"\\x1b_G\";\nconst ITERM2_PREFIX = \"\\x1b]1337;File=\";\n\nexport function isImageLine(line: string): boolean {\n\t// Fast path: sequence at line start (single-row images)\n\tif (line.startsWith(KITTY_PREFIX) || line.startsWith(ITERM2_PREFIX)) {\n\t\treturn true;\n\t}\n\t// Slow path: sequence elsewhere (multi-row images have cursor-up prefix)\n\treturn line.includes(KITTY_PREFIX) || line.includes(ITERM2_PREFIX);\n}\n\n/**\n * Generate a random image ID for Kitty graphics protocol.\n * Uses random IDs to avoid collisions between different module instances\n * (e.g., main app vs extensions).\n */\nexport function allocateImageId(): number {\n\t// Use random ID in range [1, 0xffffffff] to avoid collisions\n\treturn Math.floor(Math.random() * 0xfffffffe) + 1;\n}\n\nexport function encodeKitty(\n\tbase64Data: string,\n\toptions: {\n\t\tcolumns?: number;\n\t\trows?: number;\n\t\timageId?: number;\n\t} = {},\n): string {\n\tconst CHUNK_SIZE = 4096;\n\n\tconst params: string[] = [\"a=T\", \"f=100\", \"q=2\"];\n\n\tif (options.columns) params.push(`c=${options.columns}`);\n\tif (options.rows) params.push(`r=${options.rows}`);\n\tif (options.imageId) params.push(`i=${options.imageId}`);\n\n\tif (base64Data.length <= CHUNK_SIZE) {\n\t\treturn `\\x1b_G${params.join(\",\")};${base64Data}\\x1b\\\\`;\n\t}\n\n\tconst chunks: string[] = [];\n\tlet offset = 0;\n\tlet isFirst = true;\n\n\twhile (offset < base64Data.length) {\n\t\tconst chunk = base64Data.slice(offset, offset + CHUNK_SIZE);\n\t\tconst isLast = offset + CHUNK_SIZE >= base64Data.length;\n\n\t\tif (isFirst) {\n\t\t\tchunks.push(`\\x1b_G${params.join(\",\")},m=1;${chunk}\\x1b\\\\`);\n\t\t\tisFirst = false;\n\t\t} else if (isLast) {\n\t\t\tchunks.push(`\\x1b_Gm=0;${chunk}\\x1b\\\\`);\n\t\t} else {\n\t\t\tchunks.push(`\\x1b_Gm=1;${chunk}\\x1b\\\\`);\n\t\t}\n\n\t\toffset += CHUNK_SIZE;\n\t}\n\n\treturn chunks.join(\"\");\n}\n\n/**\n * Delete a Kitty graphics image by ID.\n * Uses uppercase 'I' to also free the image data.\n */\nexport function deleteKittyImage(imageId: number): string {\n\treturn `\\x1b_Ga=d,d=I,i=${imageId}\\x1b\\\\`;\n}\n\n/**\n * Delete all visible Kitty graphics images.\n * Uses uppercase 'A' to also free the image data.\n */\nexport function deleteAllKittyImages(): string {\n\treturn `\\x1b_Ga=d,d=A\\x1b\\\\`;\n}\n\nexport function encodeITerm2(\n\tbase64Data: string,\n\toptions: {\n\t\twidth?: number | string;\n\t\theight?: number | string;\n\t\tname?: string;\n\t\tpreserveAspectRatio?: boolean;\n\t\tinline?: boolean;\n\t} = {},\n): string {\n\tconst params: string[] = [`inline=${options.inline !== false ? 1 : 0}`];\n\n\tif (options.width !== undefined) params.push(`width=${options.width}`);\n\tif (options.height !== undefined) params.push(`height=${options.height}`);\n\tif (options.name) {\n\t\tconst nameBase64 = Buffer.from(options.name).toString(\"base64\");\n\t\tparams.push(`name=${nameBase64}`);\n\t}\n\tif (options.preserveAspectRatio === false) {\n\t\tparams.push(\"preserveAspectRatio=0\");\n\t}\n\n\treturn `\\x1b]1337;File=${params.join(\";\")}:${base64Data}\\x07`;\n}\n\nexport function calculateImageRows(\n\timageDimensions: ImageDimensions,\n\ttargetWidthCells: number,\n\tcellDimensions: CellDimensions = { widthPx: 9, heightPx: 18 },\n): number {\n\tconst targetWidthPx = targetWidthCells * cellDimensions.widthPx;\n\tconst scale = targetWidthPx / imageDimensions.widthPx;\n\tconst scaledHeightPx = imageDimensions.heightPx * scale;\n\tconst rows = Math.ceil(scaledHeightPx / cellDimensions.heightPx);\n\treturn Math.max(1, rows);\n}\n\n/**\n * Parse image dimensions using the native Rust image module.\n * Auto-detects format from byte content (PNG, JPEG, GIF, WebP).\n */\nexport async function getImageDimensions(base64Data: string): Promise<ImageDimensions | null> {\n\tconst { parseImage: parse } = await import(\"@gsd/native/image\");\n\ttry {\n\t\tconst bytes = new Uint8Array(Buffer.from(base64Data, \"base64\"));\n\t\tconst handle = await parse(bytes);\n\t\treturn { widthPx: handle.width, heightPx: handle.height };\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function renderImage(\n\tbase64Data: string,\n\timageDimensions: ImageDimensions,\n\toptions: ImageRenderOptions = {},\n): { sequence: string; rows: number; imageId?: number } | null {\n\tconst caps = getCapabilities();\n\n\tif (!caps.images) {\n\t\treturn null;\n\t}\n\n\tconst maxWidth = options.maxWidthCells ?? 80;\n\tconst rows = calculateImageRows(imageDimensions, maxWidth, getCellDimensions());\n\n\tif (caps.images === \"kitty\") {\n\t\t// Only use imageId if explicitly provided - static images don't need IDs\n\t\tconst sequence = encodeKitty(base64Data, { columns: maxWidth, rows, imageId: options.imageId });\n\t\treturn { sequence, rows, imageId: options.imageId };\n\t}\n\n\tif (caps.images === \"iterm2\") {\n\t\tconst sequence = encodeITerm2(base64Data, {\n\t\t\twidth: maxWidth,\n\t\t\theight: \"auto\",\n\t\t\tpreserveAspectRatio: options.preserveAspectRatio ?? true,\n\t\t});\n\t\treturn { sequence, rows };\n\t}\n\n\treturn null;\n}\n\nexport function imageFallback(mimeType: string, dimensions?: ImageDimensions, filename?: string): string {\n\tconst parts: string[] = [];\n\tif (filename) parts.push(filename);\n\tparts.push(`[${mimeType}]`);\n\tif (dimensions) parts.push(`${dimensions.widthPx}x${dimensions.heightPx}`);\n\treturn `[Image: ${parts.join(\" \")}]`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"terminal-image.js","sourceRoot":"","sources":["../src/terminal-image.ts"],"names":[],"mappings":"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","sourcesContent":["export type ImageProtocol = \"kitty\" | \"iterm2\" | null;\n\nexport interface TerminalCapabilities {\n\timages: ImageProtocol;\n\ttrueColor: boolean;\n\thyperlinks: boolean;\n}\n\nexport interface CellDimensions {\n\twidthPx: number;\n\theightPx: number;\n}\n\nexport interface ImageDimensions {\n\twidthPx: number;\n\theightPx: number;\n}\n\nexport interface ImageRenderOptions {\n\tmaxWidthCells?: number;\n\tmaxHeightCells?: number;\n\tpreserveAspectRatio?: boolean;\n\t/** Kitty image ID. If provided, reuses/replaces existing image with this ID. */\n\timageId?: number;\n}\n\nlet cachedCapabilities: TerminalCapabilities | null = null;\n\n// Default cell dimensions - updated by TUI when terminal responds to query\nlet cellDimensions: CellDimensions = { widthPx: 9, heightPx: 18 };\n\nexport function getCellDimensions(): CellDimensions {\n\treturn cellDimensions;\n}\n\nexport function setCellDimensions(dims: CellDimensions): void {\n\tcellDimensions = dims;\n}\n\nexport function detectCapabilities(): TerminalCapabilities {\n\tconst termProgram = process.env.TERM_PROGRAM?.toLowerCase() || \"\";\n\tconst term = process.env.TERM?.toLowerCase() || \"\";\n\tconst colorTerm = process.env.COLORTERM?.toLowerCase() || \"\";\n\n\tif (process.env.KITTY_WINDOW_ID || termProgram === \"kitty\") {\n\t\treturn { images: \"kitty\", trueColor: true, hyperlinks: true };\n\t}\n\n\tif (termProgram === \"ghostty\" || term.includes(\"ghostty\") || process.env.GHOSTTY_RESOURCES_DIR) {\n\t\treturn { images: \"kitty\", trueColor: true, hyperlinks: true };\n\t}\n\n\tif (process.env.WEZTERM_PANE || termProgram === \"wezterm\") {\n\t\treturn { images: \"kitty\", trueColor: true, hyperlinks: true };\n\t}\n\n\tif (process.env.ITERM_SESSION_ID || termProgram === \"iterm.app\") {\n\t\treturn { images: \"iterm2\", trueColor: true, hyperlinks: true };\n\t}\n\n\tif (termProgram === \"vscode\") {\n\t\treturn { images: null, trueColor: true, hyperlinks: true };\n\t}\n\n\tif (termProgram === \"alacritty\") {\n\t\treturn { images: null, trueColor: true, hyperlinks: true };\n\t}\n\n\tconst trueColor = colorTerm === \"truecolor\" || colorTerm === \"24bit\";\n\treturn { images: null, trueColor, hyperlinks: true };\n}\n\nexport function getCapabilities(): TerminalCapabilities {\n\tif (!cachedCapabilities) {\n\t\tcachedCapabilities = detectCapabilities();\n\t}\n\treturn cachedCapabilities;\n}\n\nexport function resetCapabilitiesCache(): void {\n\tcachedCapabilities = null;\n}\n\nconst KITTY_PREFIX = \"\\x1b_G\";\nconst ITERM2_PREFIX = \"\\x1b]1337;File=\";\n\nexport function isImageLine(line: string): boolean {\n\t// Fast path: sequence at line start (single-row images)\n\tif (line.startsWith(KITTY_PREFIX) || line.startsWith(ITERM2_PREFIX)) {\n\t\treturn true;\n\t}\n\t// Slow path: sequence elsewhere (multi-row images have cursor-up prefix)\n\treturn line.includes(KITTY_PREFIX) || line.includes(ITERM2_PREFIX);\n}\n\n/**\n * Generate a random image ID for Kitty graphics protocol.\n * Uses random IDs to avoid collisions between different module instances\n * (e.g., main app vs extensions).\n */\nexport function allocateImageId(): number {\n\t// Use random ID in range [1, 0xffffffff] to avoid collisions\n\treturn Math.floor(Math.random() * 0xfffffffe) + 1;\n}\n\nexport function encodeKitty(\n\tbase64Data: string,\n\toptions: {\n\t\tcolumns?: number;\n\t\trows?: number;\n\t\timageId?: number;\n\t} = {},\n): string {\n\tconst CHUNK_SIZE = 4096;\n\n\tconst params: string[] = [\"a=T\", \"f=100\", \"q=2\"];\n\n\tif (options.columns) params.push(`c=${options.columns}`);\n\tif (options.rows) params.push(`r=${options.rows}`);\n\tif (options.imageId) params.push(`i=${options.imageId}`);\n\n\tif (base64Data.length <= CHUNK_SIZE) {\n\t\treturn `\\x1b_G${params.join(\",\")};${base64Data}\\x1b\\\\`;\n\t}\n\n\tconst chunks: string[] = [];\n\tlet offset = 0;\n\tlet isFirst = true;\n\n\twhile (offset < base64Data.length) {\n\t\tconst chunk = base64Data.slice(offset, offset + CHUNK_SIZE);\n\t\tconst isLast = offset + CHUNK_SIZE >= base64Data.length;\n\n\t\tif (isFirst) {\n\t\t\tchunks.push(`\\x1b_G${params.join(\",\")},m=1;${chunk}\\x1b\\\\`);\n\t\t\tisFirst = false;\n\t\t} else if (isLast) {\n\t\t\tchunks.push(`\\x1b_Gm=0;${chunk}\\x1b\\\\`);\n\t\t} else {\n\t\t\tchunks.push(`\\x1b_Gm=1;${chunk}\\x1b\\\\`);\n\t\t}\n\n\t\toffset += CHUNK_SIZE;\n\t}\n\n\treturn chunks.join(\"\");\n}\n\n/**\n * Delete a Kitty graphics image by ID.\n * Uses uppercase 'I' to also free the image data.\n */\nexport function deleteKittyImage(imageId: number): string {\n\treturn `\\x1b_Ga=d,d=I,i=${imageId}\\x1b\\\\`;\n}\n\n/**\n * Delete all visible Kitty graphics images.\n * Uses uppercase 'A' to also free the image data.\n */\nexport function deleteAllKittyImages(): string {\n\treturn `\\x1b_Ga=d,d=A\\x1b\\\\`;\n}\n\nexport function encodeITerm2(\n\tbase64Data: string,\n\toptions: {\n\t\twidth?: number | string;\n\t\theight?: number | string;\n\t\tname?: string;\n\t\tpreserveAspectRatio?: boolean;\n\t\tinline?: boolean;\n\t} = {},\n): string {\n\tconst params: string[] = [`inline=${options.inline !== false ? 1 : 0}`];\n\n\tif (options.width !== undefined) params.push(`width=${options.width}`);\n\tif (options.height !== undefined) params.push(`height=${options.height}`);\n\tif (options.name) {\n\t\tconst nameBase64 = Buffer.from(options.name).toString(\"base64\");\n\t\tparams.push(`name=${nameBase64}`);\n\t}\n\tif (options.preserveAspectRatio === false) {\n\t\tparams.push(\"preserveAspectRatio=0\");\n\t}\n\n\treturn `\\x1b]1337;File=${params.join(\";\")}:${base64Data}\\x07`;\n}\n\nexport function calculateImageRows(\n\timageDimensions: ImageDimensions,\n\ttargetWidthCells: number,\n\tcellDimensions: CellDimensions = { widthPx: 9, heightPx: 18 },\n): number {\n\tconst targetWidthPx = targetWidthCells * cellDimensions.widthPx;\n\tconst scale = targetWidthPx / imageDimensions.widthPx;\n\tconst scaledHeightPx = imageDimensions.heightPx * scale;\n\tconst rows = Math.ceil(scaledHeightPx / cellDimensions.heightPx);\n\treturn Math.max(1, rows);\n}\n\n/**\n * Parse image dimensions using the native Rust image module.\n * Auto-detects format from byte content (PNG, JPEG, GIF, WebP).\n */\nexport async function getImageDimensions(base64Data: string): Promise<ImageDimensions | null> {\n\tconst { parseImage: parse } = await import(\"@gsd/native/image\");\n\ttry {\n\t\tconst bytes = new Uint8Array(Buffer.from(base64Data, \"base64\"));\n\t\tconst handle = await parse(bytes);\n\t\treturn { widthPx: handle.width, heightPx: handle.height };\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function renderImage(\n\tbase64Data: string,\n\timageDimensions: ImageDimensions,\n\toptions: ImageRenderOptions = {},\n): { sequence: string; rows: number; imageId?: number } | null {\n\tconst caps = getCapabilities();\n\n\tif (!caps.images) {\n\t\treturn null;\n\t}\n\n\tconst maxWidth = options.maxWidthCells ?? 80;\n\tconst rows = calculateImageRows(imageDimensions, maxWidth, getCellDimensions());\n\n\tif (caps.images === \"kitty\") {\n\t\t// Only use imageId if explicitly provided - static images don't need IDs\n\t\tconst sequence = encodeKitty(base64Data, { columns: maxWidth, rows, imageId: options.imageId });\n\t\treturn { sequence, rows, imageId: options.imageId };\n\t}\n\n\tif (caps.images === \"iterm2\") {\n\t\tconst sequence = encodeITerm2(base64Data, {\n\t\t\twidth: maxWidth,\n\t\t\theight: \"auto\",\n\t\t\tpreserveAspectRatio: options.preserveAspectRatio ?? true,\n\t\t});\n\t\treturn { sequence, rows };\n\t}\n\n\treturn null;\n}\n\nexport function imageFallback(mimeType: string, dimensions?: ImageDimensions, filename?: string): string {\n\tconst parts: string[] = [];\n\tif (filename) parts.push(filename);\n\tparts.push(`[${mimeType}]`);\n\tif (dimensions) parts.push(`${dimensions.widthPx}x${dimensions.heightPx}`);\n\treturn `[Image: ${parts.join(\" \")}]`;\n}\n"]}
|
|
@@ -41,16 +41,11 @@ export function detectCapabilities(): TerminalCapabilities {
|
|
|
41
41
|
const termProgram = process.env.TERM_PROGRAM?.toLowerCase() || "";
|
|
42
42
|
const term = process.env.TERM?.toLowerCase() || "";
|
|
43
43
|
const colorTerm = process.env.COLORTERM?.toLowerCase() || "";
|
|
44
|
-
const isCmux = Boolean(process.env.CMUX_WORKSPACE_ID && process.env.CMUX_SURFACE_ID);
|
|
45
44
|
|
|
46
45
|
if (process.env.KITTY_WINDOW_ID || termProgram === "kitty") {
|
|
47
46
|
return { images: "kitty", trueColor: true, hyperlinks: true };
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
if (isCmux) {
|
|
51
|
-
return { images: "kitty", trueColor: true, hyperlinks: true };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
49
|
if (termProgram === "ghostty" || term.includes("ghostty") || process.env.GHOSTTY_RESOURCES_DIR) {
|
|
55
50
|
return { images: "kitty", trueColor: true, hyperlinks: true };
|
|
56
51
|
}
|