agentsw 0.7.1 → 0.7.2
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 +30 -11
- package/README.zh-CN.md +26 -11
- package/dist/apps.js +48 -44
- package/dist/commands.js +221 -74
- package/dist/config-transaction.js +169 -84
- package/dist/discover.js +55 -17
- package/dist/envfile.js +101 -20
- package/dist/fsutil.js +109 -14
- package/dist/i18n.js +2 -1
- package/dist/import.js +2 -2
- package/dist/index.js +16 -1
- package/dist/jsonc.js +4 -10
- package/dist/modelsdev.js +7 -3
- package/dist/provider-identity.js +25 -0
- package/dist/remove.js +84 -38
- package/dist/rename.js +91 -58
- package/dist/store.js +38 -7
- package/dist/target-transaction.js +25 -0
- package/dist/targets/claudecode.js +12 -3
- package/dist/targets/codex.js +34 -3
- package/dist/targets/dsh.js +117 -65
- package/dist/targets/hermes.js +98 -49
- package/dist/targets/omp.js +11 -47
- package/dist/targets/opencode.js +27 -6
- package/dist/targets/pistyle.js +12 -8
- package/dist/targets/wire.js +15 -9
- package/dist/targets/workbuddy.js +151 -76
- package/dist/yaml.js +39 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ switching to vfing (openai) · default model glm-5.3-flash
|
|
|
23
23
|
|
|
24
24
|
skip claude Claude Code does not support openai-protocol providers
|
|
25
25
|
ok codex ~/.codex/config.toml, ~/.codex/auth.json
|
|
26
|
-
backup: ~/.config/agentsw/backups/
|
|
26
|
+
backup: ~/.config/agentsw/backups/transaction-<unique>
|
|
27
27
|
ok omp ~/.omp/agent/models.yml
|
|
28
28
|
select in omp with: omp --model vfing/glm-5.3-flash
|
|
29
29
|
skip pi pi not detected (pass --apps pi to force)
|
|
@@ -39,8 +39,8 @@ ok dsh ~/.dsh/settings.yaml, ~/.dsh/.credentials.yaml
|
|
|
39
39
|
- **It runs where a desktop app cannot.** Headless servers, containers, devcontainers, CI.
|
|
40
40
|
`npx agentsw use myproxy -a codex,omp` is one line in a provisioning script; nine GUI
|
|
41
41
|
clicks are not.
|
|
42
|
-
- **`--dry-run` prints diffs.**
|
|
43
|
-
|
|
42
|
+
- **`--dry-run` prints redacted diffs.** Preview changes without writing files or creating
|
|
43
|
+
backups. Existing configuration files are backed up when actual changes are committed.
|
|
44
44
|
- **Non-destructive by design.** Only the fields agentsw owns are rewritten. Provider-level
|
|
45
45
|
keys it does not model — `authHeader`, `headers`, `compat`, `discovery` — per-model extras
|
|
46
46
|
like `thinkingLevelMap`, and YAML comments all survive a re-sync.
|
|
@@ -50,7 +50,7 @@ ok dsh ~/.dsh/settings.yaml, ~/.dsh/.credentials.yaml
|
|
|
50
50
|
`contextWindow`/`maxTokens` for omp, `reasoningEfforts`/`input` for dsh.
|
|
51
51
|
- **Adopts what you already have** — including [cc-switch](https://github.com/farion1231/cc-switch).
|
|
52
52
|
`import` scans every installed agent *and* cc-switch's own store, merges duplicates
|
|
53
|
-
(same protocol +
|
|
53
|
+
(same protocol + normalized endpoint + credentials), and never writes back to either.
|
|
54
54
|
- **Wire-aware.** `/v1/chat/completions` and `/v1/responses` are different endpoints;
|
|
55
55
|
agentsw tracks which one a provider speaks and never downgrades a working one.
|
|
56
56
|
|
|
@@ -62,7 +62,7 @@ imports from it rather than competing with it.
|
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
64
|
npx agentsw # zero install: opens the interactive menu
|
|
65
|
-
npm install -g agentsw # requires Node >= 22.
|
|
65
|
+
npm install -g agentsw # requires Node >= 22.13 (built-in SQLite without an extra flag)
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
The provider sync and config management work on Linux, macOS, and Windows. On Windows,
|
|
@@ -139,7 +139,9 @@ imported sub · openai · https://new.vfing.de/v1 · 18 models [from omp]
|
|
|
139
139
|
imported zhipu-glm · anthropic · https://open.bigmodel.cn/api/anthropic · 1 models [from cc-switch]
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
In an interactive terminal, omitting `--all` opens a multi-select preview before provider
|
|
143
|
+
records are saved. `--all`, or non-interactive stdin, imports all eligible new entries;
|
|
144
|
+
use `--all` explicitly in scripts. Model discovery may update the local metadata cache.
|
|
143
145
|
|
|
144
146
|
## Model discovery filters
|
|
145
147
|
|
|
@@ -204,8 +206,18 @@ sync of a managed provider can add it back to that app.
|
|
|
204
206
|
|
|
205
207
|
## How your configs are treated
|
|
206
208
|
|
|
207
|
-
-
|
|
208
|
-
|
|
209
|
+
- Existing provider-store and agent configuration files are backed up before committed
|
|
210
|
+
changes to `~/.config/agentsw/backups/transaction-*`. New files have no prior copy;
|
|
211
|
+
cache refreshes are not configuration backups. Dry runs create neither files nor backups.
|
|
212
|
+
- Each adapter stages all its files before committing; failed writes trigger best-effort
|
|
213
|
+
rollback. A multi-agent sync is not one filesystem-wide transaction: successful agents
|
|
214
|
+
remain updated if another fails, and failures are reported with a nonzero exit status.
|
|
215
|
+
- The provider store lives at `~/.config/agentsw/config.json` (mode 0600). Concurrent stale
|
|
216
|
+
saves are rejected instead of overwriting newer records; retry after the other writer finishes.
|
|
217
|
+
- Existing file permissions are preserved and new configuration files default to 0600.
|
|
218
|
+
- A busy write lock reports its owner PID/time. If a writer crashed, stop all agentsw
|
|
219
|
+
writers and confirm the lock is abandoned before manually removing the reported
|
|
220
|
+
`.write.lock` file; active or unknown locks are never automatically stolen.
|
|
209
221
|
- A sync overwrites only the fields agentsw owns. Unmodeled provider-level keys and per-model
|
|
210
222
|
extras survive; an owned field that stops applying is cleared rather than left stale, and a
|
|
211
223
|
per-model `api`/`baseUrl` override that contradicts the route is dropped (it would silently
|
|
@@ -219,8 +231,10 @@ sync of a managed provider can add it back to that app.
|
|
|
219
231
|
- Codex only speaks the Responses API; a chat-completions-only endpoint cannot be used with
|
|
220
232
|
it, and the sync output says so.
|
|
221
233
|
- DeepSeek Harness keeps secrets out of `settings.yaml`: the route carries
|
|
222
|
-
`apiKeyEnv: AGENTSW_<ID>_API_KEY`, the key goes into `$DSH_HOME/.credentials.yaml`
|
|
234
|
+
`apiKeyEnv: AGENTSW_<ID>_<DIGEST>_API_KEY`, the key goes into `$DSH_HOME/.credentials.yaml`
|
|
223
235
|
(`refs:`, mode 0600). `$DSH_HOME` defaults to `~/.dsh`.
|
|
236
|
+
The digest distinguishes IDs such as `foo-bar` and `foo_bar`; legacy generated references
|
|
237
|
+
remain recognizable. External/custom references are not renamed or removed indiscriminately.
|
|
224
238
|
- cc-switch's database is opened read-only and never written to.
|
|
225
239
|
- The apps manager supports native Windows commands for Claude Code, Codex CLI, pi,
|
|
226
240
|
opencode, Hermes, and DeepSeek Harness. Oh My Pi and prime-agent still need their own
|
|
@@ -250,8 +264,13 @@ npm run dev -- ... # run from source via tsx
|
|
|
250
264
|
|
|
251
265
|
Each agent is one adapter in [`src/targets/`](./src/targets) implementing
|
|
252
266
|
`apply` / `prune` / `current` / `candidates`; read-only import sources live in
|
|
253
|
-
[`src/sources/`](./src/sources).
|
|
254
|
-
[`src/targets/index.ts`](./src/targets/index.ts)
|
|
267
|
+
[`src/sources/`](./src/sources). To add an app, register it in
|
|
268
|
+
[`src/targets/index.ts`](./src/targets/index.ts), wrap its implementation with
|
|
269
|
+
`transactionalTarget()`, and add its schemas/references to `rename.ts` and `remove.ts`.
|
|
270
|
+
Use shared credential, YAML/JSONC, URL and model helpers; add apply/prune/import,
|
|
271
|
+
rename/removal, malformed-input, dry-run and account-isolation tests. If installable,
|
|
272
|
+
also add its platform commands to `apps.ts`. WorkBuddy local deletion uses the stable
|
|
273
|
+
account selector shown by `list --apps workbuddy`, not an ambiguous hostname alone.
|
|
255
274
|
|
|
256
275
|
## License
|
|
257
276
|
|
package/README.zh-CN.md
CHANGED
|
@@ -23,7 +23,7 @@ switching to vfing (openai) · default model glm-5.3-flash
|
|
|
23
23
|
|
|
24
24
|
skip claude Claude Code does not support openai-protocol providers
|
|
25
25
|
ok codex ~/.codex/config.toml, ~/.codex/auth.json
|
|
26
|
-
backup: ~/.config/agentsw/backups/
|
|
26
|
+
backup: ~/.config/agentsw/backups/transaction-<unique>
|
|
27
27
|
ok omp ~/.omp/agent/models.yml
|
|
28
28
|
select in omp with: omp --model vfing/glm-5.3-flash
|
|
29
29
|
skip pi pi not detected (pass --apps pi to force)
|
|
@@ -38,8 +38,8 @@ ok dsh ~/.dsh/settings.yaml, ~/.dsh/.credentials.yaml
|
|
|
38
38
|
|
|
39
39
|
- **它能去桌面应用去不了的地方。** 无头服务器、容器、devcontainer、CI。
|
|
40
40
|
`npx agentsw use myproxy -a codex,omp` 在开机脚本里是一行;GUI 点九次不是。
|
|
41
|
-
- **`--dry-run`
|
|
42
|
-
|
|
41
|
+
- **`--dry-run` 输出脱敏 diff。** 写盘前先预览;预览不写文件、不创建备份。
|
|
42
|
+
正式提交变更时,已有配置文件才会先备份。
|
|
43
43
|
- **默认不破坏现有配置。** 只覆盖 agentsw 自己管理的字段:它不建模的供应商级键
|
|
44
44
|
(`authHeader`、`headers`、`compat`、`discovery`)、`thinkingLevelMap` 之类的模型级字段、
|
|
45
45
|
以及 YAML 注释,都会在重新同步后原样保留。
|
|
@@ -49,7 +49,7 @@ ok dsh ~/.dsh/settings.yaml, ~/.dsh/.credentials.yaml
|
|
|
49
49
|
dsh 的 `reasoningEfforts`/`input`。
|
|
50
50
|
- **接管你已有的配置——包括 [cc-switch](https://github.com/farion1231/cc-switch)。**
|
|
51
51
|
`import` 会扫描所有已安装的智能体**以及 cc-switch 自己的存储**,合并重复项
|
|
52
|
-
(相同协议 +
|
|
52
|
+
(相同协议 + 归一化端点 + 凭据),并且对两者都只读不写。
|
|
53
53
|
- **区分接口形态。** `/v1/chat/completions` 与 `/v1/responses` 是两个不同端点;
|
|
54
54
|
agentsw 记录供应商说的是哪一种,并且绝不把可用的 responses 降级。
|
|
55
55
|
|
|
@@ -61,7 +61,7 @@ ok dsh ~/.dsh/settings.yaml, ~/.dsh/.credentials.yaml
|
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
63
|
npx agentsw # 免安装,直接打开交互菜单
|
|
64
|
-
npm install -g agentsw # 需要 Node >= 22.
|
|
64
|
+
npm install -g agentsw # 需要 Node >= 22.13,内置 SQLite 无需额外启动参数
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
供应商同步与配置管理支持 Linux、macOS 和 Windows。Windows 下 agentsw 自身状态默认存放在
|
|
@@ -137,7 +137,9 @@ imported sub · openai · https://new.vfing.de/v1 · 18 models [from omp]
|
|
|
137
137
|
imported zhipu-glm · anthropic · https://open.bigmodel.cn/api/anthropic · 1 models [from cc-switch]
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
在交互终端中,不加 `--all` 会先展示多选预览,选定后才保存供应商记录。
|
|
141
|
+
使用 `--all` 或非交互输入时会导入全部符合条件的新条目;脚本中建议明确写 `--all`。
|
|
142
|
+
模型发现过程中可能更新本地元数据缓存。
|
|
141
143
|
|
|
142
144
|
## 模型发现过滤
|
|
143
145
|
|
|
@@ -200,8 +202,16 @@ asw remove unused-provider --apps omp
|
|
|
200
202
|
|
|
201
203
|
## 它如何对待你的配置
|
|
202
204
|
|
|
203
|
-
-
|
|
204
|
-
|
|
205
|
+
- 正式提交变更前,已有中央存储和智能体配置先备份到
|
|
206
|
+
`~/.config/agentsw/backups/transaction-*`。新文件没有旧副本;缓存刷新不属于配置备份。
|
|
207
|
+
预览不写文件,也不创建备份。
|
|
208
|
+
- 每个适配器先准备全部文件再提交,写入失败时尝试回滚。多智能体同步不是全局文件系统事务:
|
|
209
|
+
某个智能体失败时,其他成功项仍保留,并通过非零退出码报告失败。
|
|
210
|
+
- 供应商存储位于 `~/.config/agentsw/config.json`(权限 0600)。并发写入导致快照过期时,
|
|
211
|
+
拒绝覆盖新记录;等待其他写入完成后重试。
|
|
212
|
+
- 保留已有文件权限,新建配置文件默认使用 0600。
|
|
213
|
+
- 写锁占用时会显示持有者 PID 和时间。若进程曾异常退出,先停止所有 agentsw 写入并确认
|
|
214
|
+
锁已遗留,再手动删除报错中指定的 `.write.lock` 后重试;不会自动抢占活动或状态不明的锁。
|
|
205
215
|
- 同步只覆盖 agentsw 自己管理的字段:未建模的供应商级键与模型级扩展字段保留;
|
|
206
216
|
自己管理但本次不再写出的字段会被清除而非留成陈旧值;与新路由矛盾的模型级
|
|
207
217
|
`api`/`baseUrl` 覆盖会被删除(否则它会静默盖过供应商条目)并在输出中点名。
|
|
@@ -212,8 +222,9 @@ asw remove unused-provider --apps omp
|
|
|
212
222
|
手动指定;同步绝不把已有的 responses 端点降级。
|
|
213
223
|
- Codex 只支持 Responses API;仅提供 chat-completions 的端点无法用于 Codex,同步输出会提示。
|
|
214
224
|
- DeepSeek Harness 不把密钥写进 `settings.yaml`:路由里只保存
|
|
215
|
-
`apiKeyEnv: AGENTSW_<ID>_API_KEY` 引用,密钥写入 `$DSH_HOME/.credentials.yaml`
|
|
225
|
+
`apiKeyEnv: AGENTSW_<ID>_<DIGEST>_API_KEY` 引用,密钥写入 `$DSH_HOME/.credentials.yaml`
|
|
216
226
|
的 `refs:`(权限 0600)。`$DSH_HOME` 默认为 `~/.dsh`。
|
|
227
|
+
摘要用于区分 `foo-bar` 和 `foo_bar` 等 ID;旧版生成引用仍可识别,不随意修改外部或自定义引用。
|
|
217
228
|
- cc-switch 的数据库以只读方式打开,永不写入。
|
|
218
229
|
- 安装管理器在 Windows 下为 Claude Code、Codex CLI、pi、opencode、Hermes 和 DeepSeek Harness
|
|
219
230
|
使用原生安装命令。Oh My Pi 与 prime-agent 仍需使用各自的 Windows 安装方式;WorkBuddy
|
|
@@ -242,8 +253,12 @@ npm run dev -- ... # 通过 tsx 从源码运行
|
|
|
242
253
|
|
|
243
254
|
每个智能体是 [`src/targets/`](./src/targets) 下的一个适配器,实现
|
|
244
255
|
`apply` / `prune` / `current` / `candidates`;只读的导入来源放在
|
|
245
|
-
[`src/sources/`](./src/sources)
|
|
246
|
-
[`src/targets/index.ts`](./src/targets/index.ts)
|
|
256
|
+
[`src/sources/`](./src/sources)。新增应用时,在
|
|
257
|
+
[`src/targets/index.ts`](./src/targets/index.ts) 注册,用 `transactionalTarget()` 包装适配器,
|
|
258
|
+
并为 `rename.ts`、`remove.ts` 补充对应配置结构与引用处理。
|
|
259
|
+
复用共享凭据、YAML/JSONC、URL 和模型工具,补齐同步、清理、导入、重命名、删除、
|
|
260
|
+
异常输入、预览及账户隔离测试;支持安装的应用还需在 `apps.ts` 中注册平台命令。
|
|
261
|
+
删除 WorkBuddy 本地账户时,使用 `list --apps workbuddy` 显示的稳定账户标识,避免同域名歧义。
|
|
247
262
|
|
|
248
263
|
## 许可证
|
|
249
264
|
|
package/dist/apps.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFileSync, execSync } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import semver from "semver";
|
|
4
5
|
import { appDataDir, home, readJsonIfExists } from "./fsutil.js";
|
|
5
6
|
export const appPackages = [
|
|
6
7
|
{
|
|
@@ -137,12 +138,20 @@ export function installedVersion(app) {
|
|
|
137
138
|
if (app.binary) {
|
|
138
139
|
try {
|
|
139
140
|
const binary = executableName(app.binary);
|
|
140
|
-
const
|
|
141
|
+
const args = app.versionArgs ?? ["--version"];
|
|
142
|
+
const isBatch = process.platform === "win32" && /\.(cmd|bat)$/i.test(binary);
|
|
143
|
+
const quote = (value) => {
|
|
144
|
+
if (/["%\r\n]/.test(value))
|
|
145
|
+
throw new Error("unsupported Windows version-probe argument");
|
|
146
|
+
return `"${value}"`;
|
|
147
|
+
};
|
|
148
|
+
const out = execFileSync(isBatch ? process.env.ComSpec || "cmd.exe" : binary, isBatch ? ["/d", "/s", "/c", `"${[binary, ...args].map(quote).join(" ")}"`] : args, {
|
|
141
149
|
encoding: "utf8",
|
|
142
150
|
timeout: 15000,
|
|
143
151
|
stdio: ["ignore", "pipe", "pipe"],
|
|
152
|
+
...(isBatch ? { windowsVerbatimArguments: true } : {}),
|
|
144
153
|
});
|
|
145
|
-
return SEMVERISH.exec(out)?.[0];
|
|
154
|
+
return normalizeAppVersion(SEMVERISH.exec(out)?.[0]) ?? "?";
|
|
146
155
|
}
|
|
147
156
|
catch {
|
|
148
157
|
// binary present but probe failed (e.g. runtime version gate) -> installed, version unknown
|
|
@@ -200,25 +209,27 @@ export async function latestVersion(app) {
|
|
|
200
209
|
return undefined;
|
|
201
210
|
}
|
|
202
211
|
}
|
|
203
|
-
/**
|
|
212
|
+
/** Accept SemVer (including prereleases), with the legacy two-component display form. */
|
|
213
|
+
export function normalizeAppVersion(value) {
|
|
214
|
+
if (typeof value !== "string")
|
|
215
|
+
return undefined;
|
|
216
|
+
const version = value.trim();
|
|
217
|
+
return semver.valid(version) ?? (/^v?\d+\.\d+$/.test(version) ? semver.valid(`${version}.0`) ?? undefined : undefined);
|
|
218
|
+
}
|
|
219
|
+
/** Build metadata has no precedence; prereleases sort before the corresponding release. */
|
|
204
220
|
export function isNewer(installed, latest) {
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
const a = pa[i] ?? 0;
|
|
209
|
-
const b = pb[i] ?? 0;
|
|
210
|
-
if (Number.isNaN(a) || Number.isNaN(b))
|
|
211
|
-
break;
|
|
212
|
-
if (b > a)
|
|
213
|
-
return true;
|
|
214
|
-
if (b < a)
|
|
215
|
-
return false;
|
|
216
|
-
}
|
|
217
|
-
return false;
|
|
221
|
+
const before = normalizeAppVersion(installed);
|
|
222
|
+
const after = normalizeAppVersion(latest);
|
|
223
|
+
return before !== undefined && after !== undefined && semver.gt(after, before);
|
|
218
224
|
}
|
|
219
225
|
/** Run an install/upgrade command with live output. Throws on nonzero exit. */
|
|
220
226
|
export function runShell(command) {
|
|
221
|
-
|
|
227
|
+
if (process.platform === "win32") {
|
|
228
|
+
execSync(command, { stdio: "inherit", env: process.env });
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
execFileSync("bash", ["-o", "pipefail", "-c", command], { stdio: "inherit", env: process.env });
|
|
232
|
+
}
|
|
222
233
|
}
|
|
223
234
|
/** Resolve the platform-specific command while keeping the package table readable. */
|
|
224
235
|
export function appCommand(app, action, platform = process.platform) {
|
|
@@ -227,34 +238,27 @@ export function appCommand(app, action, platform = process.platform) {
|
|
|
227
238
|
return action === "install" ? app.installCmd : app.upgradeCmd ?? app.installCmd;
|
|
228
239
|
}
|
|
229
240
|
function executableName(binary) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
241
|
+
return findBinary(binary) ?? binary;
|
|
242
|
+
}
|
|
243
|
+
function findBinary(binary) {
|
|
244
|
+
const names = process.platform === "win32" && !path.extname(binary)
|
|
245
|
+
? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean).map((suffix) => `${binary}${suffix.toLowerCase()}`)
|
|
246
|
+
: [binary];
|
|
247
|
+
const dirs = path.isAbsolute(binary) ? [""] : (process.env.PATH ?? "").split(path.delimiter).filter(Boolean);
|
|
248
|
+
for (const dir of dirs) {
|
|
249
|
+
for (const name of names) {
|
|
250
|
+
const file = path.resolve(dir.replace(/^"|"$/g, ""), name);
|
|
251
|
+
try {
|
|
252
|
+
if (!fs.statSync(file).isFile())
|
|
253
|
+
continue;
|
|
254
|
+
fs.accessSync(file, process.platform === "win32" ? fs.constants.F_OK : fs.constants.X_OK);
|
|
255
|
+
return file;
|
|
256
|
+
}
|
|
257
|
+
catch { /* Try the next executable; directories and inaccessible files are not commands. */ }
|
|
258
|
+
}
|
|
236
259
|
}
|
|
237
|
-
return
|
|
260
|
+
return undefined;
|
|
238
261
|
}
|
|
239
262
|
export function binaryOnPath(binary) {
|
|
240
|
-
|
|
241
|
-
return dirs.some((d) => {
|
|
242
|
-
try {
|
|
243
|
-
const names = process.platform === "win32" && !path.extname(binary)
|
|
244
|
-
? [binary, ...((process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((s) => `${binary}${s.toLowerCase()}`))]
|
|
245
|
-
: [binary];
|
|
246
|
-
return names.some((name) => {
|
|
247
|
-
try {
|
|
248
|
-
fs.accessSync(path.join(d, name), process.platform === "win32" ? fs.constants.F_OK : fs.constants.X_OK);
|
|
249
|
-
return true;
|
|
250
|
-
}
|
|
251
|
-
catch {
|
|
252
|
-
return false;
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
catch {
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
});
|
|
263
|
+
return findBinary(binary) !== undefined;
|
|
260
264
|
}
|