@lucasschirm/devinp 0.1.11 → 0.1.13

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 +36 -10
  2. package/bin/command.js +11 -9
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -34,11 +34,21 @@ invalid JSON (… 0 bytes)"*. It now works harder to get the whole document:
34
34
  - Prices are captured through a temp file in `os.tmpdir()` — a pipe stream from
35
35
  `devin` can be truncated mid-write, so `devinp` points `devin`'s stdout at a
36
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.
37
+ - **Retries that take the terminal away from `devin`.** If an attempt yields no
38
+ usable JSON, `devinp` runs `devin` again, each time stripping more of the
39
+ interactive terminal off it:
40
+
41
+ | # | stdout goes to | environment | controlling terminal |
42
+ | - | ------------ | -------------------------------------------------------- | --------------------- |
43
+ | 1 | temp file | yours, untouched | yes — the healthy path, one `devin` call |
44
+ | 2 | temp file | `PAGER=cat`, `GIT_PAGER=cat`, `TERM=dumb`, `NO_COLOR=1`, `CI=1` | **none** |
45
+ | 3 | pipe | same | **none** |
46
+
47
+ Spawned in its own session, `devin` cannot open `/dev/tty`: a pager or a TUI
48
+ that paints a "click to show more" view onto the terminal has nowhere to
49
+ draw, so the document has to come out of stdout. Nothing lands on the screen
50
+ you are typing in, and Ctrl-C there no longer kills the lookup. The first
51
+ attempt that parses wins, so the healthy path is unchanged.
42
52
  - Noise around the document is tolerated: ANSI colours, an update banner in
43
53
  front, or the `pass a slug to --model` hint behind are stripped before the
44
54
  JSON is parsed.
@@ -46,9 +56,11 @@ invalid JSON (… 0 bytes)"*. It now works harder to get the whole document:
46
56
  prints a warning, and renders the partial table.
47
57
  - A real CLI failure (not signed in, network down) is reported as-is instead of
48
58
  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:
59
+ - If nothing worked, the error lists all three attempts — bytes, exit code,
60
+ stderr, timeout — and how to feed the data in yourself. That always works,
61
+ because a redirect or a pipe makes `devin` write the whole document in one go:
62
+ (and if it does not, `command -v devin` tells you whether devinp is even
63
+ talking to the CLI and not to a shell function or agent wrapper around it).
52
64
 
