@lucasschirm/devinp 0.1.4 → 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.
Files changed (3) hide show
  1. package/README.md +13 -0
  2. package/bin/command.js +180 -62
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -24,6 +24,19 @@ The package ships as a self-contained bundle — no dependencies are installed.
24
24
  not authenticated, `devinp` exits with an error (use `--source` for a saved
25
25
  snapshot).
26
26
 
27
+ ### When `devin` fails or truncates
28
+
29
+ - Prices are captured through a temp file in `os.tmpdir()` — `devin`'s
30
+ interactive CLI can truncate a pipe stream mid-write, so `devinp` points
31
+ `devin`'s stdout at a real file instead. The temp file is always removed
32
+ after the run.
33
+ - If `devin`'s output is cut off mid-stream, `devinp` repairs the document,
34
+ prints a warning, and renders the partial table.
35
+ - If the output cannot be recovered, `devinp` reports a diagnostic with the
36
+ size and tail of what `devin` wrote, and suggests re-running or saving the
37
+ output manually: `devin models list --format json > models.json`, then
38
+ `devinp --source models.json`.
39
+
27
40
  ## Usage
28
41
 
29
42
  ```sh
package/bin/command.js CHANGED
@@ -2887,9 +2887,9 @@ var require_ejs = __commonJS({
2887
2887
  createRegex: function() {
2888
2888
  var str = _REGEX_STRING;
2889
2889
  var delim = utils_js_1.default.escapeRegExpChars(this.opts.delimiter);
2890
- var open = utils_js_1.default.escapeRegExpChars(this.opts.openDelimiter);
2890
+ var open2 = utils_js_1.default.escapeRegExpChars(this.opts.openDelimiter);
2891
2891
  var close = utils_js_1.default.escapeRegExpChars(this.opts.closeDelimiter);
2892
- str = str.replace(/%/g, delim).replace(/</g, open).replace(/>/g, close);
2892
+ str = str.replace(/%/g, delim).replace(/</g, open2).replace(/>/g, close);
2893
2893
  return new RegExp(str);
2894
2894
  },
2895
2895
  compile: function() {
@@ -6393,8 +6393,8 @@ var require_async = __commonJS({
6393
6393
  "node_modules/get-package-type/async.cjs"(exports, module) {
6394
6394
  "use strict";
6395
6395
  var path = __require("path");
6396
- var { promisify: promisify4 } = __require("util");
6397
- var readFile3 = promisify4(__require("fs").readFile);
6396
+ var { promisify: promisify3 } = __require("util");
6397
+ var readFile3 = promisify3(__require("fs").readFile);
6398
6398
  var isNodeModules = require_is_node_modules();
6399
6399
  var resultsCache = require_cache2();
6400
6400
  var promiseCache = /* @__PURE__ */ new Map();
@@ -12628,10 +12628,10 @@ var require_commonjs3 = __commonJS({
12628
12628
  }
12629
12629
  return filtered.join("/");
12630
12630
  }).join("|");
12631
- const [open, close] = set.length > 1 ? ["(?:", ")"] : ["", ""];
12632
- re = "^" + open + re + close + "$";
12631
+ const [open2, close] = set.length > 1 ? ["(?:", ")"] : ["", ""];
12632
+ re = "^" + open2 + re + close + "$";
12633
12633
  if (this.partial) {
12634
- re = "^(?:\\/|" + open + re.slice(1, -1) + close + ")$";
12634
+ re = "^(?:\\/|" + open2 + re.slice(1, -1) + close + ")$";
12635
12635
  }
12636
12636
  if (this.negate)
12637
12637
  re = "^(?!" + re + ").+$";
@@ -13719,16 +13719,16 @@ var require_parse2 = __commonJS({
13719
13719
  const analysis = analyzeRepeatedExtglob(body, opts);
13720
13720
  if ((token.type === "plus" || token.type === "star") && analysis.risky) {
13721
13721
  const safeOutput = analysis.safeOutput ? (token.output ? "" : ONE_CHAR) + (opts.capture ? `(${analysis.safeOutput})` : analysis.safeOutput) : void 0;
13722
- const open = tokens[token.tokensIndex];
13723
- open.type = "text";
13724
- open.value = literal;
13725
- open.output = safeOutput || utils.escapeRegex(literal);
13722
+ const open2 = tokens[token.tokensIndex];
13723
+ open2.type = "text";
13724
+ open2.value = literal;
13725
+ open2.output = safeOutput || utils.escapeRegex(literal);
13726
13726
  for (let i = token.tokensIndex + 1; i < tokens.length; i++) {
13727
13727
  tokens[i].value = "";
13728
13728
  tokens[i].output = "";
13729
13729
  delete tokens[i].suffix;
13730
13730
  }
13731
- state.output = token.output + open.output;
13731
+ state.output = token.output + open2.output;
13732
13732
  state.backtrack = true;
13733
13733
  push({ type: "paren", extglob: true, value, output: "" });
13734
13734
  decrement("parens");
@@ -13948,15 +13948,15 @@ var require_parse2 = __commonJS({
13948
13948
  }
13949
13949
  if (value === "{" && opts.nobrace !== true) {
13950
13950
  increment("braces");
13951
- const open = {
13951
+ const open2 = {
13952
13952
  type: "brace",
13953
13953
  value,
13954
13954
  output: "(",
13955
13955
  outputIndex: state.output.length,
13956
13956
  tokensIndex: state.tokens.length
13957
13957
  };
13958
- braces.push(open);
13959
- push(open);
13958
+ braces.push(open2);
13959
+ push(open2);
13960
13960
  continue;
13961
13961
  }
13962
13962
  if (value === "}") {
@@ -27622,7 +27622,7 @@ var require_thread_stream = __commonJS({
27622
27622
  var { version } = require_package2();
27623
27623
  var { EventEmitter } = __require("events");
27624
27624
  var { Worker } = __require("worker_threads");
27625
- var { join: join2 } = __require("path");
27625
+ var { join: join3 } = __require("path");
27626
27626
  var { pathToFileURL } = __require("url");
27627
27627
  var { wait } = require_wait();
27628
27628
  var {
@@ -27673,7 +27673,7 @@ var require_thread_stream = __commonJS({
27673
27673
  function createWorker(stream, opts) {
27674
27674
  const { filename, workerData } = opts;
27675
27675
  const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
27676
- const toExecute = bundlerOverrides["thread-stream-worker"] || join2(__dirname, "lib", "worker.js");
27676
+ const toExecute = bundlerOverrides["thread-stream-worker"] || join3(__dirname, "lib", "worker.js");
27677
27677
  const worker = new Worker(toExecute, {
27678
27678
  ...opts.workerOpts,
27679
27679
  name: opts.workerOpts?.name || "thread-stream",
@@ -28141,7 +28141,7 @@ var require_transport = __commonJS({
28141
28141
  var { createRequire } = __require("module");
28142
28142
  var { existsSync } = __require("node:fs");
28143
28143
  var getCallers = require_caller();
28144
- var { join: join2, isAbsolute, sep } = __require("node:path");
28144
+ var { join: join3, isAbsolute, sep } = __require("node:path");
28145
28145
  var { fileURLToPath } = __require("node:url");
28146
28146
  var sleep = require_atomic_sleep();
28147
28147
  var onExit = require_on_exit_leak_free();
@@ -28294,7 +28294,7 @@ var require_transport = __commonJS({
28294
28294
  throw new Error("only one of target or targets can be specified");
28295
28295
  }
28296
28296
  if (targets) {
28297
- target = bundlerOverrides["pino-worker"] || join2(__dirname, "worker.js");
28297
+ target = bundlerOverrides["pino-worker"] || join3(__dirname, "worker.js");
28298
28298
  options.targets = targets.filter((dest) => dest.target).map((dest) => {
28299
28299
  return {
28300
28300
  ...dest,
@@ -28312,7 +28312,7 @@ var require_transport = __commonJS({
28312
28312
  });
28313
28313
  });
28314
28314
  } else if (pipeline) {
28315
- target = bundlerOverrides["pino-worker"] || join2(__dirname, "worker.js");
28315
+ target = bundlerOverrides["pino-worker"] || join3(__dirname, "worker.js");
28316
28316
  options.pipelines = [pipeline.map((dest) => {
28317
28317
  return {
28318
28318
  ...dest,
@@ -28335,7 +28335,7 @@ var require_transport = __commonJS({
28335
28335
  return origin;
28336
28336
  }
28337
28337
  if (origin === "pino/file") {
28338
- return join2(__dirname, "..", "file.js");
28338
+ return join3(__dirname, "..", "file.js");
28339
28339
  }
28340
28340
  let fixTarget2;
28341
28341
  for (const filePath of callers) {
@@ -29315,7 +29315,7 @@ var require_safe_stable_stringify = __commonJS({
29315
29315
  return circularValue;
29316
29316
  }
29317
29317
  let res = "";
29318
- let join2 = ",";
29318
+ let join3 = ",";
29319
29319
  const originalIndentation = indentation;
29320
29320
  if (Array.isArray(value)) {
29321
29321
  if (value.length === 0) {
@@ -29329,7 +29329,7 @@ var require_safe_stable_stringify = __commonJS({
29329
29329
  indentation += spacer;
29330
29330
  res += `
