@ziamana/bruine 0.1.3 → 0.1.5
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 +1 -1
- package/dist/bin.js +18 -6
- package/dist/plugins/render.js +124 -1
- package/dist/plugins/repl.js +152 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](LICENSE)
|
|
13
13
|
[](https://nodejs.org)
|
|
14
14
|
 
|
|
15
|
-
[](CHANGELOG.md)
|
|
16
16
|
|
|
17
17
|
<a href="docs/media/bruine-film.mp4"><img src="docs/media/hero.webp" alt="The bruine film: the word bruine becomes the logo in the rain, then the effort climbs from low to max and the rain turns into a storm" width="100%"></a>
|
|
18
18
|
|
package/dist/bin.js
CHANGED
|
@@ -1559,7 +1559,7 @@ var init_theme = __esm({
|
|
|
1559
1559
|
|
|
1560
1560
|
// src/update.ts
|
|
1561
1561
|
import { spawn } from "child_process";
|
|
1562
|
-
import { realpathSync } from "fs";
|
|
1562
|
+
import { realpathSync as realpathSync2 } from "fs";
|
|
1563
1563
|
import { mkdir as mkdir4, readFile as readFile5, rename as rename4, writeFile as writeFile4 } from "fs/promises";
|
|
1564
1564
|
import path5 from "path";
|
|
1565
1565
|
function updateCachePath(dshHome, pathMod = path5) {
|
|
@@ -4783,7 +4783,7 @@ var init_approve = __esm({
|
|
|
4783
4783
|
// src/bin.ts
|
|
4784
4784
|
init_compat();
|
|
4785
4785
|
import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
|
|
4786
|
-
import { existsSync as existsSync8, readFileSync as readFileSync7, realpathSync as
|
|
4786
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, realpathSync as realpathSync3 } from "fs";
|
|
4787
4787
|
import { createInterface } from "readline";
|
|
4788
4788
|
import os from "os";
|
|
4789
4789
|
import { dirname as dirname5, join as join8, resolve as resolve2 } from "path";
|
|
@@ -4855,7 +4855,7 @@ function flagMode(argv) {
|
|
|
4855
4855
|
// src/profile.ts
|
|
4856
4856
|
import { createRequire as createRequire2 } from "module";
|
|
4857
4857
|
import { mkdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
|
|
4858
|
-
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, rmSync, symlinkSync } from "fs";
|
|
4858
|
+
import { existsSync as existsSync3, lstatSync, mkdirSync, readFileSync as readFileSync2, realpathSync, rmSync, symlinkSync } from "fs";
|
|
4859
4859
|
import path3 from "path";
|
|
4860
4860
|
import { parse as parseYaml } from "yaml";
|
|
4861
4861
|
var require3 = createRequire2(import.meta.url);
|
|
@@ -5003,6 +5003,19 @@ function linkProfileBundles(profileDir, own, dshBaseDir) {
|
|
|
5003
5003
|
return false;
|
|
5004
5004
|
}
|
|
5005
5005
|
}
|
|
5006
|
+
function bundleIsCurrent(profileDir, name, own) {
|
|
5007
|
+
const at = path3.join(profileDir, "node_modules", name);
|
|
5008
|
+
const marker = path3.join(at, "package.json");
|
|
5009
|
+
if (!existsSync3(marker)) return false;
|
|
5010
|
+
if (own.root === void 0) return true;
|
|
5011
|
+
try {
|
|
5012
|
+
if (realpathSync(at) === realpathSync(own.root)) return true;
|
|
5013
|
+
if (lstatSync(at).isSymbolicLink()) return false;
|
|
5014
|
+
return JSON.parse(readFileSync2(marker, "utf8")).version === own.version;
|
|
5015
|
+
} catch {
|
|
5016
|
+
return false;
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5006
5019
|
function resolveDshBaseDir() {
|
|
5007
5020
|
try {
|
|
5008
5021
|
const dshRequire = createRequire2(require3.resolve("@deepseek-ai/dsh/package.json"));
|
|
@@ -5136,9 +5149,8 @@ function selfPackageRoot() {
|
|
|
5136
5149
|
}
|
|
5137
5150
|
}
|
|
5138
5151
|
function ensureBundleInstalled(profileDir, dshEntry, env, headless = false) {
|
|
5139
|
-
const marker = join8(profileDir, "node_modules", pkg2.name, "package.json");
|
|
5140
|
-
if (existsSync8(marker)) return;
|
|
5141
5152
|
const root = selfPackageRoot();
|
|
5153
|
+
if (bundleIsCurrent(profileDir, pkg2.name, { root, version: pkg2.version })) return;
|
|
5142
5154
|
if (!headless) console.log("Setting up bruine (one time)\u2026");
|
|
5143
5155
|
const dshBase = resolveDshBaseDir();
|
|
5144
5156
|
if (root !== void 0 && dshBase !== void 0 && linkProfileBundles(profileDir, { name: pkg2.name, root }, dshBase)) {
|
|
@@ -5189,7 +5201,7 @@ async function runUpdate() {
|
|
|
5189
5201
|
const entry = process.argv[1] ?? fileURLToPath2(import.meta.url);
|
|
5190
5202
|
let real = entry;
|
|
5191
5203
|
try {
|
|
5192
|
-
real =
|
|
5204
|
+
real = realpathSync3(entry);
|
|
5193
5205
|
} catch {
|
|
5194
5206
|
}
|
|
5195
5207
|
const cmd = updateCommand(detectInstallKind(real));
|
package/dist/plugins/render.js
CHANGED
|
@@ -826,6 +826,7 @@ var ReasoningComponent = class {
|
|
|
826
826
|
};
|
|
827
827
|
|
|
828
828
|
// src/ui/tool-call-component.ts
|
|
829
|
+
import { Image } from "@earendil-works/pi-tui";
|
|
829
830
|
import stringWidth4 from "string-width";
|
|
830
831
|
import { isAbsolute as isAbsolute2, relative, resolve as resolve2 } from "path";
|
|
831
832
|
|
|
@@ -1092,7 +1093,7 @@ function readableToolSummary(tool, args) {
|
|
|
1092
1093
|
// src/ui/image-preview.ts
|
|
1093
1094
|
import { readFileSync as readFileSync3, statSync } from "fs";
|
|
1094
1095
|
import { createRequire } from "module";
|
|
1095
|
-
import { inflateSync } from "zlib";
|
|
1096
|
+
import { crc32, deflateSync, inflateSync } from "zlib";
|
|
1096
1097
|
var MAX_PREVIEW_BYTES = 16 * 1024 * 1024;
|
|
1097
1098
|
var MAX_PREVIEW_PIXELS = 4e7;
|
|
1098
1099
|
var PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
@@ -1308,12 +1309,85 @@ function previewImageFile(path7, cols, rows) {
|
|
|
1308
1309
|
return void 0;
|
|
1309
1310
|
}
|
|
1310
1311
|
}
|
|
1312
|
+
function encodePng(img) {
|
|
1313
|
+
const chunk = (type, data) => {
|
|
1314
|
+
const out = Buffer.alloc(12 + data.length);
|
|
1315
|
+
out.writeUInt32BE(data.length, 0);
|
|
1316
|
+
out.write(type, 4, "ascii");
|
|
1317
|
+
Buffer.from(data).copy(out, 8);
|
|
1318
|
+
out.writeUInt32BE(crc32(Buffer.concat([Buffer.from(type, "ascii"), Buffer.from(data)])), 8 + data.length);
|
|
1319
|
+
return out;
|
|
1320
|
+
};
|
|
1321
|
+
const header = Buffer.alloc(13);
|
|
1322
|
+
header.writeUInt32BE(img.width, 0);
|
|
1323
|
+
header.writeUInt32BE(img.height, 4);
|
|
1324
|
+
header[8] = 8;
|
|
1325
|
+
header[9] = 6;
|
|
1326
|
+
const stride = img.width * 4;
|
|
1327
|
+
const raw = Buffer.alloc((stride + 1) * img.height);
|
|
1328
|
+
for (let y = 0; y < img.height; y += 1) Buffer.from(img.data.buffer, img.data.byteOffset + y * stride, stride).copy(raw, y * (stride + 1) + 1);
|
|
1329
|
+
return new Uint8Array(Buffer.concat([Buffer.from(PNG_SIGNATURE), chunk("IHDR", header), chunk("IDAT", deflateSync(raw)), chunk("IEND", new Uint8Array())]));
|
|
1330
|
+
}
|
|
1331
|
+
function shrink(img, max) {
|
|
1332
|
+
const scale = Math.min(1, max / Math.max(img.width, img.height));
|
|
1333
|
+
if (scale >= 1) return img;
|
|
1334
|
+
const width = Math.max(1, Math.round(img.width * scale));
|
|
1335
|
+
const height = Math.max(1, Math.round(img.height * scale));
|
|
1336
|
+
const data = new Uint8Array(width * height * 4);
|
|
1337
|
+
const sx = img.width / width;
|
|
1338
|
+
const sy = img.height / height;
|
|
1339
|
+
for (let y = 0; y < height; y += 1) {
|
|
1340
|
+
for (let x = 0; x < width; x += 1) {
|
|
1341
|
+
const sum = [0, 0, 0, 0];
|
|
1342
|
+
let n = 0;
|
|
1343
|
+
for (let yy = Math.floor(y * sy); yy < Math.min(img.height, Math.ceil((y + 1) * sy)); yy += 1) {
|
|
1344
|
+
for (let xx = Math.floor(x * sx); xx < Math.min(img.width, Math.ceil((x + 1) * sx)); xx += 1) {
|
|
1345
|
+
const o = (yy * img.width + xx) * 4;
|
|
1346
|
+
for (let c2 = 0; c2 < 4; c2 += 1) sum[c2] += img.data[o + c2];
|
|
1347
|
+
n += 1;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
for (let c2 = 0; c2 < 4; c2 += 1) data[(y * width + x) * 4 + c2] = Math.round(sum[c2] / Math.max(1, n));
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
return { width, height, data };
|
|
1354
|
+
}
|
|
1355
|
+
var SEND_MAX_BYTES = 2 * 1024 * 1024;
|
|
1356
|
+
var SEND_MAX_SIDE = 1600;
|
|
1357
|
+
function terminalImageFile(path7, protocol) {
|
|
1358
|
+
try {
|
|
1359
|
+
if (statSync(path7).size > MAX_PREVIEW_BYTES) return void 0;
|
|
1360
|
+
const bytes = new Uint8Array(readFileSync3(path7));
|
|
1361
|
+
const mimeType = sniffImage(bytes);
|
|
1362
|
+
const size = imageSize(bytes);
|
|
1363
|
+
if (mimeType === void 0 || size === void 0) return void 0;
|
|
1364
|
+
const description = describeImage(bytes);
|
|
1365
|
+
const tooBig = bytes.length > SEND_MAX_BYTES || Math.max(size.width, size.height) > SEND_MAX_SIDE;
|
|
1366
|
+
if (!tooBig && (protocol === "iterm2" || mimeType === "image/png")) {
|
|
1367
|
+
return { base64: Buffer.from(bytes).toString("base64"), mimeType, widthPx: size.width, heightPx: size.height, description };
|
|
1368
|
+
}
|
|
1369
|
+
const img = decodeImage(bytes);
|
|
1370
|
+
if (img === void 0) return void 0;
|
|
1371
|
+
const sent = shrink(img, SEND_MAX_SIDE);
|
|
1372
|
+
return { base64: Buffer.from(encodePng(sent)).toString("base64"), mimeType: "image/png", widthPx: sent.width, heightPx: sent.height, description };
|
|
1373
|
+
} catch {
|
|
1374
|
+
return void 0;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// src/ui/term-images.ts
|
|
1379
|
+
import { getCapabilities, setCapabilityOverrides } from "@earendil-works/pi-tui";
|
|
1380
|
+
function imageProtocol() {
|
|
1381
|
+
return getCapabilities().images;
|
|
1382
|
+
}
|
|
1311
1383
|
|
|
1312
1384
|
// src/ui/tool-call-component.ts
|
|
1313
1385
|
var IMAGE_TOOLS = /* @__PURE__ */ new Set(["read_image", "view_image"]);
|
|
1314
1386
|
var IMAGE_FILE = /\.(png|jpe?g|gif|webp)$/i;
|
|
1315
1387
|
var IMAGE_PREVIEW_COLS = 64;
|
|
1316
1388
|
var IMAGE_PREVIEW_ROWS = 16;
|
|
1389
|
+
var IMAGE_PICTURE_COLS = 72;
|
|
1390
|
+
var IMAGE_PICTURE_ROWS = 20;
|
|
1317
1391
|
var SUMMARY_KEYS2 = ["command", "cmd", "path", "file_path", "url", "query", "pattern", "name"];
|
|
1318
1392
|
var WRITE_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "multi_edit", "notebook_edit"]);
|
|
1319
1393
|
var MAX_OUTPUT_LINES2 = 5;
|
|
@@ -1355,6 +1429,8 @@ var ToolCallComponent = class {
|
|
|
1355
1429
|
#diff;
|
|
1356
1430
|
/** The image file a successful image read showed the model. */
|
|
1357
1431
|
#image;
|
|
1432
|
+
/** That image, prepared once for the terminal's graphics protocol (null: it could not be). */
|
|
1433
|
+
#picture;
|
|
1358
1434
|
#readableSummaries;
|
|
1359
1435
|
get active() {
|
|
1360
1436
|
return this.#done === void 0;
|
|
@@ -1548,6 +1624,12 @@ var ToolCallComponent = class {
|
|
|
1548
1624
|
return out;
|
|
1549
1625
|
}
|
|
1550
1626
|
if (this.#image !== void 0) {
|
|
1627
|
+
const picture = this.#pictureLines(width);
|
|
1628
|
+
if (picture !== void 0) {
|
|
1629
|
+
out.push(...picture.lines.map((l) => ` ${l}`));
|
|
1630
|
+
out.push(ansi.gray(clipCells(` ${picture.description}${dur === void 0 ? "" : ` ${this.icons.think === "*" ? "-" : "\xB7"} ${dur}`}`, width)));
|
|
1631
|
+
return out;
|
|
1632
|
+
}
|
|
1551
1633
|
const preview = previewImageFile(this.#image, Math.min(IMAGE_PREVIEW_COLS, Math.max(1, width - 6)), IMAGE_PREVIEW_ROWS);
|
|
1552
1634
|
if (preview !== void 0) {
|
|
1553
1635
|
if (preview.lines !== void 0) out.push(...preview.lines.map((l) => ` ${l}`));
|
|
@@ -1561,6 +1643,26 @@ var ToolCallComponent = class {
|
|
|
1561
1643
|
if (dur !== void 0) out.push(ansi.gray(clipCells(`Took ${dur}`, width)));
|
|
1562
1644
|
return out;
|
|
1563
1645
|
}
|
|
1646
|
+
/**
|
|
1647
|
+
* The image itself, through the terminal's graphics protocol, when it has one: Kitty, Ghostty,
|
|
1648
|
+
* WezTerm, Warp, iTerm2, Konsole. pi-tui places it and keeps its rows; the half-block sketch
|
|
1649
|
+
* stays for every other terminal.
|
|
1650
|
+
*/
|
|
1651
|
+
#pictureLines(width) {
|
|
1652
|
+
const protocol = imageProtocol();
|
|
1653
|
+
if (protocol === null || this.#image === void 0) return void 0;
|
|
1654
|
+
const key = `${protocol}:${this.#image}`;
|
|
1655
|
+
if (this.#picture === void 0 || this.#picture !== null && this.#picture.key !== key) {
|
|
1656
|
+
const file = terminalImageFile(this.#image, protocol);
|
|
1657
|
+
this.#picture = file === void 0 ? null : {
|
|
1658
|
+
key,
|
|
1659
|
+
description: file.description,
|
|
1660
|
+
image: new Image(file.base64, file.mimeType, { fallbackColor: ansi.gray }, { maxWidthCells: IMAGE_PICTURE_COLS, maxHeightCells: IMAGE_PICTURE_ROWS }, { widthPx: file.widthPx, heightPx: file.heightPx })
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
if (this.#picture === null) return void 0;
|
|
1664
|
+
return { lines: this.#picture.image.render(Math.max(3, Math.min(IMAGE_PICTURE_COLS + 2, width - 4))), description: this.#picture.description };
|
|
1665
|
+
}
|
|
1564
1666
|
invalidate() {
|
|
1565
1667
|
}
|
|
1566
1668
|
};
|
|
@@ -3163,6 +3265,21 @@ function attachTui(ctx, agent, ui, service, getAgent) {
|
|
|
3163
3265
|
const reading = silence?.reading(id === void 0 ? void 0 : String(id));
|
|
3164
3266
|
return reading === void 0 ? void 0 : { quietMs: Date.now() - reading.quietSince, budgetMs: reading.budget };
|
|
3165
3267
|
});
|
|
3268
|
+
let backgroundTaskCount = 0;
|
|
3269
|
+
const jobs = ctx.get("jobs");
|
|
3270
|
+
const refreshBackgroundTasks = () => {
|
|
3271
|
+
let count = 0;
|
|
3272
|
+
try {
|
|
3273
|
+
count = (jobs?.list(liveAgent()) ?? []).filter((job) => job.kind !== "subagent" && (job.status === "running" || job.status === "stopping")).length;
|
|
3274
|
+
} catch {
|
|
3275
|
+
}
|
|
3276
|
+
if (count === backgroundTaskCount) return;
|
|
3277
|
+
backgroundTaskCount = count;
|
|
3278
|
+
ui.footer.set({ backgroundTasks: count });
|
|
3279
|
+
ui.requestRender();
|
|
3280
|
+
};
|
|
3281
|
+
const offJobsChanged = jobs?.onJobsChanged?.(refreshBackgroundTasks);
|
|
3282
|
+
refreshBackgroundTasks();
|
|
3166
3283
|
let subagentCount = 0;
|
|
3167
3284
|
const refreshSubagents = () => {
|
|
3168
3285
|
const registry = ctx.get("agents");
|
|
@@ -3322,6 +3439,7 @@ function attachTui(ctx, agent, ui, service, getAgent) {
|
|
|
3322
3439
|
}
|
|
3323
3440
|
case "turn/start":
|
|
3324
3441
|
refreshSubagents();
|
|
3442
|
+
refreshBackgroundTasks();
|
|
3325
3443
|
tps.reset();
|
|
3326
3444
|
ui.footer.set({ tps: 0, tpsEstimated: false, pp: void 0, cachePct: void 0, cacheFirst: false });
|
|
3327
3445
|
turnStartWall = Date.now();
|
|
@@ -3543,6 +3661,11 @@ function attachTui(ctx, agent, ui, service, getAgent) {
|
|
|
3543
3661
|
offAgentCreated();
|
|
3544
3662
|
offAgentStatus();
|
|
3545
3663
|
offAgentDisposed();
|
|
3664
|
+
offJobsChanged?.();
|
|
3665
|
+
if (backgroundTaskCount > 0) {
|
|
3666
|
+
ui.footer.set({ backgroundTasks: 0 });
|
|
3667
|
+
ui.requestRender();
|
|
3668
|
+
}
|
|
3546
3669
|
if (subagentCount > 0) {
|
|
3547
3670
|
ui.footer.set({ subagents: 0 });
|
|
3548
3671
|
ui.requestRender();
|
package/dist/plugins/repl.js
CHANGED
|
@@ -942,6 +942,45 @@ import { readFileSync } from "fs";
|
|
|
942
942
|
import { randomUUID } from "crypto";
|
|
943
943
|
import { visibleWidth as visibleWidth2 } from "@earendil-works/pi-tui";
|
|
944
944
|
|
|
945
|
+
// src/ui/term-images.ts
|
|
946
|
+
import { getCapabilities, setCapabilityOverrides } from "@earendil-works/pi-tui";
|
|
947
|
+
function imageProtocolFor(env = process.env) {
|
|
948
|
+
const asked = env.BRUINE_IMAGES?.trim().toLowerCase();
|
|
949
|
+
if (asked === "kitty" || asked === "iterm2") return asked;
|
|
950
|
+
if (asked === "blocks" || asked === "off" || asked === "none" || asked === "0") return null;
|
|
951
|
+
if (env.TMUX !== void 0 || (env.TERM ?? "").startsWith("tmux") || (env.TERM ?? "").startsWith("screen")) return void 0;
|
|
952
|
+
const konsole = Number.parseInt(env.KONSOLE_VERSION ?? "", 10);
|
|
953
|
+
if (Number.isFinite(konsole) && konsole >= 220400) return "iterm2";
|
|
954
|
+
return void 0;
|
|
955
|
+
}
|
|
956
|
+
function applyImageProtocol(env = process.env) {
|
|
957
|
+
const protocol = imageProtocolFor(env);
|
|
958
|
+
if (protocol !== void 0) setCapabilityOverrides({ images: protocol });
|
|
959
|
+
}
|
|
960
|
+
function imageProtocol() {
|
|
961
|
+
return getCapabilities().images;
|
|
962
|
+
}
|
|
963
|
+
function imageRows(lines) {
|
|
964
|
+
const rows = /* @__PURE__ */ new Set();
|
|
965
|
+
lines.forEach((line, at) => {
|
|
966
|
+
if (line.includes("\x1B_G")) {
|
|
967
|
+
const tall = Number.parseInt(/[;,]r=(\d+)/.exec(line)?.[1] ?? "1", 10);
|
|
968
|
+
for (let i = 0; i < Math.max(1, tall); i += 1) rows.add(at + i);
|
|
969
|
+
} else if (line.includes("\x1B]1337;File=")) {
|
|
970
|
+
const up = Number.parseInt(/\x1b\[(\d+)A\x1b\]1337;File=/.exec(line)?.[1] ?? "0", 10);
|
|
971
|
+
for (let i = 0; i <= up; i += 1) rows.add(at - i);
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
return rows;
|
|
975
|
+
}
|
|
976
|
+
function splitImageLine(line) {
|
|
977
|
+
const kitty = line.indexOf("\x1B_G");
|
|
978
|
+
const iterm = /(?:\x1b\[\d+A)?\x1b\]1337;File=/.exec(line);
|
|
979
|
+
const at = kitty >= 0 ? kitty : iterm?.index ?? -1;
|
|
980
|
+
if (at < 0) return void 0;
|
|
981
|
+
return { before: line.slice(0, at), sequence: line.slice(at) };
|
|
982
|
+
}
|
|
983
|
+
|
|
945
984
|
// src/ui/logo-motion.ts
|
|
946
985
|
var LOGO = [
|
|
947
986
|
"\u250F\u2513 \u250F\u2501\u2513\u257B \u257B\u257B\u250F\u2513\u257B\u250F\u2501\u2578",
|
|
@@ -1070,9 +1109,11 @@ var WeatherBackdrop = class {
|
|
|
1070
1109
|
}
|
|
1071
1110
|
const ink = storm2 || this.#weather.level >= 0.85 ? STORM_INK : QUIET_INK;
|
|
1072
1111
|
const out = [...lines];
|
|
1112
|
+
const pictures = imageRows(lines);
|
|
1073
1113
|
for (let y = 0; y < height; y += 1) {
|
|
1074
1114
|
const row = from + y;
|
|
1075
1115
|
if (row >= controlsFrom && row < content.length) continue;
|
|
1116
|
+
if (pictures.has(row)) continue;
|
|
1076
1117
|
const line = lines[from + y];
|
|
1077
1118
|
if (hasBackground(line)) continue;
|
|
1078
1119
|
const padded = line + " ".repeat(Math.max(0, width - visibleWidth2(line)));
|
|
@@ -1329,6 +1370,14 @@ var ChatTranscript = class extends Container {
|
|
|
1329
1370
|
}
|
|
1330
1371
|
for (const [i, line] of block.entries()) {
|
|
1331
1372
|
const clean = child.transcriptStyle === "reasoning" ? reasoningStyle(withoutEmoji(line)) : withoutEmoji(line);
|
|
1373
|
+
const picture = splitImageLine(clean);
|
|
1374
|
+
if (picture !== void 0) {
|
|
1375
|
+
const lead = rail === void 0 ? ` ${picture.before}` : `${showRail ? railPaint(rail, railChar, (i + 1) / railSpan) : " "} ${picture.before}`;
|
|
1376
|
+
const row = rail === void 0 ? lead : card(role, lead);
|
|
1377
|
+
const back = rail === void 0 ? 0 : cardCells - visibleWidth3(lead);
|
|
1378
|
+
piece2.push(`${row}${back > 0 ? `\x1B[${String(back)}D` : ""}${picture.sequence}`);
|
|
1379
|
+
continue;
|
|
1380
|
+
}
|
|
1332
1381
|
if (rail === void 0) {
|
|
1333
1382
|
piece2.push(truncateToWidth(` ${clean}`, width - 2));
|
|
1334
1383
|
} else {
|
|
@@ -1585,7 +1634,11 @@ var FooterComponent = class {
|
|
|
1585
1634
|
const turn = this.#turnRow(w);
|
|
1586
1635
|
const rows = [place, turn.line];
|
|
1587
1636
|
const count = this.state.subagents ?? 0;
|
|
1588
|
-
const
|
|
1637
|
+
const tasks = this.state.backgroundTasks ?? 0;
|
|
1638
|
+
const agents = [
|
|
1639
|
+
tasks > 0 ? this.#ink("sky")(`task ${Math.floor(tasks)}`) : "",
|
|
1640
|
+
count > 0 ? this.#ink("sky")(`subagents ${Math.floor(count)}`) : ""
|
|
1641
|
+
].filter(Boolean).join(" ");
|
|
1589
1642
|
const firstRoom = agents ? Math.max(1, w - visibleWidth4(agents) - GAP) : w;
|
|
1590
1643
|
const speeds = this.#speedRows(w, firstRoom);
|
|
1591
1644
|
if (agents) {
|
|
@@ -4734,6 +4787,7 @@ function fileLink(text, absolutePath) {
|
|
|
4734
4787
|
}
|
|
4735
4788
|
|
|
4736
4789
|
// src/ui/tool-call-component.ts
|
|
4790
|
+
import { Image } from "@earendil-works/pi-tui";
|
|
4737
4791
|
import stringWidth7 from "string-width";
|
|
4738
4792
|
import { isAbsolute as isAbsolute4, relative as relative2, resolve as resolve2 } from "path";
|
|
4739
4793
|
|
|
@@ -4969,7 +5023,7 @@ function readableToolSummary(tool, args) {
|
|
|
4969
5023
|
// src/ui/image-preview.ts
|
|
4970
5024
|
import { readFileSync as readFileSync7, statSync as statSync2 } from "fs";
|
|
4971
5025
|
import { createRequire } from "module";
|
|
4972
|
-
import { inflateSync } from "zlib";
|
|
5026
|
+
import { crc32, deflateSync, inflateSync } from "zlib";
|
|
4973
5027
|
var MAX_PREVIEW_BYTES = 16 * 1024 * 1024;
|
|
4974
5028
|
var MAX_PREVIEW_PIXELS = 4e7;
|
|
4975
5029
|
var PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
@@ -5185,12 +5239,79 @@ function previewImageFile(path8, cols, rows) {
|
|
|
5185
5239
|
return void 0;
|
|
5186
5240
|
}
|
|
5187
5241
|
}
|
|
5242
|
+
function encodePng(img) {
|
|
5243
|
+
const chunk = (type, data) => {
|
|
5244
|
+
const out = Buffer.alloc(12 + data.length);
|
|
5245
|
+
out.writeUInt32BE(data.length, 0);
|
|
5246
|
+
out.write(type, 4, "ascii");
|
|
5247
|
+
Buffer.from(data).copy(out, 8);
|
|
5248
|
+
out.writeUInt32BE(crc32(Buffer.concat([Buffer.from(type, "ascii"), Buffer.from(data)])), 8 + data.length);
|
|
5249
|
+
return out;
|
|
5250
|
+
};
|
|
5251
|
+
const header = Buffer.alloc(13);
|
|
5252
|
+
header.writeUInt32BE(img.width, 0);
|
|
5253
|
+
header.writeUInt32BE(img.height, 4);
|
|
5254
|
+
header[8] = 8;
|
|
5255
|
+
header[9] = 6;
|
|
5256
|
+
const stride = img.width * 4;
|
|
5257
|
+
const raw = Buffer.alloc((stride + 1) * img.height);
|
|
5258
|
+
for (let y = 0; y < img.height; y += 1) Buffer.from(img.data.buffer, img.data.byteOffset + y * stride, stride).copy(raw, y * (stride + 1) + 1);
|
|
5259
|
+
return new Uint8Array(Buffer.concat([Buffer.from(PNG_SIGNATURE), chunk("IHDR", header), chunk("IDAT", deflateSync(raw)), chunk("IEND", new Uint8Array())]));
|
|
5260
|
+
}
|
|
5261
|
+
function shrink(img, max) {
|
|
5262
|
+
const scale = Math.min(1, max / Math.max(img.width, img.height));
|
|
5263
|
+
if (scale >= 1) return img;
|
|
5264
|
+
const width = Math.max(1, Math.round(img.width * scale));
|
|
5265
|
+
const height = Math.max(1, Math.round(img.height * scale));
|
|
5266
|
+
const data = new Uint8Array(width * height * 4);
|
|
5267
|
+
const sx = img.width / width;
|
|
5268
|
+
const sy = img.height / height;
|
|
5269
|
+
for (let y = 0; y < height; y += 1) {
|
|
5270
|
+
for (let x = 0; x < width; x += 1) {
|
|
5271
|
+
const sum = [0, 0, 0, 0];
|
|
5272
|
+
let n = 0;
|
|
5273
|
+
for (let yy = Math.floor(y * sy); yy < Math.min(img.height, Math.ceil((y + 1) * sy)); yy += 1) {
|
|
5274
|
+
for (let xx = Math.floor(x * sx); xx < Math.min(img.width, Math.ceil((x + 1) * sx)); xx += 1) {
|
|
5275
|
+
const o = (yy * img.width + xx) * 4;
|
|
5276
|
+
for (let c2 = 0; c2 < 4; c2 += 1) sum[c2] += img.data[o + c2];
|
|
5277
|
+
n += 1;
|
|
5278
|
+
}
|
|
5279
|
+
}
|
|
5280
|
+
for (let c2 = 0; c2 < 4; c2 += 1) data[(y * width + x) * 4 + c2] = Math.round(sum[c2] / Math.max(1, n));
|
|
5281
|
+
}
|
|
5282
|
+
}
|
|
5283
|
+
return { width, height, data };
|
|
5284
|
+
}
|
|
5285
|
+
var SEND_MAX_BYTES = 2 * 1024 * 1024;
|
|
5286
|
+
var SEND_MAX_SIDE = 1600;
|
|
5287
|
+
function terminalImageFile(path8, protocol) {
|
|
5288
|
+
try {
|
|
5289
|
+
if (statSync2(path8).size > MAX_PREVIEW_BYTES) return void 0;
|
|
5290
|
+
const bytes = new Uint8Array(readFileSync7(path8));
|
|
5291
|
+
const mimeType = sniffImage(bytes);
|
|
5292
|
+
const size = imageSize(bytes);
|
|
5293
|
+
if (mimeType === void 0 || size === void 0) return void 0;
|
|
5294
|
+
const description = describeImage(bytes);
|
|
5295
|
+
const tooBig = bytes.length > SEND_MAX_BYTES || Math.max(size.width, size.height) > SEND_MAX_SIDE;
|
|
5296
|
+
if (!tooBig && (protocol === "iterm2" || mimeType === "image/png")) {
|
|
5297
|
+
return { base64: Buffer.from(bytes).toString("base64"), mimeType, widthPx: size.width, heightPx: size.height, description };
|
|
5298
|
+
}
|
|
5299
|
+
const img = decodeImage(bytes);
|
|
5300
|
+
if (img === void 0) return void 0;
|
|
5301
|
+
const sent = shrink(img, SEND_MAX_SIDE);
|
|
5302
|
+
return { base64: Buffer.from(encodePng(sent)).toString("base64"), mimeType: "image/png", widthPx: sent.width, heightPx: sent.height, description };
|
|
5303
|
+
} catch {
|
|
5304
|
+
return void 0;
|
|
5305
|
+
}
|
|
5306
|
+
}
|
|
5188
5307
|
|
|
5189
5308
|
// src/ui/tool-call-component.ts
|
|
5190
5309
|
var IMAGE_TOOLS = /* @__PURE__ */ new Set(["read_image", "view_image"]);
|
|
5191
5310
|
var IMAGE_FILE = /\.(png|jpe?g|gif|webp)$/i;
|
|
5192
5311
|
var IMAGE_PREVIEW_COLS = 64;
|
|
5193
5312
|
var IMAGE_PREVIEW_ROWS = 16;
|
|
5313
|
+
var IMAGE_PICTURE_COLS = 72;
|
|
5314
|
+
var IMAGE_PICTURE_ROWS = 20;
|
|
5194
5315
|
var SUMMARY_KEYS = ["command", "cmd", "path", "file_path", "url", "query", "pattern", "name"];
|
|
5195
5316
|
var WRITE_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "multi_edit", "notebook_edit"]);
|
|
5196
5317
|
var MAX_OUTPUT_LINES = 5;
|
|
@@ -5232,6 +5353,8 @@ var ToolCallComponent = class {
|
|
|
5232
5353
|
#diff;
|
|
5233
5354
|
/** The image file a successful image read showed the model. */
|
|
5234
5355
|
#image;
|
|
5356
|
+
/** That image, prepared once for the terminal's graphics protocol (null: it could not be). */
|
|
5357
|
+
#picture;
|
|
5235
5358
|
#readableSummaries;
|
|
5236
5359
|
get active() {
|
|
5237
5360
|
return this.#done === void 0;
|
|
@@ -5425,6 +5548,12 @@ var ToolCallComponent = class {
|
|
|
5425
5548
|
return out;
|
|
5426
5549
|
}
|
|
5427
5550
|
if (this.#image !== void 0) {
|
|
5551
|
+
const picture = this.#pictureLines(width);
|
|
5552
|
+
if (picture !== void 0) {
|
|
5553
|
+
out.push(...picture.lines.map((l) => ` ${l}`));
|
|
5554
|
+
out.push(ansi.gray(clipCells(` ${picture.description}${dur === void 0 ? "" : ` ${this.icons.think === "*" ? "-" : "\xB7"} ${dur}`}`, width)));
|
|
5555
|
+
return out;
|
|
5556
|
+
}
|
|
5428
5557
|
const preview = previewImageFile(this.#image, Math.min(IMAGE_PREVIEW_COLS, Math.max(1, width - 6)), IMAGE_PREVIEW_ROWS);
|
|
5429
5558
|
if (preview !== void 0) {
|
|
5430
5559
|
if (preview.lines !== void 0) out.push(...preview.lines.map((l) => ` ${l}`));
|
|
@@ -5438,6 +5567,26 @@ var ToolCallComponent = class {
|
|
|
5438
5567
|
if (dur !== void 0) out.push(ansi.gray(clipCells(`Took ${dur}`, width)));
|
|
5439
5568
|
return out;
|
|
5440
5569
|
}
|
|
5570
|
+
/**
|
|
5571
|
+
* The image itself, through the terminal's graphics protocol, when it has one: Kitty, Ghostty,
|
|
5572
|
+
* WezTerm, Warp, iTerm2, Konsole. pi-tui places it and keeps its rows; the half-block sketch
|
|
5573
|
+
* stays for every other terminal.
|
|
5574
|
+
*/
|
|
5575
|
+
#pictureLines(width) {
|
|
5576
|
+
const protocol = imageProtocol();
|
|
5577
|
+
if (protocol === null || this.#image === void 0) return void 0;
|
|
5578
|
+
const key = `${protocol}:${this.#image}`;
|
|
5579
|
+
if (this.#picture === void 0 || this.#picture !== null && this.#picture.key !== key) {
|
|
5580
|
+
const file = terminalImageFile(this.#image, protocol);
|
|
5581
|
+
this.#picture = file === void 0 ? null : {
|
|
5582
|
+
key,
|
|
5583
|
+
description: file.description,
|
|
5584
|
+
image: new Image(file.base64, file.mimeType, { fallbackColor: ansi.gray }, { maxWidthCells: IMAGE_PICTURE_COLS, maxHeightCells: IMAGE_PICTURE_ROWS }, { widthPx: file.widthPx, heightPx: file.heightPx })
|
|
5585
|
+
};
|
|
5586
|
+
}
|
|
5587
|
+
if (this.#picture === null) return void 0;
|
|
5588
|
+
return { lines: this.#picture.image.render(Math.max(3, Math.min(IMAGE_PICTURE_COLS + 2, width - 4))), description: this.#picture.description };
|
|
5589
|
+
}
|
|
5441
5590
|
invalidate() {
|
|
5442
5591
|
}
|
|
5443
5592
|
};
|
|
@@ -6249,6 +6398,7 @@ var BruineUi = class {
|
|
|
6249
6398
|
if (process.stdout.isTTY === true && process.env.CI !== "1" && appEnv("NO_CLEAR") !== "1") {
|
|
6250
6399
|
this.terminal.clearScreen();
|
|
6251
6400
|
}
|
|
6401
|
+
applyImageProtocol();
|
|
6252
6402
|
this.tui.start();
|
|
6253
6403
|
this.mouse.start();
|
|
6254
6404
|
void this.probeBackdrop();
|