@ziamana/bruine 0.1.3 → 0.1.4

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 CHANGED
@@ -12,7 +12,7 @@
12
12
  [![License: MIT](https://img.shields.io/badge/license-MIT-b4a7ff)](LICENSE)
13
13
  [![Node 22+](https://img.shields.io/badge/node-22%2B-7dcfff)](https://nodejs.org)
14
14
  ![Linux tested · macOS and Windows in progress](https://img.shields.io/badge/Linux-tested-8fe3a3) ![macOS · Windows](https://img.shields.io/badge/macOS%20%C2%B7%20Windows-in%20progress-f5c97a)
15
- [![Changelog](https://img.shields.io/badge/version-0.1.3-ff9ed2)](CHANGELOG.md)
15
+ [![Changelog](https://img.shields.io/badge/version-0.1.4-ff9ed2)](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
 
@@ -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
  };
@@ -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 {
@@ -4734,6 +4783,7 @@ function fileLink(text, absolutePath) {
4734
4783
  }
4735
4784
 
4736
4785
  // src/ui/tool-call-component.ts
4786
+ import { Image } from "@earendil-works/pi-tui";
4737
4787
  import stringWidth7 from "string-width";
4738
4788
  import { isAbsolute as isAbsolute4, relative as relative2, resolve as resolve2 } from "path";
4739
4789
 
@@ -4969,7 +5019,7 @@ function readableToolSummary(tool, args) {
4969
5019
  // src/ui/image-preview.ts
4970
5020
  import { readFileSync as readFileSync7, statSync as statSync2 } from "fs";
4971
5021
  import { createRequire } from "module";
4972
- import { inflateSync } from "zlib";
5022
+ import { crc32, deflateSync, inflateSync } from "zlib";
4973
5023
  var MAX_PREVIEW_BYTES = 16 * 1024 * 1024;
4974
5024
  var MAX_PREVIEW_PIXELS = 4e7;
4975
5025
  var PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
@@ -5185,12 +5235,79 @@ function previewImageFile(path8, cols, rows) {
5185
5235
  return void 0;
5186
5236
  }
5187
5237
  }
5238
+ function encodePng(img) {
5239
+ const chunk = (type, data) => {
5240
+ const out = Buffer.alloc(12 + data.length);
5241
+ out.writeUInt32BE(data.length, 0);
5242
+ out.write(type, 4, "ascii");
5243
+ Buffer.from(data).copy(out, 8);
5244
+ out.writeUInt32BE(crc32(Buffer.concat([Buffer.from(type, "ascii"), Buffer.from(data)])), 8 + data.length);
5245
+ return out;
5246
+ };
5247
+ const header = Buffer.alloc(13);
5248
+ header.writeUInt32BE(img.width, 0);
5249
+ header.writeUInt32BE(img.height, 4);
5250
+ header[8] = 8;
5251
+ header[9] = 6;
5252
+ const stride = img.width * 4;
5253
+ const raw = Buffer.alloc((stride + 1) * img.height);
5254
+ 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);
5255
+ return new Uint8Array(Buffer.concat([Buffer.from(PNG_SIGNATURE), chunk("IHDR", header), chunk("IDAT", deflateSync(raw)), chunk("IEND", new Uint8Array())]));
5256
+ }
5257
+ function shrink(img, max) {
5258
+ const scale = Math.min(1, max / Math.max(img.width, img.height));
5259
+ if (scale >= 1) return img;
5260
+ const width = Math.max(1, Math.round(img.width * scale));
5261
+ const height = Math.max(1, Math.round(img.height * scale));
5262
+ const data = new Uint8Array(width * height * 4);
5263
+ const sx = img.width / width;
5264
+ const sy = img.height / height;
5265
+ for (let y = 0; y < height; y += 1) {
5266
+ for (let x = 0; x < width; x += 1) {
5267
+ const sum = [0, 0, 0, 0];
5268
+ let n = 0;
5269
+ for (let yy = Math.floor(y * sy); yy < Math.min(img.height, Math.ceil((y + 1) * sy)); yy += 1) {
5270
+ for (let xx = Math.floor(x * sx); xx < Math.min(img.width, Math.ceil((x + 1) * sx)); xx += 1) {
5271
+ const o = (yy * img.width + xx) * 4;
5272
+ for (let c2 = 0; c2 < 4; c2 += 1) sum[c2] += img.data[o + c2];
5273
+ n += 1;
5274
+ }
5275
+ }
5276
+ for (let c2 = 0; c2 < 4; c2 += 1) data[(y * width + x) * 4 + c2] = Math.round(sum[c2] / Math.max(1, n));
5277
+ }
5278
+ }
5279
+ return { width, height, data };
5280
+ }
5281
+ var SEND_MAX_BYTES = 2 * 1024 * 1024;
5282
+ var SEND_MAX_SIDE = 1600;
5283
+ function terminalImageFile(path8, protocol) {
5284
+ try {
5285
+ if (statSync2(path8).size > MAX_PREVIEW_BYTES) return void 0;
5286
+ const bytes = new Uint8Array(readFileSync7(path8));
5287
+ const mimeType = sniffImage(bytes);
5288
+ const size = imageSize(bytes);
5289
+ if (mimeType === void 0 || size === void 0) return void 0;
5290
+ const description = describeImage(bytes);
5291
+ const tooBig = bytes.length > SEND_MAX_BYTES || Math.max(size.width, size.height) > SEND_MAX_SIDE;
5292
+ if (!tooBig && (protocol === "iterm2" || mimeType === "image/png")) {
5293
+ return { base64: Buffer.from(bytes).toString("base64"), mimeType, widthPx: size.width, heightPx: size.height, description };
5294
+ }
5295
+ const img = decodeImage(bytes);
5296
+ if (img === void 0) return void 0;
5297
+ const sent = shrink(img, SEND_MAX_SIDE);
5298
+ return { base64: Buffer.from(encodePng(sent)).toString("base64"), mimeType: "image/png", widthPx: sent.width, heightPx: sent.height, description };
5299
+ } catch {
5300
+ return void 0;
5301
+ }
5302
+ }
5188
5303
 
5189
5304
  // src/ui/tool-call-component.ts
5190
5305
  var IMAGE_TOOLS = /* @__PURE__ */ new Set(["read_image", "view_image"]);
5191
5306
  var IMAGE_FILE = /\.(png|jpe?g|gif|webp)$/i;
5192
5307
  var IMAGE_PREVIEW_COLS = 64;
5193
5308
  var IMAGE_PREVIEW_ROWS = 16;
5309
+ var IMAGE_PICTURE_COLS = 72;
5310
+ var IMAGE_PICTURE_ROWS = 20;
5194
5311
  var SUMMARY_KEYS = ["command", "cmd", "path", "file_path", "url", "query", "pattern", "name"];
5195
5312
  var WRITE_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "multi_edit", "notebook_edit"]);
5196
5313
  var MAX_OUTPUT_LINES = 5;
@@ -5232,6 +5349,8 @@ var ToolCallComponent = class {
5232
5349
  #diff;
5233
5350
  /** The image file a successful image read showed the model. */
5234
5351
  #image;
5352
+ /** That image, prepared once for the terminal's graphics protocol (null: it could not be). */
5353
+ #picture;
5235
5354
  #readableSummaries;
5236
5355
  get active() {
5237
5356
  return this.#done === void 0;
@@ -5425,6 +5544,12 @@ var ToolCallComponent = class {
5425
5544
  return out;
5426
5545
  }
5427
5546
  if (this.#image !== void 0) {
5547
+ const picture = this.#pictureLines(width);
5548
+ if (picture !== void 0) {
5549
+ out.push(...picture.lines.map((l) => ` ${l}`));
5550
+ out.push(ansi.gray(clipCells(` ${picture.description}${dur === void 0 ? "" : ` ${this.icons.think === "*" ? "-" : "\xB7"} ${dur}`}`, width)));
5551
+ return out;
5552
+ }
5428
5553
  const preview = previewImageFile(this.#image, Math.min(IMAGE_PREVIEW_COLS, Math.max(1, width - 6)), IMAGE_PREVIEW_ROWS);
5429
5554
  if (preview !== void 0) {
5430
5555
  if (preview.lines !== void 0) out.push(...preview.lines.map((l) => ` ${l}`));
@@ -5438,6 +5563,26 @@ var ToolCallComponent = class {
5438
5563
  if (dur !== void 0) out.push(ansi.gray(clipCells(`Took ${dur}`, width)));
5439
5564
  return out;
5440
5565
  }
5566
+ /**
5567
+ * The image itself, through the terminal's graphics protocol, when it has one: Kitty, Ghostty,
5568
+ * WezTerm, Warp, iTerm2, Konsole. pi-tui places it and keeps its rows; the half-block sketch
5569
+ * stays for every other terminal.
5570
+ */
5571
+ #pictureLines(width) {
5572
+ const protocol = imageProtocol();
5573
+ if (protocol === null || this.#image === void 0) return void 0;
5574
+ const key = `${protocol}:${this.#image}`;
5575
+ if (this.#picture === void 0 || this.#picture !== null && this.#picture.key !== key) {
5576
+ const file = terminalImageFile(this.#image, protocol);
5577
+ this.#picture = file === void 0 ? null : {
5578
+ key,
5579
+ description: file.description,
5580
+ image: new Image(file.base64, file.mimeType, { fallbackColor: ansi.gray }, { maxWidthCells: IMAGE_PICTURE_COLS, maxHeightCells: IMAGE_PICTURE_ROWS }, { widthPx: file.widthPx, heightPx: file.heightPx })
5581
+ };
5582
+ }
5583
+ if (this.#picture === null) return void 0;
5584
+ return { lines: this.#picture.image.render(Math.max(3, Math.min(IMAGE_PICTURE_COLS + 2, width - 4))), description: this.#picture.description };
5585
+ }
5441
5586
  invalidate() {
5442
5587
  }
5443
5588
  };
@@ -6249,6 +6394,7 @@ var BruineUi = class {
6249
6394
  if (process.stdout.isTTY === true && process.env.CI !== "1" && appEnv("NO_CLEAR") !== "1") {
6250
6395
  this.terminal.clearScreen();
6251
6396
  }
6397
+ applyImageProtocol();
6252
6398
  this.tui.start();
6253
6399
  this.mouse.start();
6254
6400
  void this.probeBackdrop();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziamana/bruine",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Interactive terminal coding agent that runs your own models, local or OpenAI-compatible",
5
5
  "type": "module",
6
6
  "license": "MIT",