29331
29331
  ${indentation}`;
29332
- join2 = `,
29332
+ join3 = `,
29333
29333
  ${indentation}`;
29334
29334
  }
29335
29335
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -29337,13 +29337,13 @@ ${indentation}`;
29337
29337
  for (; i < maximumValuesToStringify - 1; i++) {
29338
29338
  const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
29339
29339
  res += tmp2 !== void 0 ? tmp2 : "null";
29340
- res += join2;
29340
+ res += join3;
29341
29341
  }
29342
29342
  const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
29343
29343
  res += tmp !== void 0 ? tmp : "null";
29344
29344
  if (value.length - 1 > maximumBreadth) {
29345
29345
  const removedKeys = value.length - maximumBreadth - 1;
29346
- res += `${join2}"... ${getItemCount(removedKeys)} not stringified"`;
29346
+ res += `${join3}"... ${getItemCount(removedKeys)} not stringified"`;
29347
29347
  }
29348
29348
  if (spacer !== "") {
29349
29349
  res += `
@@ -29364,7 +29364,7 @@ ${originalIndentation}`;
29364
29364
  let separator = "";
29365
29365
  if (spacer !== "") {
29366
29366
  indentation += spacer;
29367
- join2 = `,
29367
+ join3 = `,
29368
29368
  ${indentation}`;
