@liustack/modlens 3.1.0 → 3.1.1
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/CHANGELOG.md +9 -0
- package/dist/main.js +27310 -63
- package/docs/security.md +3 -1
- package/package.json +3 -2
- package/skills/modlens/SKILL.md +6 -5
- package/skills/modlens/references/runtime.md +17 -7
- package/skills/modlens/scripts/run.ps1 +34 -5
- package/skills/modlens/scripts/run.sh +37 -5
package/docs/security.md
CHANGED
|
@@ -20,7 +20,9 @@ ModLens invokes `agy` with `--dangerously-skip-permissions` because prompt mode
|
|
|
20
20
|
|
|
21
21
|
The `claude-cli` provider runs with `--allowedTools Read` only, so it can read local files and nothing else.
|
|
22
22
|
|
|
23
|
-
Both subprocess providers also run in a throwaway directory
|
|
23
|
+
Both subprocess providers also run in a throwaway directory created fresh per call and removed afterward. For a local image it holds a private copy of that one image and nothing else, and it is a real copy, never a hardlink, so a provider writing to its temp path cannot touch the original. For a remote image the directory is empty and the agent downloads into it. Without this, text inside an image could steer a broadly-permissioned agent into reading files next to the original, or whatever project the caller happened to be in. Passing `--workdir` opts out and runs where you point it.
|
|
24
|
+
|
|
25
|
+
This is exposure reduction, not an OS sandbox: the agent can still read absolute paths, reach the network, and spawn processes. Treat it as a narrower default, not a security boundary. For images you do not trust, prefer an inline API provider (`-p gemini-api`), which hands the bytes to an HTTP endpoint and runs no local agent.
|
|
24
26
|
|
|
25
27
|
## Image content is untrusted input
|
|
26
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liustack/modlens",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Plug-in vision for text-only LLMs, powered by the free Antigravity CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -54,7 +54,8 @@
|
|
|
54
54
|
"node": ">=22.13"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"commander": "^13.1.0"
|
|
57
|
+
"commander": "^13.1.0",
|
|
58
|
+
"undici": "^8.10.0"
|
|
58
59
|
},
|
|
59
60
|
"devDependencies": {
|
|
60
61
|
"@biomejs/biome": "^2.5.7",
|
package/skills/modlens/SKILL.md
CHANGED
|
@@ -33,11 +33,11 @@ The launcher finds a working way to run modlens and forwards your arguments to i
|
|
|
33
33
|
|
|
34
34
|
### If you cannot run the launcher script
|
|
35
35
|
|
|
36
|
-
Some harnesses forbid running scripts. Reason through the same order by hand and run the first line that works (the pinned version is 3.
|
|
36
|
+
Some harnesses forbid running scripts. Reason through the same order by hand and run the first line that works (the pinned version is 3.1.1):
|
|
37
37
|
|
|
38
|
-
1. A `modlens` on `PATH` whose major version is 3 and is at least 3.
|
|
39
|
-
2. Otherwise, if `npx` exists: `npx --yes --package @liustack/modlens@3.
|
|
40
|
-
3. Otherwise, if `bunx` exists: `bunx --bun @liustack/modlens@3.
|
|
38
|
+
1. A `modlens` on `PATH` whose major version is 3 and is at least 3.1.1: `modlens <args>`.
|
|
39
|
+
2. Otherwise, if `npx` exists: `npx --yes --package @liustack/modlens@3.1.1 modlens <args>`.
|
|
40
|
+
3. Otherwise, if `bunx` exists: `bunx --bun @liustack/modlens@3.1.1 <args>`.
|
|
41
41
|
4. Otherwise none of these runtimes is here. Tell the user no JavaScript runtime was found and that installing Node 22.13+ (https://nodejs.org) or Bun (https://bun.sh) is the next step. Do not claim modlens itself failed.
|
|
42
42
|
|
|
43
43
|
`references/runtime.md` documents the version pin, the compatibility rule, and the diagnostic fields.
|
|
@@ -84,9 +84,10 @@ Harnesses rarely hand you a clean path. First identify which harness you are in,
|
|
|
84
84
|
|
|
85
85
|
**Claude Code, Pi, or OpenCode** (no path tag anywhere; the image reads as `[Unsupported Image]`, a bare `[Image #1]`, or an attachment you simply cannot see):
|
|
86
86
|
|
|
87
|
-
- None of these harnesses writes pasted images to a regular temp file, but all of them persist user messages locally before any gateway strips them: Claude Code and Pi in session JSONL files (`~/.claude/projects/`, `~/.pi/agent/sessions/`), OpenCode in a SQLite database (`~/.local/share/opencode/opencode.db`, read via node:sqlite, needs Node 22.5
|
|
87
|
+
- None of these harnesses writes pasted images to a regular temp file, but all of them persist user messages locally before any gateway strips them: Claude Code and Pi in session JSONL files (`~/.claude/projects/`, `~/.pi/agent/sessions/`), OpenCode in a SQLite database (`~/.local/share/opencode/opencode.db`, read via node:sqlite, needs Node 22.5+; Bun cannot load node:sqlite, so if the launcher resolved to bunx, OpenCode recovery needs a real Node install). Run `modlens recover-paste` from the project directory the conversation is happening in (add `--count <n>` for several images). It detects which harness it is running inside (process ancestry, then env fingerprints) and reads ONLY that harness's storage, so another tool's old sessions cannot leak in. In Claude Code it also targets your exact session automatically via the injected CLAUDE_CODE_SESSION_ID; `--session <id>` (e.g. from the ${CLAUDE_SESSION_ID} substitution) is only needed to override.
|
|
88
88
|
- The output is JSON with real file paths, ordered oldest to newest, so the LAST path is the user's most recent paste. Analyze that one first. Entries carry `filename` (the original attachment name) when the harness stored one; if the user's message or an error mentions a filename, match on it.
|
|
89
89
|
- Run every command yourself: `recover-paste`, then `modlens -i <path>` on the recovered file, then answer from the JSON. Never ask the user to run modlens or to relay paths.
|
|
90
|
+
- When the analysis is done, delete the recovered files: they are private copies of the user's pasted images sitting in the temp dir, and nothing cleans them up until the OS does. Remove the recovery output directory (each entry's `path` sits inside it), unless the user asked to keep the files.
|
|
90
91
|
- The output's `detected` field names the harness scope that was applied. If it is absent, detection failed and every store was scanned by newest-image timestamp: before describing anything, check that `harness` and `filename` match what you expect, force the scope with `--harness <claude-code|pi|opencode>` if they do not, and when in doubt ask the user for the file instead of describing the wrong image.
|
|
91
92
|
- Recovery is scoped to this project: the harness's own record of its working directory is checked, not just the directory name, so images from a neighbouring project are never handed over. Recovered files are private to the user (0600).
|
|
92
93
|
- If recovery fails (session storage is each harness's internals and may change), ask the user to drag the image file into the terminal or type its path.
|
|
@@ -8,7 +8,7 @@ shell syntax.
|
|
|
8
8
|
|
|
9
9
|
## Pinned version
|
|
10
10
|
|
|
11
|
-
- Pinned CLI version: 3.1.
|
|
11
|
+
- Pinned CLI version: 3.1.1
|
|
12
12
|
- npm package: `@liustack/modlens`
|
|
13
13
|
- CLI binary name: `modlens`
|
|
14
14
|
|
|
@@ -22,7 +22,7 @@ launcher/reference copies ever drift from `package.json`.
|
|
|
22
22
|
Each call resolves a way to run the CLI, in this order:
|
|
23
23
|
|
|
24
24
|
1. **A compatible `modlens` already on `PATH`** — run it directly, by name.
|
|
25
|
-
2. **`npx` present** — `npx --yes --package @liustack/modlens@<pinned> modlens <args>`.
|
|
25
|
+
2. **`npx` present, and `node` meets the CLI's 22.13 floor** — `npx --yes --package @liustack/modlens@<pinned> modlens <args>`. An npx sitting on an older node is skipped: it would select a path known to fail at run time.
|
|
26
26
|
3. **`bunx` present** — `bunx --bun @liustack/modlens@<pinned> <args>`.
|
|
27
27
|
4. **A native artifact** — reserved for phase B. None is published yet, so this
|
|
28
28
|
branch reports `nativeArtifact.available: false` and moves on.
|
|
@@ -43,7 +43,9 @@ instead.
|
|
|
43
43
|
|
|
44
44
|
## Cache and permissions (phase B, not active yet)
|
|
45
45
|
|
|
46
|
-
Phase A
|
|
46
|
+
Phase A ships no native artifact. The `npx` and `bunx` paths fetch the pinned
|
|
47
|
+
npm package on first use and cache it (that is how those runners work); nothing
|
|
48
|
+
else is ever downloaded. When native artifacts land in phase B, the
|
|
47
49
|
launchers will cache them per user, keyed by version, and launch them by
|
|
48
50
|
absolute path:
|
|
49
51
|
|
|
@@ -67,7 +69,9 @@ would have set.
|
|
|
67
69
|
`arm64` / `x64`).
|
|
68
70
|
- `checked.pathCli` — `{ present, path, version, compatible }` for a `modlens`
|
|
69
71
|
on `PATH`, with `compatible` applying the rule above.
|
|
70
|
-
- `checked.npx
|
|
72
|
+
- `checked.npx` — `{ present, path, nodeMeetsFloor }`; `nodeMeetsFloor` is whether
|
|
73
|
+
the local node satisfies the CLI's 22.13 floor, required for the npx path.
|
|
74
|
+
- `checked.bunx` — `{ present, path }`.
|
|
71
75
|
- `checked.node` — `{ present, version }`.
|
|
72
76
|
- `nativeArtifact` — `{ available, note }`; `available` is `false` in phase A.
|
|
73
77
|
- `selected` — the resolved path: `path`, `npx`, `bunx`, or `none`.
|
|
@@ -76,9 +80,15 @@ would have set.
|
|
|
76
80
|
- `cliDoctor` — when a CLI is resolvable, the CLI's own `doctor --json` report
|
|
77
81
|
(provider, config, and harness diagnosis) is nested here; `null` otherwise.
|
|
78
82
|
|
|
79
|
-
`doctor`
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
`doctor` spends no quota. The launcher's own diagnosis is offline: it inspects
|
|
84
|
+
the local environment and makes no network request of its own. Chaining the
|
|
85
|
+
CLI's `doctor` through the npx or bunx path can download the pinned package the
|
|
86
|
+
first time (that is how those runners work); after that it is served from the
|
|
87
|
+
local cache.
|
|
88
|
+
|
|
89
|
+
One capability note for the bunx path: Bun cannot load `node:sqlite`, which
|
|
90
|
+
OpenCode paste recovery needs, so on a machine where the launcher resolved to
|
|
91
|
+
bunx, `recover-paste` for OpenCode requires installing Node 22.13+ instead.
|
|
82
92
|
|
|
83
93
|
## Delivery form: local CLI, long term
|
|
84
94
|
|
|
@@ -24,7 +24,7 @@ $ErrorActionPreference = 'Stop'
|
|
|
24
24
|
# package.json version, and the release script rewrites it on every bump.
|
|
25
25
|
$Package = '@liustack/modlens'
|
|
26
26
|
$Bin = 'modlens'
|
|
27
|
-
$Pinned = '3.1.
|
|
27
|
+
$Pinned = '3.1.1'
|
|
28
28
|
# -------------------------------------------------------------------------------
|
|
29
29
|
|
|
30
30
|
$NativeNote = 'no native artifact is published for this tool yet; phase A ships npm launch paths only'
|
|
@@ -41,6 +41,7 @@ $script:BunxPresent = $false
|
|
|
41
41
|
$script:BunxPath = $null
|
|
42
42
|
$script:NodePresent = $false
|
|
43
43
|
$script:NodeVer = $null
|
|
44
|
+
$script:NodeFloorOk = $false
|
|
44
45
|
$script:Selected = 'none'
|
|
45
46
|
|
|
46
47
|
# First "X.Y.Z" token printed by `$Bin --version`.
|
|
@@ -68,6 +69,24 @@ function Test-Compatible {
|
|
|
68
69
|
return ($fPat -ge $pPat)
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
# The npx path runs the CLI on this machine's node, so npx is only usable when
|
|
73
|
+
# node itself meets the CLI's floor. An old node with a working npx used to be
|
|
74
|
+
# selected anyway, a path known to fail at run time.
|
|
75
|
+
$NodeFloor = '22.13.0'
|
|
76
|
+
function Test-NodeMeetsFloor {
|
|
77
|
+
if (-not (Get-Command node -ErrorAction SilentlyContinue)) { return $false }
|
|
78
|
+
try { $nv = ((& node --version 2>$null) -replace '^v', '') } catch { return $false }
|
|
79
|
+
if (-not $nv) { return $false }
|
|
80
|
+
$n = $nv -split '\.'
|
|
81
|
+
$f = $NodeFloor -split '\.'
|
|
82
|
+
if ($n.Count -lt 2) { return $false }
|
|
83
|
+
$nMaj = [int]$n[0]; $nMin = [int]$n[1]
|
|
84
|
+
$fMaj = [int]$f[0]; $fMin = [int]$f[1]
|
|
85
|
+
if ($nMaj -gt $fMaj) { return $true }
|
|
86
|
+
if ($nMaj -lt $fMaj) { return $false }
|
|
87
|
+
return ($nMin -ge $fMin)
|
|
88
|
+
}
|
|
89
|
+
|
|
71
90
|
# Return exactly one word: the chosen launch path.
|
|
72
91
|
function Resolve-LaunchKind {
|
|
73
92
|
$cli = Get-Command $Bin -ErrorAction SilentlyContinue
|
|
@@ -75,7 +94,7 @@ function Resolve-LaunchKind {
|
|
|
75
94
|
$v = Get-CliVersion
|
|
76
95
|
if ($v -and (Test-Compatible $v)) { return 'path' }
|
|
77
96
|
}
|
|
78
|
-
if (Get-Command npx -ErrorAction SilentlyContinue) { return 'npx' }
|
|
97
|
+
if ((Get-Command npx -ErrorAction SilentlyContinue) -and (Test-NodeMeetsFloor)) { return 'npx' }
|
|
79
98
|
if (Get-Command bunx -ErrorAction SilentlyContinue) { return 'bunx' }
|
|
80
99
|
# Phase B goes here: check a versioned user cache, then download and verify a
|
|
81
100
|
# native artifact into it. Any such download must use curl.exe (written in
|
|
@@ -127,6 +146,7 @@ function Collect {
|
|
|
127
146
|
if ($node) {
|
|
128
147
|
$script:NodePresent = $true
|
|
129
148
|
try { $script:NodeVer = ((& node --version 2>$null) -replace '^v', '') } catch { $script:NodeVer = $null }
|
|
149
|
+
$script:NodeFloorOk = Test-NodeMeetsFloor
|
|
130
150
|
}
|
|
131
151
|
|
|
132
152
|
$script:Selected = Resolve-LaunchKind
|
|
@@ -138,15 +158,19 @@ function Build-DiagnosisJson {
|
|
|
138
158
|
param($Chained)
|
|
139
159
|
$checked = [ordered]@{
|
|
140
160
|
pathCli = [ordered]@{ present = $script:CliPresent; path = $script:CliPath; version = $script:CliVer; compatible = $script:CliCompat }
|
|
141
|
-
npx = [ordered]@{ present = $script:NpxPresent; path = $script:NpxPath }
|
|
161
|
+
npx = [ordered]@{ present = $script:NpxPresent; path = $script:NpxPath; nodeMeetsFloor = $script:NodeFloorOk }
|
|
142
162
|
bunx = [ordered]@{ present = $script:BunxPresent; path = $script:BunxPath }
|
|
143
163
|
node = [ordered]@{ present = $script:NodePresent; version = $script:NodeVer }
|
|
144
164
|
}
|
|
145
165
|
$steps = @()
|
|
146
166
|
if ($script:Selected -eq 'none') {
|
|
147
167
|
$major = $Pinned.Split('.')[0]
|
|
168
|
+
$first = "Install Node 22.13+ from https://nodejs.org so npx can run $Package@$Pinned, then re-run this launcher."
|
|
169
|
+
if ($script:NpxPresent -and (-not $script:NodeFloorOk)) {
|
|
170
|
+
$first = "npx is present but node $(if ($script:NodeVer) { $script:NodeVer } else { 'missing' }) is below the $NodeFloor floor this CLI needs. Upgrade Node at https://nodejs.org, then re-run this launcher."
|
|
171
|
+
}
|
|
148
172
|
$steps = @(
|
|
149
|
-
|
|
173
|
+
$first,
|
|
150
174
|
"No JavaScript runtime? Install Bun from https://bun.sh to use bunx, or put a compatible $Bin (major $major, at or above $Pinned) on PATH."
|
|
151
175
|
)
|
|
152
176
|
}
|
|
@@ -178,7 +202,12 @@ function Write-DiagnosisText {
|
|
|
178
202
|
else {
|
|
179
203
|
Write-Output (" {0} on PATH: no" -f $Bin)
|
|
180
204
|
}
|
|
181
|
-
|
|
205
|
+
$npxDesc = 'no'
|
|
206
|
+
if ($script:NpxPresent) {
|
|
207
|
+
if ($script:NodeFloorOk) { $npxDesc = $script:NpxPath }
|
|
208
|
+
else { $npxDesc = "$($script:NpxPath) (unusable: node $(if ($script:NodeVer) { $script:NodeVer } else { 'missing' }) is below $NodeFloor)" }
|
|
209
|
+
}
|
|
210
|
+
Write-Output (" npx: {0}" -f $npxDesc)
|
|
182
211
|
Write-Output (" bunx: {0}" -f $(if ($script:BunxPresent) { $script:BunxPath } else { 'no' }))
|
|
183
212
|
Write-Output (" node: {0}" -f $(if ($script:NodePresent) { $script:NodeVer } else { 'no' }))
|
|
184
213
|
Write-Output (" selected path: {0}" -f $script:Selected)
|
|
@@ -22,7 +22,7 @@ set -eu
|
|
|
22
22
|
# package.json version, and the release script rewrites it on every bump.
|
|
23
23
|
PKG="@liustack/modlens"
|
|
24
24
|
BIN="modlens"
|
|
25
|
-
PINNED="3.1.
|
|
25
|
+
PINNED="3.1.1"
|
|
26
26
|
# -------------------------------------------------------------------------------
|
|
27
27
|
|
|
28
28
|
NATIVE_NOTE="no native artifact is published for this tool yet; phase A ships npm launch paths only"
|
|
@@ -67,6 +67,23 @@ cli_version() {
|
|
|
67
67
|
sed -n 's/.*\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/p'
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
# The npx path runs the CLI on this machine's node, so npx is only usable when
|
|
71
|
+
# node itself meets the CLI's floor. An old node with a working npx used to be
|
|
72
|
+
# selected anyway, a path known to fail at run time.
|
|
73
|
+
NODE_FLOOR="22.13.0"
|
|
74
|
+
node_meets_floor() {
|
|
75
|
+
command -v node >/dev/null 2>&1 || return 1
|
|
76
|
+
_nv="$(node --version 2>/dev/null | sed 's/^v//')"
|
|
77
|
+
[ -n "$_nv" ] || return 1
|
|
78
|
+
parse_semver "$NODE_FLOOR"
|
|
79
|
+
_floor_maj="$_MAJ"
|
|
80
|
+
_floor_min="$_MIN"
|
|
81
|
+
parse_semver "$_nv"
|
|
82
|
+
if [ "$_MAJ" -gt "$_floor_maj" ]; then return 0; fi
|
|
83
|
+
if [ "$_MAJ" -lt "$_floor_maj" ]; then return 1; fi
|
|
84
|
+
[ "$_MIN" -ge "$_floor_min" ]
|
|
85
|
+
}
|
|
86
|
+
|
|
70
87
|
# Echo exactly one word: the chosen launch path.
|
|
71
88
|
resolve() {
|
|
72
89
|
if command -v "$BIN" >/dev/null 2>&1; then
|
|
@@ -76,7 +93,7 @@ resolve() {
|
|
|
76
93
|
return
|
|
77
94
|
fi
|
|
78
95
|
fi
|
|
79
|
-
if command -v npx >/dev/null 2>&1; then
|
|
96
|
+
if command -v npx >/dev/null 2>&1 && node_meets_floor; then
|
|
80
97
|
echo "npx"
|
|
81
98
|
return
|
|
82
99
|
fi
|
|
@@ -160,13 +177,20 @@ collect() {
|
|
|
160
177
|
G_NODE_VER="$(node --version 2>/dev/null | sed 's/^v//')"
|
|
161
178
|
fi
|
|
162
179
|
|
|
180
|
+
G_NODE_FLOOR_OK=0
|
|
181
|
+
if node_meets_floor; then G_NODE_FLOOR_OK=1; fi
|
|
182
|
+
|
|
163
183
|
G_SEL="$(resolve)"
|
|
164
184
|
}
|
|
165
185
|
|
|
166
186
|
# Build the nextSteps JSON array body (without the brackets) into G_NEXTSTEPS.
|
|
167
187
|
compute_next_steps() {
|
|
168
188
|
if [ "$G_SEL" = "none" ]; then
|
|
169
|
-
|
|
189
|
+
if [ "$G_NPX_PRESENT" = 1 ] && [ "$G_NODE_FLOOR_OK" = 0 ]; then
|
|
190
|
+
_s1="npx is present but node ${G_NODE_VER:-missing} is below the $NODE_FLOOR floor this CLI needs. Upgrade Node at https://nodejs.org, then re-run this launcher."
|
|
191
|
+
else
|
|
192
|
+
_s1="Install Node 22.13+ from https://nodejs.org so npx can run $PKG@$PINNED, then re-run this launcher."
|
|
193
|
+
fi
|
|
170
194
|
_s2="No JavaScript runtime? Install Bun from https://bun.sh to use bunx, or put a compatible $BIN (major ${PINNED%%.*}, at or above $PINNED) on PATH."
|
|
171
195
|
G_NEXTSTEPS="$(printf '"%s", "%s"' "$(json_escape "$_s1")" "$(json_escape "$_s2")")"
|
|
172
196
|
else
|
|
@@ -187,7 +211,7 @@ emit_json() {
|
|
|
187
211
|
printf ' "checked": {\n'
|
|
188
212
|
printf ' "pathCli": { "present": %s, "path": %s, "version": %s, "compatible": %s },\n' \
|
|
189
213
|
"$(jbool "$G_CLI_PRESENT")" "$(jstr "$G_CLI_PATH")" "$(jstr "$G_CLI_VER")" "$(jbool "$G_CLI_COMPAT")"
|
|
190
|
-
printf ' "npx": { "present": %s, "path": %s },\n' "$(jbool "$G_NPX_PRESENT")" "$(jstr "$G_NPX_PATH")"
|
|
214
|
+
printf ' "npx": { "present": %s, "path": %s, "nodeMeetsFloor": %s },\n' "$(jbool "$G_NPX_PRESENT")" "$(jstr "$G_NPX_PATH")" "$(jbool "$G_NODE_FLOOR_OK")"
|
|
191
215
|
printf ' "bunx": { "present": %s, "path": %s },\n' "$(jbool "$G_BUNX_PRESENT")" "$(jstr "$G_BUNX_PATH")"
|
|
192
216
|
printf ' "node": { "present": %s, "version": %s }\n' "$(jbool "$G_NODE_PRESENT")" "$(jstr "$G_NODE_VER")"
|
|
193
217
|
printf ' },\n'
|
|
@@ -216,7 +240,15 @@ emit_text() {
|
|
|
216
240
|
else
|
|
217
241
|
printf ' %s on PATH: no\n' "$BIN"
|
|
218
242
|
fi
|
|
219
|
-
|
|
243
|
+
_npx_desc="no"
|
|
244
|
+
if [ "$G_NPX_PRESENT" = 1 ]; then
|
|
245
|
+
if [ "$G_NODE_FLOOR_OK" = 1 ]; then
|
|
246
|
+
_npx_desc="$G_NPX_PATH"
|
|
247
|
+
else
|
|
248
|
+
_npx_desc="$G_NPX_PATH (unusable: node ${G_NODE_VER:-missing} is below $NODE_FLOOR)"
|
|
249
|
+
fi
|
|
250
|
+
fi
|
|
251
|
+
printf ' npx: %s\n' "$_npx_desc"
|
|
220
252
|
printf ' bunx: %s\n' "$([ "$G_BUNX_PRESENT" = 1 ] && echo "$G_BUNX_PATH" || echo no)"
|
|
221
253
|
printf ' node: %s\n' "$([ "$G_NODE_PRESENT" = 1 ] && echo "${G_NODE_VER:-yes}" || echo no)"
|
|
222
254
|
printf ' selected path: %s\n' "$G_SEL"
|