agentsw 0.9.2 → 0.10.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/README.md +50 -4
- package/README.zh-CN.md +44 -3
- package/dist/app-paths.js +127 -0
- package/dist/apps.js +11 -15
- package/dist/commands.js +375 -205
- package/dist/discover.js +21 -11
- package/dist/gateway.js +77 -18
- package/dist/i18n.js +147 -3
- package/dist/index.js +14 -2
- package/dist/menu.js +141 -121
- package/dist/metadata.js +34 -9
- package/dist/modelsdev.js +51 -21
- package/dist/progress.js +116 -0
- package/dist/provider-actions.js +29 -9
- package/dist/remove.js +15 -30
- package/dist/rename.js +17 -32
- package/dist/report.js +67 -0
- package/dist/store.js +25 -0
- package/dist/targets/claudecode.js +12 -9
- package/dist/targets/codex.js +22 -13
- package/dist/targets/dsh.js +20 -35
- package/dist/targets/hermes.js +11 -20
- package/dist/targets/omp.js +15 -7
- package/dist/targets/opencode.js +42 -25
- package/dist/targets/pistyle.js +10 -21
- package/dist/targets/wire.js +22 -7
- package/dist/targets/workbuddy.js +6 -4
- package/dist/ui.js +157 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -113,14 +113,23 @@ dsh yes openai+anthropic myproxy · glm-5.3-flash ~/.d
|
|
|
113
113
|
| [Oh My Pi](https://omp.sh) (omp) | `~/.omp/agent/models.yml` (comments preserved) | both |
|
|
114
114
|
| [pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) | `~/.pi/agent/models.json` + `settings.json` | both |
|
|
115
115
|
| [prime-agent](https://github.com/PrimeIntellect-ai/prime-agent) | `~/.prime/agent/models.json` + `settings.json` | both |
|
|
116
|
-
| [opencode](https://opencode.ai) | `~/.config/opencode/opencode.json` | both |
|
|
116
|
+
| [opencode](https://opencode.ai) | `~/.config/opencode/opencode.json`, `.jsonc` or `config.json` (the one that exists; comments preserved) | both |
|
|
117
117
|
| [Hermes](https://pypi.org/project/hermes-agent/) | `~/.hermes/config.yaml` + `.env` (comments preserved) | both |
|
|
118
|
-
| WorkBuddy | `~/.workbuddy/models.json` + `settings.json` | openai |
|
|
118
|
+
| WorkBuddy | `~/.workbuddy/models.json` + `settings.json` (`%APPDATA%\workbuddy` on Windows) | openai |
|
|
119
119
|
| [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) | `~/.dsh/settings.yaml` + `.credentials.yaml` | both |
|
|
120
120
|
|
|
121
121
|
An agent that is not installed is skipped, not guessed at. `--apps codex,omp` narrows a run;
|
|
122
122
|
`--apps` with an uninstalled agent forces it.
|
|
123
123
|
|
|
124
|
+
Every adapter honors the configuration-directory variable its app defines, and resolves it on
|
|
125
|
+
each command, so `use`, `sync`, `status`, `rename` and `remove` always agree on the same files:
|
|
126
|
+
`CLAUDE_CONFIG_DIR` (Claude Code), `CODEX_HOME` (Codex), `PI_CODING_AGENT_DIR` /
|
|
127
|
+
`PRIME_AGENT_CODING_AGENT_DIR` (pi / prime-agent), `OPENCODE_CONFIG_DIR` and `OPENCODE_CONFIG`
|
|
128
|
+
(opencode), `HERMES_HOME` (Hermes), `DSH_HOME` (dsh), `WORKBUDDY_CONFIG_DIR` (WorkBuddy, with
|
|
129
|
+
the legacy `CODEBUDDY_CONFIG_DIR` as fallback). An empty value is ignored, a leading `~` is
|
|
130
|
+
expanded, and opencode is edited in place whichever of `opencode.json`, `opencode.jsonc` or
|
|
131
|
+
`config.json` exists.
|
|
132
|
+
|
|
124
133
|
The interactive menu asks the same question before writing: a multi-select where the first row
|
|
125
134
|
is **all detected apps**, detected agents are listed first and undetected ones are marked. The
|
|
126
135
|
last selection is remembered, so a menu `use`/`sync` keeps rewriting only the agents you picked.
|
|
@@ -233,7 +242,9 @@ Import still skips already configured accounts.
|
|
|
233
242
|
`refresh --provider <id> --metadata-mode on` to request fresh reference prices.
|
|
234
243
|
- **Auditable and optional:** `metadata.fields` records origins, value snapshots and timestamps;
|
|
235
244
|
`metadata.conflicts` records disagreements with retained values. This audit stays in agentsw, never runtime agent configs.
|
|
236
|
-
Gateway has a separate 24-hour cache;
|
|
245
|
+
Gateway has a separate 24-hour cache; a failed request is retried once before falling back to stale cached data
|
|
246
|
+
(or skipping supplementation when there is none). `AGENTSW_DEBUG=1` reports why an attempt failed, and
|
|
247
|
+
`AGENTSW_GATEWAY_TIMEOUT_MS` overrides the 15-second whole-request budget.
|
|
237
248
|
- **Automatic lookup is not automatic sync:** enrichment updates saved metadata during add/quick/discover/import/refresh.
|
|
238
249
|
It does not add an automatic push to agents. Sync behavior is unchanged: ordinary `sync` only writes saved settings
|
|
239
250
|
and fetches neither model lists nor metadata catalogs.
|
|
@@ -294,6 +305,38 @@ Do not combine `--apps` and `--prune`. Renames and removals preflight changes an
|
|
|
294
305
|
private backups before writes. App-only removal leaves the store intact, so a later explicit
|
|
295
306
|
sync of a managed provider can add it back to that app.
|
|
296
307
|
|
|
308
|
+
### Global options
|
|
309
|
+
|
|
310
|
+
These work with every command, written before or after the subcommand:
|
|
311
|
+
|
|
312
|
+
| Option | Effect |
|
|
313
|
+
|---|---|
|
|
314
|
+
| `--json` | write exactly one JSON document to stdout and nothing else; interactive prompts and progress are disabled, so a scripted run never blocks on input |
|
|
315
|
+
| `-q, --quiet` | suppress progress, hints and warnings; results and errors are kept |
|
|
316
|
+
| `--no-color` | disable colored output (`NO_COLOR=1` does the same; `FORCE_COLOR=1` forces color, `--no-color` wins) |
|
|
317
|
+
| `--lang en\|zh-CN` | interface language |
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
asw status --json | jq '.data.apps[] | select(.current != .id)'
|
|
321
|
+
asw use myproxy -a codex --dry-run --json # file paths only, never config bodies
|
|
322
|
+
asw list --quiet
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Success is `{"ok":true,"command":"status","version":"…","data":{…}}`; a failure is
|
|
326
|
+
`{"ok":false,…,"error":{"message":"…"}}` on stdout with exit code 1. `--json` overrides
|
|
327
|
+
`--quiet`. Credentials never appear in a JSON payload, and `--json --dry-run` reports the file
|
|
328
|
+
paths a run would touch — never the staged configuration. `install` and `upgrade` hand their
|
|
329
|
+
stdio to the package manager, whose own output still reaches the terminal (under `--json` its
|
|
330
|
+
stdout is routed to stderr so the JSON document stays parseable).
|
|
331
|
+
|
|
332
|
+
Results go to stdout; progress, warnings and the dimmed next-step hints go to stderr. So
|
|
333
|
+
`agentsw status > out.txt` captures only the table, and a script can read stderr for what
|
|
334
|
+
happened without parsing it out of the result.
|
|
335
|
+
|
|
336
|
+
Exit codes: `0` success, `1` error, `130` cancelled at a prompt. Without `--json`,
|
|
337
|
+
`models --provider <id> --metadata` keeps printing its bare JSON object, so existing scripts
|
|
338
|
+
are unaffected.
|
|
339
|
+
|
|
297
340
|
## How your configs are treated
|
|
298
341
|
|
|
299
342
|
- Existing provider-store and agent configuration files are backed up before committed
|
|
@@ -311,7 +354,10 @@ sync of a managed provider can add it back to that app.
|
|
|
311
354
|
- A sync overwrites only the fields agentsw owns. Unmodeled provider-level keys and per-model
|
|
312
355
|
extras survive; an owned field that stops applying is cleared rather than left stale, and a
|
|
313
356
|
per-model `api`/`baseUrl` override that contradicts the route is dropped (it would silently
|
|
314
|
-
win over the entry) and reported.
|
|
357
|
+
win over the entry) and reported. Clearing needs the entry to say something in the first
|
|
358
|
+
place: a model the store knows nothing about — an id no catalog row matched — is written as
|
|
359
|
+
an id and its capabilities alone, and the limits already in the file are kept rather than
|
|
360
|
+
deleted.
|
|
315
361
|
- YAML configs (omp, Hermes, dsh) keep their comments. `~/.codex/config.toml` does not —
|
|
316
362
|
TOML round-trip drops them, which is why the backup happens first.
|
|
317
363
|
- OpenAI endpoints come in two wires: `/v1/chat/completions` and `/v1/responses`. Imported
|
package/README.zh-CN.md
CHANGED
|
@@ -108,14 +108,22 @@ dsh yes openai+anthropic myproxy · glm-5.3-flash ~/.d
|
|
|
108
108
|
| [Oh My Pi](https://omp.sh) (omp) | `~/.omp/agent/models.yml`(保留注释) | 双协议 |
|
|
109
109
|
| [pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) | `~/.pi/agent/models.json` + `settings.json` | 双协议 |
|
|
110
110
|
| [prime-agent](https://github.com/PrimeIntellect-ai/prime-agent) | `~/.prime/agent/models.json` + `settings.json` | 双协议 |
|
|
111
|
-
| [opencode](https://opencode.ai) | `~/.config/opencode
|
|
111
|
+
| [opencode](https://opencode.ai) | `~/.config/opencode/` 下已存在的 `opencode.json`、`.jsonc` 或 `config.json`(保留注释) | 双协议 |
|
|
112
112
|
| [Hermes](https://pypi.org/project/hermes-agent/) | `~/.hermes/config.yaml` + `.env`(保留注释) | 双协议 |
|
|
113
|
-
| WorkBuddy | `~/.workbuddy/models.json` + `settings.json` | openai |
|
|
113
|
+
| WorkBuddy | `~/.workbuddy/models.json` + `settings.json`(Windows 下为 `%APPDATA%\workbuddy`) | openai |
|
|
114
114
|
| [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) | `~/.dsh/settings.yaml` + `.credentials.yaml` | 双协议 |
|
|
115
115
|
|
|
116
116
|
未安装的应用会被跳过,而不是瞎猜。`--apps codex,omp` 只跑指定应用;
|
|
117
117
|
对未检测到的应用显式传 `--apps` 可强制写入。
|
|
118
118
|
|
|
119
|
+
每个适配器都会遵循对应应用自己的配置目录环境变量,并在每次命令中重新解析,因此
|
|
120
|
+
`use`、`sync`、`status`、`rename`、`remove` 始终指向同一批文件:`CLAUDE_CONFIG_DIR`
|
|
121
|
+
(Claude Code)、`CODEX_HOME`(Codex)、`PI_CODING_AGENT_DIR` / `PRIME_AGENT_CODING_AGENT_DIR`
|
|
122
|
+
(pi / prime-agent)、`OPENCODE_CONFIG_DIR` 与 `OPENCODE_CONFIG`(opencode)、`HERMES_HOME`
|
|
123
|
+
(Hermes)、`DSH_HOME`(dsh)、`WORKBUDDY_CONFIG_DIR`(WorkBuddy,回退到旧的
|
|
124
|
+
`CODEBUDDY_CONFIG_DIR`)。空值会被忽略,开头的 `~` 会展开;opencode 会就地编辑实际存在的
|
|
125
|
+
`opencode.json`、`opencode.jsonc` 或 `config.json`。
|
|
126
|
+
|
|
119
127
|
交互菜单写配置前会问同一个问题:多选框第一行是**全部检测到的应用**,已检测到的排在前面,
|
|
120
128
|
未检测到的会标注出来。上次的选择会被记住,所以菜单里的 `use`/`sync` 只会继续写你勾选的那几个。
|
|
121
129
|
任何配置文件解析失败(JSON/YAML/TOML 非法)都会报出文件与位置,绝不回显出错那一行;
|
|
@@ -219,7 +227,9 @@ asw sync --provider myproxy
|
|
|
219
227
|
自动模式不会仅为刷新参考价格而查询:可查看 `metadata.gateway.fetchedAt` 判断快照时间,
|
|
220
228
|
需要重新查询参考价格时使用 `refresh --provider <id> --metadata-mode on`。
|
|
221
229
|
- **可追溯、可降级:** `metadata.fields` 记录来源、原值快照和时间,`metadata.conflicts` 记录保留值与冲突。
|
|
222
|
-
审计信息仅保存在 agentsw,不写入智能体运行配置。Gateway 目录独立缓存 24
|
|
230
|
+
审计信息仅保存在 agentsw,不写入智能体运行配置。Gateway 目录独立缓存 24 小时;请求失败会先重试一次,
|
|
231
|
+
仍失败才使用旧缓存,无缓存时只跳过补充。`AGENTSW_DEBUG=1` 会说明该次尝试失败的原因,
|
|
232
|
+
`AGENTSW_GATEWAY_TIMEOUT_MS` 可覆盖默认 15 秒的整体请求预算。
|
|
223
233
|
- **自动查询不等于自动同步:** 添加、快速添加、发现、导入、刷新时补全保存的元数据,并不新增自动推送。
|
|
224
234
|
同步行为不变:普通 `sync` 仅写入已保存的设置,既不获取模型列表,也不请求元数据目录。
|
|
225
235
|
|
|
@@ -278,6 +288,35 @@ asw remove unused-provider --apps omp
|
|
|
278
288
|
重命名和删除会先检查全部计划变更,再创建私有备份并写入。
|
|
279
289
|
仅从智能体删除时中央条目仍保留,之后主动同步该供应商会重新写入该智能体。
|
|
280
290
|
|
|
291
|
+
### 全局选项
|
|
292
|
+
|
|
293
|
+
所有命令都支持,写在子命令前后均可:
|
|
294
|
+
|
|
295
|
+
| 选项 | 作用 |
|
|
296
|
+
|---|---|
|
|
297
|
+
| `--json` | stdout 只输出一份 JSON 文档;同时关闭交互提问与进度,脚本运行绝不因等待输入而挂起 |
|
|
298
|
+
| `-q, --quiet` | 抑制进度、提示与警告;结果与错误保留 |
|
|
299
|
+
| `--no-color` | 关闭彩色输出(`NO_COLOR=1` 等效;`FORCE_COLOR=1` 强制彩色,`--no-color` 优先) |
|
|
300
|
+
| `--lang en\|zh-CN` | 界面语言 |
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
asw status --json | jq '.data.apps[] | select(.current != .id)'
|
|
304
|
+
asw use myproxy -a codex --dry-run --json # 只给文件路径,不含配置正文
|
|
305
|
+
asw list --quiet
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
成功时输出 `{"ok":true,"command":"status","version":"…","data":{…}}`;失败时 stdout 输出
|
|
309
|
+
`{"ok":false,…,"error":{"message":"…"}}` 并以退出码 1 结束。`--json` 优先于 `--quiet`。
|
|
310
|
+
JSON 载荷一律不含凭据;`--json --dry-run` 只报告将要改动的文件路径,绝不输出待写入的配置内容。
|
|
311
|
+
`install` 与 `upgrade` 把 stdio 交给包管理器,其输出仍会到达终端(`--json` 下会把它的 stdout
|
|
312
|
+
转到 stderr,以保证 JSON 文档可解析)。
|
|
313
|
+
|
|
314
|
+
结果写 stdout,进度、警告与灰字的下一步提示写 stderr。因此 `agentsw status > out.txt` 只截到
|
|
315
|
+
表格本身,脚本也可以单独读 stderr 了解过程中发生了什么,而不用从结果里把它剥出来。
|
|
316
|
+
|
|
317
|
+
退出码:`0` 成功,`1` 出错,`130` 在交互提问处取消。不带 `--json` 时
|
|
318
|
+
`models --provider <id> --metadata` 仍输出原来的裸 JSON 对象,现有脚本不受影响。
|
|
319
|
+
|
|
281
320
|
## 它如何对待你的配置
|
|
282
321
|
|
|
283
322
|
- 正式提交变更前,已有中央存储和智能体配置先备份到
|
|
@@ -293,6 +332,8 @@ asw remove unused-provider --apps omp
|
|
|
293
332
|
- 同步只覆盖 agentsw 自己管理的字段:未建模的供应商级键与模型级扩展字段保留;
|
|
294
333
|
自己管理但本次不再写出的字段会被清除而非留成陈旧值;与新路由矛盾的模型级
|
|
295
334
|
`api`/`baseUrl` 覆盖会被删除(否则它会静默盖过供应商条目)并在输出中点名。
|
|
335
|
+
但"清除"要求这一条本身说了点什么:store 完全不认识的模型(没有任何目录行匹配到的 id)
|
|
336
|
+
只带 id 和能力写入,文件里已有的上下文长度等参数会保留而不是被删掉。
|
|
296
337
|
- YAML 配置(omp、Hermes、dsh)保留注释;`~/.codex/config.toml` 不行——TOML 往返会丢注释,
|
|
297
338
|
所以才先备份。
|
|
298
339
|
- OpenAI 端点有两种接口形态:`/v1/chat/completions` 与 `/v1/responses`。导入时沿用配置里
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { appDataDir, expandHome, home, localAppDataDir } from "./fsutil.js";
|
|
4
|
+
/**
|
|
5
|
+
* Every app path lives here and is resolved on each call, so one command's `use`/`sync`
|
|
6
|
+
* targets the same files as `remove`/`rename`/`status` — including when the app's own
|
|
7
|
+
* config-directory environment variable is set. An empty value is ignored (the apps do
|
|
8
|
+
* the same); a relative value is kept as-is, exactly as the app would resolve it against
|
|
9
|
+
* its own working directory.
|
|
10
|
+
*/
|
|
11
|
+
function envPath(name) {
|
|
12
|
+
const raw = process.env[name]?.trim();
|
|
13
|
+
return raw ? expandHome(raw) : undefined;
|
|
14
|
+
}
|
|
15
|
+
export function claudeDir() {
|
|
16
|
+
return envPath("CLAUDE_CONFIG_DIR") ?? path.join(home, ".claude");
|
|
17
|
+
}
|
|
18
|
+
export function claudeSettingsFile() {
|
|
19
|
+
return path.join(claudeDir(), "settings.json");
|
|
20
|
+
}
|
|
21
|
+
export function codexDir() {
|
|
22
|
+
return envPath("CODEX_HOME") ?? path.join(home, ".codex");
|
|
23
|
+
}
|
|
24
|
+
export function codexConfigFile() {
|
|
25
|
+
return path.join(codexDir(), "config.toml");
|
|
26
|
+
}
|
|
27
|
+
export function codexAuthFile() {
|
|
28
|
+
return path.join(codexDir(), "auth.json");
|
|
29
|
+
}
|
|
30
|
+
const PI_LAYOUT = {
|
|
31
|
+
pi: { env: "PI_CODING_AGENT_DIR", dir: ".pi/agent" },
|
|
32
|
+
prime: { env: "PRIME_AGENT_CODING_AGENT_DIR", dir: ".prime/agent" },
|
|
33
|
+
};
|
|
34
|
+
export function piDir(id) {
|
|
35
|
+
return envPath(PI_LAYOUT[id].env) ?? path.join(home, PI_LAYOUT[id].dir);
|
|
36
|
+
}
|
|
37
|
+
export function piModelsFile(id) {
|
|
38
|
+
return path.join(piDir(id), "models.json");
|
|
39
|
+
}
|
|
40
|
+
export function piSettingsFile(id) {
|
|
41
|
+
return path.join(piDir(id), "settings.json");
|
|
42
|
+
}
|
|
43
|
+
export function ompAgentDir() {
|
|
44
|
+
return path.join(home, ".omp", "agent");
|
|
45
|
+
}
|
|
46
|
+
/** An existing models.yaml wins only when models.yml is absent (omp precedence). */
|
|
47
|
+
export function ompModelsFiles() {
|
|
48
|
+
return ["models.yml", "models.yaml"].map((name) => path.join(ompAgentDir(), name));
|
|
49
|
+
}
|
|
50
|
+
export function ompConfigFiles() {
|
|
51
|
+
return ["config.yml", "config.yaml"].map((name) => path.join(ompAgentDir(), name));
|
|
52
|
+
}
|
|
53
|
+
export function hermesDir() {
|
|
54
|
+
return envPath("HERMES_HOME") ?? localAppDataDir("hermes");
|
|
55
|
+
}
|
|
56
|
+
export function hermesConfigFile() {
|
|
57
|
+
return path.join(hermesDir(), "config.yaml");
|
|
58
|
+
}
|
|
59
|
+
export function hermesEnvFile() {
|
|
60
|
+
return path.join(hermesDir(), ".env");
|
|
61
|
+
}
|
|
62
|
+
export function dshDir() {
|
|
63
|
+
return envPath("DSH_HOME") ?? localAppDataDir("dsh");
|
|
64
|
+
}
|
|
65
|
+
/** The extension picks the document format, so an existing .yml/.json is written in place. */
|
|
66
|
+
export function dshSettingsFiles() {
|
|
67
|
+
return ["settings.yaml", "settings.yml", "settings.json"].map((name) => path.join(dshDir(), name));
|
|
68
|
+
}
|
|
69
|
+
export function dshSettingsFile() {
|
|
70
|
+
return dshSettingsFiles().find((file) => fs.existsSync(file)) ?? path.join(dshDir(), "settings.yaml");
|
|
71
|
+
}
|
|
72
|
+
export function dshCredentialsFile() {
|
|
73
|
+
return path.join(dshDir(), ".credentials.yaml");
|
|
74
|
+
}
|
|
75
|
+
export function workbuddyDir() {
|
|
76
|
+
return envPath("WORKBUDDY_CONFIG_DIR") ?? envPath("CODEBUDDY_CONFIG_DIR") ??
|
|
77
|
+
(process.platform === "win32" ? appDataDir("workbuddy") : path.join(home, ".workbuddy"));
|
|
78
|
+
}
|
|
79
|
+
export function workbuddyModelsFile() {
|
|
80
|
+
return path.join(workbuddyDir(), "models.json");
|
|
81
|
+
}
|
|
82
|
+
export function workbuddySettingsFile() {
|
|
83
|
+
return path.join(workbuddyDir(), "settings.json");
|
|
84
|
+
}
|
|
85
|
+
/** opencode's loader accepts any of these names in its config directory. */
|
|
86
|
+
export const OPENCODE_CONFIG_FILENAMES = ["opencode.json", "opencode.jsonc", "config.json"];
|
|
87
|
+
export function opencodeConfigDirs() {
|
|
88
|
+
const dirs = [];
|
|
89
|
+
const custom = envPath("OPENCODE_CONFIG_DIR");
|
|
90
|
+
if (custom)
|
|
91
|
+
dirs.push(custom);
|
|
92
|
+
const fallback = appDataDir("opencode");
|
|
93
|
+
if (!dirs.includes(fallback))
|
|
94
|
+
dirs.push(fallback);
|
|
95
|
+
return dirs;
|
|
96
|
+
}
|
|
97
|
+
/** Every file a management command must consider, across both directories and the explicit file. */
|
|
98
|
+
export function opencodeConfigFiles() {
|
|
99
|
+
const files = [];
|
|
100
|
+
const add = (file) => {
|
|
101
|
+
if (!files.includes(file))
|
|
102
|
+
files.push(file);
|
|
103
|
+
};
|
|
104
|
+
for (const dir of opencodeConfigDirs()) {
|
|
105
|
+
for (const name of OPENCODE_CONFIG_FILENAMES)
|
|
106
|
+
add(path.join(dir, name));
|
|
107
|
+
}
|
|
108
|
+
const explicit = envPath("OPENCODE_CONFIG");
|
|
109
|
+
if (explicit)
|
|
110
|
+
add(explicit);
|
|
111
|
+
return files;
|
|
112
|
+
}
|
|
113
|
+
/** OPENCODE_CONFIG (an explicit file), else the first existing config in the search order, else the creation default. */
|
|
114
|
+
export function opencodePrimaryConfigFile() {
|
|
115
|
+
const explicit = envPath("OPENCODE_CONFIG");
|
|
116
|
+
if (explicit)
|
|
117
|
+
return explicit;
|
|
118
|
+
const dirs = opencodeConfigDirs();
|
|
119
|
+
for (const dir of dirs) {
|
|
120
|
+
for (const name of OPENCODE_CONFIG_FILENAMES) {
|
|
121
|
+
const file = path.join(dir, name);
|
|
122
|
+
if (fs.existsSync(file))
|
|
123
|
+
return file;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return path.join(dirs[0] ?? appDataDir("opencode"), "opencode.json");
|
|
127
|
+
}
|
package/dist/apps.js
CHANGED
|
@@ -2,7 +2,9 @@ import { execFileSync, execSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import semver from "semver";
|
|
5
|
-
import {
|
|
5
|
+
import { home, readJsonIfExists } from "./fsutil.js";
|
|
6
|
+
import { dshDir, workbuddyDir } from "./app-paths.js";
|
|
7
|
+
import { isJson } from "./ui.js";
|
|
6
8
|
export const appPackages = [
|
|
7
9
|
{
|
|
8
10
|
id: "claude",
|
|
@@ -68,7 +70,7 @@ export const appPackages = [
|
|
|
68
70
|
name: "WorkBuddy",
|
|
69
71
|
// Electron desktop app: version from its data dir; managed by its own updater.
|
|
70
72
|
localVersion: () => {
|
|
71
|
-
const dir =
|
|
73
|
+
const dir = workbuddyDir();
|
|
72
74
|
const j = readJsonIfExists(path.join(dir, "last-launch.json"));
|
|
73
75
|
const v = (j?.version ?? j?.appVersion);
|
|
74
76
|
if (v)
|
|
@@ -90,12 +92,6 @@ export const appPackages = [
|
|
|
90
92
|
localVersion: () => dshLocalVersion(),
|
|
91
93
|
},
|
|
92
94
|
];
|
|
93
|
-
// dsh config dir; respects $DSH_HOME.
|
|
94
|
-
function dshHome() {
|
|
95
|
-
const env = process.env.DSH_HOME?.trim();
|
|
96
|
-
return env ? (env.startsWith("~") ? path.join(home, env.slice(1)) : env) :
|
|
97
|
-
process.platform === "win32" ? path.join(home, "AppData", "Local", "dsh") : path.join(home, ".dsh");
|
|
98
|
-
}
|
|
99
95
|
/** dsh version: search npx cache for @deepseek-ai/dsh, fall back to "?" if config dir exists. */
|
|
100
96
|
function dshLocalVersion() {
|
|
101
97
|
// 1. Fast path: check global node_modules directly (no subprocess)
|
|
@@ -125,14 +121,10 @@ function dshLocalVersion() {
|
|
|
125
121
|
}
|
|
126
122
|
}
|
|
127
123
|
// 3. Config dir exists -> used via npx, version unknown
|
|
128
|
-
if (fs.existsSync(
|
|
124
|
+
if (fs.existsSync(dshDir()))
|
|
129
125
|
return "?";
|
|
130
126
|
return undefined;
|
|
131
127
|
}
|
|
132
|
-
function workbuddyDataDir() {
|
|
133
|
-
return process.env.WORKBUDDY_CONFIG_DIR?.trim() || process.env.CODEBUDDY_CONFIG_DIR?.trim() ||
|
|
134
|
-
(process.platform === "win32" ? appDataDir("workbuddy") : path.join(home, ".workbuddy"));
|
|
135
|
-
}
|
|
136
128
|
const SEMVERISH = /\d+\.\d+(\.\d+)?([-.+][\w.-]+)?/;
|
|
137
129
|
export function installedVersion(app) {
|
|
138
130
|
if (app.binary) {
|
|
@@ -224,11 +216,15 @@ export function isNewer(installed, latest) {
|
|
|
224
216
|
}
|
|
225
217
|
/** Run an install/upgrade command with live output. Throws on nonzero exit. */
|
|
226
218
|
export function runShell(command) {
|
|
219
|
+
// --json owns stdout, so the child's output moves to stderr to keep the document parseable.
|
|
220
|
+
const stdio = isJson()
|
|
221
|
+
? ["inherit", process.stderr.fd, "inherit"]
|
|
222
|
+
: "inherit";
|
|
227
223
|
if (process.platform === "win32") {
|
|
228
|
-
execSync(command, { stdio
|
|
224
|
+
execSync(command, { stdio, env: process.env });
|
|
229
225
|
}
|
|
230
226
|
else {
|
|
231
|
-
execFileSync("bash", ["-o", "pipefail", "-c", command], { stdio
|
|
227
|
+
execFileSync("bash", ["-o", "pipefail", "-c", command], { stdio, env: process.env });
|
|
232
228
|
}
|
|
233
229
|
}
|
|
234
230
|
/** Resolve the platform-specific command while keeping the package table readable. */
|