29369
29369
  whitespace = " ";
29370
29370
  }
@@ -29378,13 +29378,13 @@ ${indentation}`;
29378
29378
  const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
29379
29379
  if (tmp !== void 0) {
29380
29380
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
29381
- separator = join2;
29381
+ separator = join3;
29382
29382
  }
29383
29383
  }
29384
29384
  if (keyLength > maximumBreadth) {
29385
29385
  const removedKeys = keyLength - maximumBreadth;
29386
29386
  res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
29387
- separator = join2;
29387
+ separator = join3;
29388
29388
  }
29389
29389
  if (spacer !== "" && separator.length > 1) {
29390
29390
  res = `
@@ -29425,7 +29425,7 @@ ${originalIndentation}`;
29425
29425
  }
29426
29426
  const originalIndentation = indentation;
29427
29427
  let res = "";
29428
- let join2 = ",";
29428
+ let join3 = ",";
29429
29429
  if (Array.isArray(value)) {
29430
29430
  if (value.length === 0) {
29431
29431
  return "[]";
@@ -29438,7 +29438,7 @@ ${originalIndentation}`;
29438
29438
  indentation += spacer;
29439
29439
  res += `
29440
29440
  ${indentation}`;
29441
- join2 = `,
29441
+ join3 = `,
29442
29442
  ${indentation}`;
29443
29443
  }
29444
29444
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -29446,13 +29446,13 @@ ${indentation}`;
29446
29446
  for (; i < maximumValuesToStringify - 1; i++) {
29447
29447
  const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
29448
29448
  res += tmp2 !== void 0 ? tmp2 : "null";
29449
- res += join2;
29449
+ res += join3;
29450
29450
  }
29451
29451
  const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
29452
29452
  res += tmp !== void 0 ? tmp : "null";
29453
29453
  if (value.length - 1 > maximumBreadth) {
29454
29454
  const removedKeys = value.length - maximumBreadth - 1;
29455
- res += `${join2}"... ${getItemCount(removedKeys)} not stringified"`;
29455
+ res += `${join3}"... ${getItemCount(removedKeys)} not stringified"`;
29456
29456
  }