53
65
  ```sh
54
66
  devin models list --format json > models.json && devinp --source models.json
@@ -125,11 +137,25 @@ so `npm publish`, git-URL installs, and `npm link` always ship a fresh bundle.
125
137
 
126
138
  ## Publishing
127
139
 
128
- `.github/workflows/publish.yml` bumps the patch version and publishes with
129
- [npm trusted publishing](https://docs.npmjs.com/trusted-publishers) (OIDC —
140
+ `.github/workflows/publish.yml` publishes a patch release on every push to main
141
+ with [npm trusted publishing](https://docs.npmjs.org/trusted-publishers) (OIDC —
130
142
  no `NPM_TOKEN`). Requires the trusted publisher to be configured on npmjs.com:
131
143
  repository `lucasschirm/devinp`, workflow `publish.yml`.
132
144
 
145
+ It **never commits to main** — the branch ruleset requires every change to
146
+ arrive through a pull request, and a rejected release commit used to strand the
147
+ publish step that ran after it (GH013, then `npm publish` skipped). The released
148
+ version therefore lives in the npm registry and in the git tag, not in main's
149
+ `package.json`:
150
+
151
+ ```sh
152
+ git ls-remote --tags origin # released versions
153
+ npm view @lucasschirm/devinp version # latest published
154
+ ```
155
+
156
+ The workflow takes the first patch number that is free in both places, so a tag
157
+ left behind by an older run cannot wedge it.
158
+
133
159
  ## License
134
160
 
135
161
  MIT
package/bin/command.js CHANGED
@@ -55095,10 +55095,11 @@ var NON_INTERACTIVE_ENV = {
55095
55095
  NO_COLOR: "1",
55096
55096
  CI: "1"
55097
55097
  };
55098
+ var DETACHED_SPAWN = { detached: true, windowsHide: true };
55098
55099
  var STRATEGIES = [
55099
55100
  { 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 }
55101
+ { label: "temp file, paging disabled, no controlling terminal", file: true, env: NON_INTERACTIVE_ENV, detached: true },
55102
+ { label: "pipe, paging disabled, no controlling terminal", file: false, env: NON_INTERACTIVE_ENV, detached: true }
55102
55103
  ];
55103
55104
  var ANSI = /\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][\s\S]*?(?:\u0007|\u001b\\)|\u001b[@-Z\\-_]/g;
55104
55105
  function stripAnsi(text) {
@@ -55120,11 +55121,12 @@ function parseJsonLoosely(text) {
55120
55121
  }
55121
55122
  return null;
55122
55123
  }
55123
- function spawnDevin({ command, env, stdout, timeoutMs }) {
55124
+ function spawnDevin({ command, env, stdout, timeoutMs, detached = false }) {
55124
55125
  return new Promise((resolve, reject) => {
55125
55126
  const child = spawn(command, ["models", "list", "--format", "json"], {
55126
55127
  stdio: ["ignore", stdout, "pipe"],
55127
- ...env ? { env: { ...process.env, ...env } } : {}
55128
+ ...env ? { env: { ...process.env, ...env } } : {},
55129
+ ...detached ? DETACHED_SPAWN : {}
55128
55130
  });
55129
55131
  let stdoutText = "";
55130
55132
  let stderr = "";
@@ -55149,11 +55151,11 @@ function spawnDevin({ command, env, stdout, timeoutMs }) {
55149
55151
  });
55150
55152
  });
55151
55153
  }
55152
- async function captureToFile({ command, env, timeoutMs }) {
55154
+ async function captureToFile({ command, env, timeoutMs, detached }) {
55153
55155
  const file = join2(tmpdir(), `devinp-models-${process.pid}-${Date.now()}.json`);
55154
55156
  const handle = await open(file, "w");
55155
55157
  try {
55156
- const result = await spawnDevin({ command, env, stdout: handle.fd, timeoutMs });
55158
+ const result = await spawnDevin({ command, env, stdout: handle.fd, timeoutMs, detached });
55157
55159
  result.stdout = await readFile2(file, "utf8");
55158
55160
  return result;
55159
55161
  } finally {
@@ -55167,7 +55169,7 @@ async function runDevinJson({ command = "devin", timeoutMs = DEVIN_TIMEOUT_MS }
55167
55169
  for (const [index, strategy] of STRATEGIES.entries()) {
55168
55170
  let result;
55169
55171
  try {
55170
- result = strategy.file ? await captureToFile({ command, env: strategy.env, timeoutMs }) : await spawnDevin({ command, env: strategy.env, stdout: "pipe", timeoutMs });
55172
+ result = strategy.file ? await captureToFile({ command, env: strategy.env, timeoutMs, detached: strategy.detached }) : await spawnDevin({ command, env: strategy.env, stdout: "pipe", timeoutMs, detached: strategy.detached });
55171
55173
  } catch (error) {
55172
55174
  if (error.code === "ENOENT") throw error;
55173
55175
  attempts.push({ label: strategy.label, bytes: 0, code: null, stderr: "", timedOut: false, note: error.message });
@@ -55222,7 +55224,7 @@ function describeFailure(attempts, timeoutMs) {
55222
55224
  const lines = [];
55223
55225
  if (bytes === 0) {
55224
55226
  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.'
55227
+ 'Unable to get model prices: `devin models list --format json` exited without writing the JSON to stdout (0 bytes). devinp re-ran it with paging disabled and in a session with no controlling terminal, so the CLI had no terminal to paint a "click to show more" view on \u2014 whatever swallowed the output sits between devinp and `devin` (a shell function or agent wrapper around it, or a broken install).'
55226
55228
  );
55227
55229
  } else {
55228
55230
  let parseError = "";
@@ -55239,7 +55241,7 @@ function describeFailure(attempts, timeoutMs) {
55239
55241
  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
55242
  lines.push(" devin models list --format json > models.json && devinp --source models.json");
55241
55243
  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`).");
55244
+ lines.push("Check `command -v devin` \u2014 if that is not the real CLI, devinp is asking the wrong program. If a plain redirect still prints a paged view, update the CLI (`devin update`).");
55243
55245
  return lines.join("\n");
55244
55246
  }
55245
55247
  async function readStdin() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lucasschirm/devinp",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Pretty pricing table for Devin models, powered by `devin models list`",
5
5
  "type": "module",
6
6
  "bin": {