@lucasschirm/devinp 0.1.9 → 0.1.11
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 +29 -10
- package/bin/command.js +184 -68
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,18 +24,36 @@ 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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
### When `devin` fails, pages, or truncates
|
|
28
|
+
|
|
29
|
+
`devin` is an interactive CLI, and on some machines it stays interactive even
|
|
30
|
+
when a script calls it: the model list comes back paged ("click to show more")
|
|
31
|
+
and the JSON never reaches stdout, so `devinp` used to die with *"returned
|
|
32
|
+
invalid JSON (… 0 bytes)"*. It now works harder to get the whole document:
|
|
33
|
+
|
|
34
|
+
- Prices are captured through a temp file in `os.tmpdir()` — a pipe stream from
|
|
35
|
+
`devin` can be truncated mid-write, so `devinp` points `devin`'s stdout at a
|
|
36
|
+
real file first. The temp file is always removed after the run.
|
|
37
|
+
- **Retries, each one louder about not being interactive.** If an attempt
|
|
38
|
+
yields no usable JSON, `devinp` runs `devin` again with the "not interactive"
|
|
39
|
+
conventions set (`PAGER=cat`, `GIT_PAGER=cat`, `TERM=dumb`, `NO_COLOR=1`,
|
|
40
|
+
`CI=1`) and, on the last try, captures over a pipe instead of a file. The
|
|
41
|
+
first successful attempt wins, so the healthy path costs one `devin` call.
|
|
42
|
+
- Noise around the document is tolerated: ANSI colours, an update banner in
|
|
43
|
+
front, or the `pass a slug to --model` hint behind are stripped before the
|
|
44
|
+
JSON is parsed.
|
|
33
45
|
- If `devin`'s output is cut off mid-stream, `devinp` repairs the document,
|
|
34
46
|
prints a warning, and renders the partial table.
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
- A real CLI failure (not signed in, network down) is reported as-is instead of
|
|
48
|
+
being retried.
|
|
49
|
+
- If nothing worked, the error lists every attempt — bytes, exit code, stderr,
|
|
50
|
+
timeout — and how to feed the data in yourself. That always works, because a
|
|
51
|
+
redirect or a pipe makes `devin` write the whole document in one go:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
devin models list --format json > models.json && devinp --source models.json
|
|
55
|
+
devin models list --format json | devinp --source=-
|
|
56
|
+
```
|
|
39
57
|
|
|
40
58
|
## Usage
|
|
41
59
|
|
|
@@ -55,6 +73,7 @@ devinp --web # serve an interactive page on http://127.0.0.1:432
|
|
|
55
73
|
devinp --web --port 8080 # pick a port
|
|
56
74
|
|
|
57
75
|
devinp --source models.json # read a saved `devin models list --format json`
|
|
76
|
+
devin models list --format json | devinp --source=- # feed it straight from stdin
|
|
58
77
|
devinp --json # emit normalized rows as JSON
|
|
59
78
|
```
|
|
60
79
|
|
package/bin/command.js
CHANGED
|
@@ -4193,14 +4193,14 @@ var require_emoji_regex = __commonJS({
|
|
|
4193
4193
|
var require_string_width = __commonJS({
|
|
4194
4194
|
"node_modules/string-width/index.js"(exports, module) {
|
|
4195
4195
|
"use strict";
|
|
4196
|
-
var
|
|
4196
|
+
var stripAnsi2 = require_strip_ansi();
|
|
4197
4197
|
var isFullwidthCodePoint = require_is_fullwidth_code_point();
|
|
4198
4198
|
var emojiRegex = require_emoji_regex();
|
|
4199
4199
|
var stringWidth = (string) => {
|
|
4200
4200
|
if (typeof string !== "string" || string.length === 0) {
|
|
4201
4201
|
return 0;
|
|
4202
4202
|
}
|
|
4203
|
-
string =
|
|
4203
|
+
string = stripAnsi2(string);
|
|
4204
4204
|
if (string.length === 0) {
|
|
4205
4205
|
return 0;
|
|
4206
4206
|
}
|
|
@@ -5332,7 +5332,7 @@ var require_wrap_ansi = __commonJS({
|
|
|
5332
5332
|
"node_modules/wrap-ansi/index.js"(exports, module) {
|
|
5333
5333
|
"use strict";
|
|
5334
5334
|
var stringWidth = require_string_width();
|
|
5335
|
-
var
|
|
5335
|
+
var stripAnsi2 = require_strip_ansi();
|
|
5336
5336
|
var ansiStyles = require_ansi_styles();
|
|
5337
5337
|
var ESCAPES = /* @__PURE__ */ new Set([
|
|
5338
5338
|
"\x1B",
|
|
@@ -5351,7 +5351,7 @@ var require_wrap_ansi = __commonJS({
|
|
|
5351
5351
|
const characters = [...word];
|
|
5352
5352
|
let isInsideEscape = false;
|
|
5353
5353
|
let isInsideLinkEscape = false;
|
|
5354
|
-
let visible2 = stringWidth(
|
|
5354
|
+
let visible2 = stringWidth(stripAnsi2(rows[rows.length - 1]));
|
|
5355
5355
|
for (const [index, character] of characters.entries()) {
|
|
5356
5356
|
const characterLength = stringWidth(character);
|
|
5357
5357
|
if (visible2 + characterLength <= columns) {
|
|
@@ -13355,7 +13355,7 @@ var require_parse2 = __commonJS({
|
|
|
13355
13355
|
const parts = [];
|
|
13356
13356
|
let bracket = 0;
|
|
13357
13357
|
let paren = 0;
|
|
13358
|
-
let
|
|
13358
|
+
let quote2 = 0;
|
|
13359
13359
|
let value = "";
|
|
13360
13360
|
let escaped = false;
|
|
13361
13361
|
for (const ch of input) {
|
|
@@ -13370,11 +13370,11 @@ var require_parse2 = __commonJS({
|
|
|
13370
13370
|
continue;
|
|
13371
13371
|
}
|
|
13372
13372
|
if (ch === '"') {
|
|
13373
|
-
|
|
13373
|
+
quote2 = quote2 === 1 ? 0 : 1;
|
|
13374
13374
|
value += ch;
|
|
13375
13375
|
continue;
|
|
13376
13376
|
}
|
|
13377
|
-
if (
|
|
13377
|
+
if (quote2 === 0) {
|
|
13378
13378
|
if (ch === "[") {
|
|
13379
13379
|
bracket++;
|
|
13380
13380
|
} else if (ch === "]" && bracket > 0) {
|
|
@@ -13451,7 +13451,7 @@ var require_parse2 = __commonJS({
|
|
|
13451
13451
|
}
|
|
13452
13452
|
let bracket = 0;
|
|
13453
13453
|
let paren = 0;
|
|
13454
|
-
let
|
|
13454
|
+
let quote2 = 0;
|
|
13455
13455
|
let escaped = false;
|
|
13456
13456
|
for (let i = 1; i < pattern.length; i++) {
|
|
13457
13457
|
const ch = pattern[i];
|
|
@@ -13464,10 +13464,10 @@ var require_parse2 = __commonJS({
|
|
|
13464
13464
|
continue;
|
|
13465
13465
|
}
|
|
13466
13466
|
if (ch === '"') {
|
|
13467
|
-
|
|
13467
|
+
quote2 = quote2 === 1 ? 0 : 1;
|
|
13468
13468
|
continue;
|
|
13469
13469
|
}
|
|
13470
|
-
if (
|
|
13470
|
+
if (quote2 === 1) {
|
|
13471
13471
|
continue;
|
|
13472
13472
|
}
|
|
13473
13473
|
if (ch === "[") {
|
|
@@ -17633,7 +17633,7 @@ var require_parse3 = __commonJS({
|
|
|
17633
17633
|
debug = () => {
|
|
17634
17634
|
};
|
|
17635
17635
|
}
|
|
17636
|
-
var
|
|
17636
|
+
var readStdin2 = async () => {
|
|
17637
17637
|
const { stdin, stdout } = process;
|
|
17638
17638
|
if (stdin.isTTY)
|
|
17639
17639
|
return null;
|
|
@@ -17669,7 +17669,7 @@ var require_parse3 = __commonJS({
|
|
|
17669
17669
|
}, { once: true });
|
|
17670
17670
|
});
|
|
17671
17671
|
};
|
|
17672
|
-
exports.readStdin =
|
|
17672
|
+
exports.readStdin = readStdin2;
|
|
17673
17673
|
function isNegativeNumber(input) {
|
|
17674
17674
|
return /^-\d/g.test(input);
|
|
17675
17675
|
}
|
|
@@ -41394,15 +41394,15 @@ var require_quote = __commonJS({
|
|
|
41394
41394
|
'"': '\\"',
|
|
41395
41395
|
"\\": "\\\\"
|
|
41396
41396
|
};
|
|
41397
|
-
function
|
|
41397
|
+
function quote2(s) {
|
|
41398
41398
|
rxEscapable.lastIndex = 0;
|
|
41399
41399
|
return '"' + (rxEscapable.test(s) ? s.replace(rxEscapable, (a2) => {
|
|
41400
41400
|
const c = escaped[a2];
|
|
41401
41401
|
return typeof c === "string" ? c : "\\u" + ("0000" + a2.charCodeAt(0).toString(16)).slice(-4);
|
|
41402
41402
|
}) : s) + '"';
|
|
41403
41403
|
}
|
|
41404
|
-
exports.default =
|
|
41405
|
-
|
|
41404
|
+
exports.default = quote2;
|
|
41405
|
+
quote2.code = 'require("ajv/dist/runtime/quote").default';
|
|
41406
41406
|
}
|
|
41407
41407
|
});
|
|
41408
41408
|
|
|
@@ -55088,45 +55088,113 @@ function scanJson(s, end) {
|
|
|
55088
55088
|
|
|
55089
55089
|
// src/lib/source.js
|
|
55090
55090
|
var DEVIN_TIMEOUT_MS = 3e4;
|
|
55091
|
-
|
|
55091
|
+
var NON_INTERACTIVE_ENV = {
|
|
55092
|
+
PAGER: "cat",
|
|
55093
|
+
GIT_PAGER: "cat",
|
|
55094
|
+
TERM: "dumb",
|
|
55095
|
+
NO_COLOR: "1",
|
|
55096
|
+
CI: "1"
|
|
55097
|
+
};
|
|
55098
|
+
var STRATEGIES = [
|
|
55099
|
+
{ label: "temp file", file: true, env: null },
|
|
55100
|
+
{ label: "temp file + non-interactive env", file: true, env: NON_INTERACTIVE_ENV },
|
|
55101
|
+
{ label: "pipe + non-interactive env", file: false, env: NON_INTERACTIVE_ENV }
|
|
55102
|
+
];
|
|
55103
|
+
var ANSI = /\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][\s\S]*?(?:\u0007|\u001b\\)|\u001b[@-Z\\-_]/g;
|
|
55104
|
+
function stripAnsi(text) {
|
|
55105
|
+
return text.replace(ANSI, "");
|
|
55106
|
+
}
|
|
55107
|
+
function parseJsonLoosely(text) {
|
|
55108
|
+
const clean = stripAnsi(text);
|
|
55109
|
+
try {
|
|
55110
|
+
return JSON.parse(clean);
|
|
55111
|
+
} catch {
|
|
55112
|
+
}
|
|
55113
|
+
const start = clean.indexOf("{");
|
|
55114
|
+
const end = clean.lastIndexOf("}");
|
|
55115
|
+
if (start >= 0 && end > start) {
|
|
55116
|
+
try {
|
|
55117
|
+
return JSON.parse(clean.slice(start, end + 1));
|
|
55118
|
+
} catch {
|
|
55119
|
+
}
|
|
55120
|
+
}
|
|
55121
|
+
return null;
|
|
55122
|
+
}
|
|
55123
|
+
function spawnDevin({ command, env, stdout, timeoutMs }) {
|
|
55124
|
+
return new Promise((resolve, reject) => {
|
|
55125
|
+
const child = spawn(command, ["models", "list", "--format", "json"], {
|
|
55126
|
+
stdio: ["ignore", stdout, "pipe"],
|
|
55127
|
+
...env ? { env: { ...process.env, ...env } } : {}
|
|
55128
|
+
});
|
|
55129
|
+
let stdoutText = "";
|
|
55130
|
+
let stderr = "";
|
|
55131
|
+
let timedOut = false;
|
|
55132
|
+
const timer = setTimeout(() => {
|
|
55133
|
+
timedOut = true;
|
|
55134
|
+
child.kill("SIGKILL");
|
|
55135
|
+
}, timeoutMs);
|
|
55136
|
+
child.stdout?.on("data", (chunk) => {
|
|
55137
|
+
stdoutText += chunk;
|
|
55138
|
+
});
|
|
55139
|
+
child.stderr.on("data", (chunk) => {
|
|
55140
|
+
stderr += chunk;
|
|
55141
|
+
});
|
|
55142
|
+
child.on("error", (error) => {
|
|
55143
|
+
clearTimeout(timer);
|
|
55144
|
+
reject(error);
|
|
55145
|
+
});
|
|
55146
|
+
child.on("close", (code) => {
|
|
55147
|
+
clearTimeout(timer);
|
|
55148
|
+
resolve({ stdout: stdoutText, stderr, code, timedOut });
|
|
55149
|
+
});
|
|
55150
|
+
});
|
|
55151
|
+
}
|
|
55152
|
+
async function captureToFile({ command, env, timeoutMs }) {
|
|
55092
55153
|
const file = join2(tmpdir(), `devinp-models-${process.pid}-${Date.now()}.json`);
|
|
55093
55154
|
const handle = await open(file, "w");
|
|
55094
55155
|
try {
|
|
55095
|
-
const {
|
|
55096
|
-
|
|
55097
|
-
|
|
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");
|
|
55156
|
+
const result = await spawnDevin({ command, env, stdout: handle.fd, timeoutMs });
|
|
55157
|
+
result.stdout = await readFile2(file, "utf8");
|
|
55158
|
+
return result;
|
|
55124
55159
|
} finally {
|
|
55125
55160
|
await handle.close();
|
|
55126
55161
|
await unlink(file).catch(() => {
|
|
55127
55162
|
});
|
|
55128
55163
|
}
|
|
55129
55164
|
}
|
|
55165
|
+
async function runDevinJson({ command = "devin", timeoutMs = DEVIN_TIMEOUT_MS } = {}) {
|
|
55166
|
+
const attempts = [];
|
|
55167
|
+
for (const [index, strategy] of STRATEGIES.entries()) {
|
|
55168
|
+
let result;
|
|
55169
|
+
try {
|
|
55170
|
+
result = strategy.file ? await captureToFile({ command, env: strategy.env, timeoutMs }) : await spawnDevin({ command, env: strategy.env, stdout: "pipe", timeoutMs });
|
|
55171
|
+
} catch (error) {
|
|
55172
|
+
if (error.code === "ENOENT") throw error;
|
|
55173
|
+
attempts.push({ label: strategy.label, bytes: 0, code: null, stderr: "", timedOut: false, note: error.message });
|
|
55174
|
+
continue;
|
|
55175
|
+
}
|
|
55176
|
+
const raw = result.stdout ?? "";
|
|
55177
|
+
const parsed = raw.trim() ? parseJsonLoosely(raw) : null;
|
|
55178
|
+
attempts.push({
|
|
55179
|
+
label: strategy.label,
|
|
55180
|
+
bytes: raw.length,
|
|
55181
|
+
code: result.code,
|
|
55182
|
+
stderr: result.stderr.trim(),
|
|
55183
|
+
timedOut: result.timedOut,
|
|
55184
|
+
raw
|
|
55185
|
+
});
|
|
55186
|
+
if (parsed != null) return { raw, parsed, attempts, timeoutMs };
|
|
55187
|
+
if (index === 0 && result.code !== 0 && !result.timedOut && result.stderr.trim()) {
|
|
55188
|
+
throw new Error(`\`devin models list\` failed \u2014 ${result.stderr.trim()}`);
|
|
55189
|
+
}
|
|
55190
|
+
}
|
|
55191
|
+
const explained = [...attempts].reverse().find((a2) => a2.stderr);
|
|
55192
|
+
if (explained && attempts.every((a2) => a2.timedOut || a2.code !== 0)) {
|
|
55193
|
+
throw new Error(`\`devin models list\` failed \u2014 ${explained.stderr}`);
|
|
55194
|
+
}
|
|
55195
|
+
const longest = attempts.reduce((acc, a2) => (a2.raw?.length ?? 0) > acc.length ? a2.raw : acc, "");
|
|
55196
|
+
return { raw: longest || "", parsed: null, attempts, timeoutMs };
|
|
55197
|
+
}
|
|
55130
55198
|
function assertFamilies(data, sourceName) {
|
|
55131
55199
|
if (data == null || typeof data !== "object" || !Array.isArray(data.families)) {
|
|
55132
55200
|
throw new Error(`Unable to get model prices: ${sourceName} has no "families" array (expected \`devin models list --format json\` output)`);
|
|
@@ -55136,43 +55204,87 @@ function assertFamilies(data, sourceName) {
|
|
|
55136
55204
|
}
|
|
55137
55205
|
return data;
|
|
55138
55206
|
}
|
|
55139
|
-
|
|
55207
|
+
function quote(text) {
|
|
55208
|
+
return JSON.stringify(text.length > 200 ? `\u2026${text.slice(-199)}` : text);
|
|
55209
|
+
}
|
|
55210
|
+
function describeAttempt(attempt, timeoutMs) {
|
|
55211
|
+
const parts = [`${attempt.label}: ${attempt.bytes} bytes`];
|
|
55212
|
+
if (attempt.timedOut) parts.push(`timed out after ${timeoutMs / 1e3}s`);
|
|
55213
|
+
else parts.push(`exit ${attempt.code}`);
|
|
55214
|
+
if (attempt.stderr) parts.push(`stderr ${quote(attempt.stderr)}`);
|
|
55215
|
+
if (attempt.note) parts.push(attempt.note);
|
|
55216
|
+
return parts.join(", ");
|
|
55217
|
+
}
|
|
55218
|
+
function describeFailure(attempts, timeoutMs) {
|
|
55219
|
+
const bytes = Math.max(...attempts.map((a2) => a2.bytes), 0);
|
|
55220
|
+
const longest = attempts.find((a2) => a2.bytes === bytes);
|
|
55221
|
+
const tail = longest?.raw?.trimEnd().slice(-200) ?? "";
|
|
55222
|
+
const lines = [];
|
|
55223
|
+
if (bytes === 0) {
|
|
55224
|
+
lines.push(
|
|
55225
|
+
'Unable to get model prices: `devin models list --format json` exited without writing the JSON to stdout (0 bytes). Something between devinp and the CLI \u2014 a pager, an interactive "click to show more" view, or a shell/agent wrapper around `devin` \u2014 kept the output for the screen.'
|
|
55226
|
+
);
|
|
55227
|
+
} else {
|
|
55228
|
+
let parseError = "";
|
|
55229
|
+
try {
|
|
55230
|
+
JSON.parse(stripAnsi(longest.raw));
|
|
55231
|
+
} catch (error) {
|
|
55232
|
+
parseError = `${error.message} \u2014 `;
|
|
55233
|
+
}
|
|
55234
|
+
lines.push(
|
|
55235
|
+
`Unable to get model prices: \`devin models list --format json\` returned invalid JSON (${parseError}${bytes} bytes, output ends with ${JSON.stringify(tail)}). The output may have been truncated mid-stream.`
|
|
55236
|
+
);
|
|
55237
|
+
}
|
|
55238
|
+
lines.push(`Tried: ${attempts.map((a2) => describeAttempt(a2, timeoutMs)).join("; ")}.`);
|
|
55239
|
+
lines.push("Capture the JSON yourself and hand it to devinp \u2014 piping or redirecting makes `devin` write the whole document in one go:");
|
|
55240
|
+
lines.push(" devin models list --format json > models.json && devinp --source models.json");
|
|
55241
|
+
lines.push(" devin models list --format json | devinp --source=-");
|
|
55242
|
+
lines.push("If a plain `devin models list --format json > models.json` still prints a paged view, update the CLI (`devin update`).");
|
|
55243
|
+
return lines.join("\n");
|
|
55244
|
+
}
|
|
55245
|
+
async function readStdin() {
|
|
55246
|
+
const chunks = [];
|
|
55247
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
55248
|
+
return Buffer.concat(chunks.map((c) => typeof c === "string" ? Buffer.from(c) : c)).toString("utf8");
|
|
55249
|
+
}
|
|
55250
|
+
async function loadModelData({
|
|
55251
|
+
source,
|
|
55252
|
+
command = "devin",
|
|
55253
|
+
timeoutMs = DEVIN_TIMEOUT_MS,
|
|
55254
|
+
readStdinFrom = readStdin
|
|
55255
|
+
} = {}) {
|
|
55140
55256
|
if (source) {
|
|
55141
|
-
|
|
55257
|
+
const label = source === "-" ? "stdin" : `"${source}"`;
|
|
55258
|
+
let raw;
|
|
55142
55259
|
try {
|
|
55143
|
-
|
|
55260
|
+
raw = source === "-" ? await readStdinFrom() : await readFile2(source, "utf8");
|
|
55144
55261
|
} catch (error) {
|
|
55145
|
-
throw new Error(`Unable to get model prices: cannot read source file "${source}" (${error.code ?? error.message})`);
|
|
55262
|
+
throw new Error(`Unable to get model prices: cannot read ${source === "-" ? "stdin" : `source file "${source}"`} (${error.code ?? error.message})`);
|
|
55146
55263
|
}
|
|
55147
55264
|
try {
|
|
55148
|
-
return { data: assertFamilies(JSON.parse(
|
|
55265
|
+
return { data: assertFamilies(JSON.parse(raw), label), retries: 0 };
|
|
55149
55266
|
} catch (error) {
|
|
55150
55267
|
if (error.message.startsWith("Unable to get model prices")) throw error;
|
|
55151
|
-
throw new Error(`Unable to get model prices:
|
|
55268
|
+
throw new Error(`Unable to get model prices: ${label} is not valid JSON (expected \`devin models list --format json\` output)`);
|
|
55152
55269
|
}
|
|
55153
55270
|
}
|
|
55154
|
-
let
|
|
55271
|
+
let outcome;
|
|
55155
55272
|
try {
|
|
55156
|
-
|
|
55273
|
+
outcome = await runDevinJson({ command, timeoutMs });
|
|
55157
55274
|
} catch (error) {
|
|
55158
55275
|
if (error.code === "ENOENT") {
|
|
55159
|
-
throw new Error("Unable to get model prices: `devin` CLI not found in PATH. Install it or pass --source <file
|
|
55276
|
+
throw new Error("Unable to get model prices: `devin` CLI not found in PATH. Install it, or pass --source <file> / --source=- to supply the JSON yourself.");
|
|
55160
55277
|
}
|
|
55161
55278
|
throw new Error(`Unable to get model prices: ${error.message}`);
|
|
55162
55279
|
}
|
|
55163
|
-
|
|
55164
|
-
return { data: assertFamilies(
|
|
55165
|
-
}
|
|
55166
|
-
|
|
55167
|
-
|
|
55168
|
-
|
|
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
|
-
);
|
|
55280
|
+
if (outcome.parsed != null) {
|
|
55281
|
+
return { data: assertFamilies(outcome.parsed, "devin models list"), retries: outcome.attempts.length - 1 };
|
|
55282
|
+
}
|
|
55283
|
+
const recovered = recoverTruncatedJson(outcome.raw);
|
|
55284
|
+
if (recovered != null) {
|
|
55285
|
+
return { data: assertFamilies(recovered, "devin models list (recovered)"), truncated: true, retries: outcome.attempts.length - 1 };
|
|
55175
55286
|
}
|
|
55287
|
+
throw new Error(describeFailure(outcome.attempts, outcome.timeoutMs));
|
|
55176
55288
|
}
|
|
55177
55289
|
|
|
55178
55290
|
// src/command.js
|
|
@@ -55189,7 +55301,8 @@ var Devinp = class _Devinp extends import_core.Command {
|
|
|
55189
55301
|
"<%= config.bin %> -o=input",
|
|
55190
55302
|
"<%= config.bin %> -o=bench -g=family",
|
|
55191
55303
|
"<%= config.bin %> --web --port 8080",
|
|
55192
|
-
"<%= config.bin %> --source sample/models.json"
|
|
55304
|
+
"<%= config.bin %> --source sample/models.json",
|
|
55305
|
+
"devin models list --format json | <%= config.bin %> --source=-"
|
|
55193
55306
|
];
|
|
55194
55307
|
static flags = {
|
|
55195
55308
|
order: import_core.Flags.string({
|
|
@@ -55214,7 +55327,7 @@ var Devinp = class _Devinp extends import_core.Command {
|
|
|
55214
55327
|
dependsOn: ["web"]
|
|
55215
55328
|
}),
|
|
55216
55329
|
source: import_core.Flags.string({
|
|
55217
|
-
summary: "load prices from a saved `devin models list --format json` file"
|
|
55330
|
+
summary: "load prices from a saved `devin models list --format json` file, or from stdin with --source=- (use it when the CLI pages its output instead of writing it to the pipe)"
|
|
55218
55331
|
}),
|
|
55219
55332
|
json: import_core.Flags.boolean({
|
|
55220
55333
|
summary: "print normalized rows as JSON instead of a table",
|
|
@@ -55229,6 +55342,9 @@ var Devinp = class _Devinp extends import_core.Command {
|
|
|
55229
55342
|
if (loaded.truncated) {
|
|
55230
55343
|
this.warn("devin output was truncated mid-stream \u2014 showing partial data, re-run for the full list");
|
|
55231
55344
|
}
|
|
55345
|
+
if (loaded.retries > 0) {
|
|
55346
|
+
this.warn(`devin needed ${loaded.retries + 1} attempts to hand over the model list \u2014 something on this machine (pager, paged "show more" view, wrapper) interferes with \`devin models list\``);
|
|
55347
|
+
}
|
|
55232
55348
|
data = loaded.data;
|
|
55233
55349
|
} catch (error) {
|
|
55234
55350
|
this.error(error instanceof Error ? error.message : String(error), { exit: 1 });
|