29457
29457
  if (spacer !== "") {
29458
29458
  res += `
@@ -29465,7 +29465,7 @@ ${originalIndentation}`;
29465
29465
  let whitespace = "";
29466
29466
  if (spacer !== "") {
29467
29467
  indentation += spacer;
29468
- join2 = `,
29468
+ join3 = `,
29469
29469
  ${indentation}`;
29470
29470
  whitespace = " ";
29471
29471
  }
@@ -29474,7 +29474,7 @@ ${indentation}`;
29474
29474
  const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
29475
29475
  if (tmp !== void 0) {
29476
29476
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
29477
- separator = join2;
29477
+ separator = join3;
29478
29478
  }
29479
29479
  }
29480
29480
  if (spacer !== "" && separator.length > 1) {
@@ -29532,20 +29532,20 @@ ${originalIndentation}`;
29532
29532
  indentation += spacer;
29533
29533
  let res2 = `
29534
29534
  ${indentation}`;
29535
- const join3 = `,
29535
+ const join4 = `,
29536
29536
  ${indentation}`;
29537
29537
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
29538
29538
  let i = 0;
29539
29539
  for (; i < maximumValuesToStringify - 1; i++) {
29540
29540
  const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
29541
29541
  res2 += tmp2 !== void 0 ? tmp2 : "null";
29542
- res2 += join3;
29542
+ res2 += join4;
29543
29543
  }
29544
29544
  const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
29545
29545
  res2 += tmp !== void 0 ? tmp : "null";
29546
29546
  if (value.length - 1 > maximumBreadth) {
29547
29547
  const removedKeys = value.length - maximumBreadth - 1;
29548
- res2 += `${join3}"... ${getItemCount(removedKeys)} not stringified"`;
29548
+ res2 += `${join4}"... ${getItemCount(removedKeys)} not stringified"`;
29549
29549
  }
29550
29550
  res2 += `
29551
29551
  ${originalIndentation}`;
@@ -29561,16 +29561,16 @@ ${originalIndentation}`;
29561
29561
  return '"[Object]"';
29562
29562
  }
29563
29563
  indentation += spacer;
29564
- const join2 = `,
29564
+ const join3 = `,
29565
29565
  ${indentation}`;
29566
29566
  let res = "";
29567
29567
  let separator = "";
29568
29568
  let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
29569
29569
  if (isTypedArrayWithEntries(value)) {
29570
- res += stringifyTypedArray(value, join2, maximumBreadth);
29570
+ res += stringifyTypedArray(value, join3, maximumBreadth);
29571
29571
  keys = keys.slice(value.length);
29572
29572
  maximumPropertiesToStringify -= value.length;
29573
- separator = join2;
29573
+ separator = join3;
29574
29574
  }
29575
29575
  if (deterministic) {
29576
29576
  keys = sort(keys, comparator);
@@ -29581,13 +29581,13 @@ ${indentation}`;
29581
29581
  const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
29582
29582
  if (tmp !== void 0) {
29583
29583
  res += `${separator}${strEscape(key2)}: ${tmp}`;
29584
- separator = join2;
29584
+ separator = join3;
29585
29585
  }
29586
29586
  }
29587
29587
  if (keyLength > maximumBreadth) {
29588
29588
  const removedKeys = keyLength - maximumBreadth;
29589
29589
  res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
29590
- separator = join2;
29590
+ separator = join3;
29591
29591
  }
29592
29592
  if (separator !== "") {
29593
29593
  res = `
@@ -55023,41 +55023,155 @@ async function startServer({ rows, benchMeta, port, log }) {
55023
55023
  }
55024
55024
 
55025
55025
  // src/lib/source.js
55026
- import { execFile as execFile4 } from "node:child_process";
55027
- import { readFile as readFile2 } from "node:fs/promises";
55028
- import { promisify as promisify3 } from "node:util";
55029
- var execFileAsync = promisify3(execFile4);
55030
- async function loadModelData({ source } = {}) {
55026
+ import { spawn } from "node:child_process";
55027
+ import { open, readFile as readFile2, unlink } from "node:fs/promises";
55028
+ import { tmpdir } from "node:os";
55029
+ import { join as join2 } from "node:path";
55030
+
55031
+ // src/lib/repair-json.js
55032
+ var CLOSER = { "{": "}", "[": "]" };
55033
+ function recoverTruncatedJson(raw) {
55034
+ if (typeof raw !== "string") return null;
55035
+ try {
55036
+ return JSON.parse(raw);
55037
+ } catch {
55038
+ }
55039
+ const scan = scanJson(raw, raw.length);
55040
+ const cuts = [raw.length];
55041
+ if (scan.inString) cuts.push(scan.stringStart);
55042
+ if (scan.lastComma != null) cuts.push(scan.lastComma + 1);
55043
+ if (scan.lastCloser != null) cuts.push(scan.lastCloser + 1);
55044
+ const seen = /* @__PURE__ */ new Set();
55045
+ for (const cut of cuts) {
55046
+ if (seen.has(cut)) continue;
55047
+ seen.add(cut);
55048
+ let candidate = raw.slice(0, cut).replace(/[\s,]+$/g, "");
55049
+ if (candidate === "") continue;
55050
+ const s = scanJson(candidate, candidate.length);
55051
+ const repaired = candidate + s.stack.map((token) => CLOSER[token]).reverse().join("");
55052
+ try {
55053
+ return JSON.parse(repaired);
55054
+ } catch {
55055
+ }
55056
+ }
55057
+ return null;
55058
+ }
55059
+ function scanJson(s, end) {
55060
+ let inString = false;
55061
+ let escape2 = false;
55062
+ let stringStart = 0;
55063
+ const stack = [];
55064
+ let lastComma = null;
55065
+ let lastCloser = null;
55066
+ for (let i = 0; i < end; i++) {
55067
+ const ch = s[i];
55068
+ if (inString) {
55069
+ if (escape2) escape2 = false;
55070
+ else if (ch === "\\") escape2 = true;
55071
+ else if (ch === '"') inString = false;
55072
+ continue;
55073
+ }
55074
+ if (ch === '"') {
55075
+ inString = true;
55076
+ stringStart = i;
55077
+ } else if (ch === ",") {
55078
+ lastComma = i;
55079
+ } else if (ch === "{" || ch === "[") {
55080
+ stack.push(ch);
55081
+ } else if (ch === "}" || ch === "]") {
55082
+ stack.pop();
55083
+ lastCloser = i;
55084
+ }
55085
+ }
55086
+ return { inString, stringStart, stack, lastComma, lastCloser };
55087
+ }
55088
+
55089
+ // src/lib/source.js
55090
+ var DEVIN_TIMEOUT_MS = 3e4;
55091
+ async function runDevinJson({ command = "devin", timeoutMs = DEVIN_TIMEOUT_MS } = {}) {
55092
+ const file = join2(tmpdir(), `devinp-models-${process.pid}-${Date.now()}.json`);
55093
+ const handle = await open(file, "w");
55094
+ try {
55095
+ const { code, stderr, timedOut } = await new Promise((resolve, reject) => {
55096
+ const child = spawn(command, ["models", "list", "--format", "json"], {
55097
+ stdio: ["ignore", handle.fd, "pipe"]
55098
+ });
55099
+ let stderr2 = "";
55100
+ let timedOut2 = false;
55101
+ const timer = setTimeout(() => {
55102
+ timedOut2 = true;
55103
+ child.kill("SIGKILL");
55104
+ }, timeoutMs);
55105
+ child.stderr.on("data", (chunk) => {
55106
+ stderr2 += chunk;
55107
+ });
55108
+ child.on("error", (error) => {
55109
+ clearTimeout(timer);
55110
+ reject(error);
55111
+ });
55112
+ child.on("close", (childCode) => {
55113
+ clearTimeout(timer);
55114
+ resolve({ code: childCode, stderr: stderr2, timedOut: timedOut2 });
55115
+ });
55116
+ });
55117
+ if (timedOut) {
55118
+ throw new Error(`\`devin models list\` timed out after ${timeoutMs / 1e3}s`);
55119
+ }
55120
+ if (code !== 0) {
55121
+ throw new Error(`\`devin models list\` failed \u2014 ${stderr.trim() || `exited with code ${code}`}`);
55122
+ }
55123
+ return await readFile2(file, "utf8");
55124
+ } finally {
55125
+ await handle.close();
55126
+ await unlink(file).catch(() => {
55127
+ });
55128
+ }
55129
+ }
55130
+ function assertFamilies(data, sourceName) {
55131
+ if (data == null || typeof data !== "object" || !Array.isArray(data.families)) {
55132
+ throw new Error(`Unable to get model prices: ${sourceName} has no "families" array (expected \`devin models list --format json\` output)`);
55133
+ }
55134
+ if (data.families.length === 0) {
55135
+ throw new Error(`Unable to get model prices: ${sourceName} contains no model families`);
55136
+ }
55137
+ return data;
55138
+ }
55139
+ async function loadModelData({ source, command = "devin", timeoutMs = DEVIN_TIMEOUT_MS } = {}) {
55031
55140
  if (source) {
55032
- let raw;
55141
+ let raw2;
55033
55142
  try {
55034
- raw = await readFile2(source, "utf8");
55143
+ raw2 = await readFile2(source, "utf8");
55035
55144
  } catch (error) {
55036
55145
  throw new Error(`Unable to get model prices: cannot read source file "${source}" (${error.code ?? error.message})`);
55037
55146
  }
55038
55147
  try {
55039
- return JSON.parse(raw);
55040
- } catch {
55148
+ return { data: assertFamilies(JSON.parse(raw2), source) };
55149
+ } catch (error) {
55150
+ if (error.message.startsWith("Unable to get model prices")) throw error;
55041
55151
  throw new Error(`Unable to get model prices: "${source}" is not valid JSON (expected \`devin models list --format json\` output)`);
55042
55152
  }
55043
55153
  }
55044
- let stdout;
55154
+ let raw;
55045
55155
  try {
55046
- ;
55047
- ({ stdout } = await execFileAsync("devin", ["models", "list", "--format", "json"], {
55048
- maxBuffer: 64 * 1024 * 1024
55049
- }));
55156
+ raw = await runDevinJson({ command, timeoutMs });
55050
55157
  } catch (error) {
55051
55158
  if (error.code === "ENOENT") {
55052
55159
  throw new Error("Unable to get model prices: `devin` CLI not found in PATH. Install it or pass --source <file>.");
55053
55160
  }
55054
- const detail = error.stderr?.trim() || error.message;
55055
- throw new Error(`Unable to get model prices: \`devin models list\` failed \u2014 ${detail}`);
55161
+ throw new Error(`Unable to get model prices: ${error.message}`);
55056
55162
  }
55057
55163
  try {
55058
- return JSON.parse(stdout);
55059
- } catch {
55060
- throw new Error("Unable to get model prices: `devin models list --format json` returned invalid JSON");
55164
+ return { data: assertFamilies(JSON.parse(raw), "devin models list") };
55165
+ } catch (error) {
55166
+ if (error.message.startsWith("Unable to get model prices")) throw error;
55167
+ const recovered = recoverTruncatedJson(raw);
55168
+ if (recovered != null) {
55169
+ return { data: assertFamilies(recovered, "devin models list (recovered)"), truncated: true };
55170
+ }
55171
+ const tail = raw.trimEnd().slice(-200);
55172
+ throw new Error(
55173
+ `Unable to get model prices: \`devin models list --format json\` returned invalid JSON (${error.message} \u2014 ${raw.length} bytes, output ends with ${JSON.stringify(tail)}). The output may have been truncated mid-stream: re-run, or save the output with \`devin models list --format json > models.json\` and pass \`--source models.json\`.`
55174
+ );
55061
55175
  }
55062
55176
  }
55063
55177
 
@@ -55111,7 +55225,11 @@ var Devinp = class _Devinp extends import_core.Command {
55111
55225
  const { flags } = await this.parse(_Devinp);
55112
55226
  let data;
55113
55227
  try {
55114
- data = await loadModelData({ source: flags.source });
55228
+ const loaded = await loadModelData({ source: flags.source });
55229
+ if (loaded.truncated) {
55230
+ this.warn("devin output was truncated mid-stream \u2014 showing partial data, re-run for the full list");
55231
+ }
55232
+ data = loaded.data;
55115
55233
  } catch (error) {
55116
55234
  this.error(error instanceof Error ? error.message : String(error), { exit: 1 });
55117
55235
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lucasschirm/devinp",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Pretty pricing table for Devin models, powered by `devin models list`",
5
5
  "type": "module",
6
6
  "bin": {