dsh-agy-link 0.2.0 → 0.2.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 +17 -1
- package/README.md +4 -0
- package/dist/index.js +77 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1 (2026-08-19)
|
|
4
|
+
|
|
5
|
+
- Cross-platform hardening (Linux / macOS / Windows):
|
|
6
|
+
- binary discovery is platform-aware: agy / agy.exe / .cmd / .bat across
|
|
7
|
+
PATH, ~/.local/bin, /usr/local/bin, /opt/homebrew/bin,
|
|
8
|
+
%LOCALAPPDATA%\Programs, and the npm shim dir; a real executable is
|
|
9
|
+
always preferred over a cmd shim
|
|
10
|
+
- Windows .cmd/.bat shims spawn through cmd.exe with cross-spawn-style
|
|
11
|
+
argument quoting (unit-tested)
|
|
12
|
+
- tree-kill uses taskkill /T /F on Windows (Unix process groups do not
|
|
13
|
+
exist there); detached sessions are POSIX-only so no console window
|
|
14
|
+
flashes on Windows
|
|
15
|
+
- CRLF stdout is normalized (trailing \r stripped per line)
|
|
16
|
+
- the MCP bridge script path resolves via fileURLToPath (URL.pathname
|
|
17
|
+
would yield /C:/... on Windows and break the spawn)
|
|
18
|
+
- 5 new cross-platform tests (56 total).
|
|
19
|
+
|
|
3
20
|
## 0.2.0 (2026-08-19)
|
|
4
21
|
|
|
5
22
|
- Multimodal (path-based): DSH image attachments are staged to a local media
|
|
@@ -25,4 +42,3 @@
|
|
|
25
42
|
- README restructured into a single bilingual page: Chinese first, then
|
|
26
43
|
English (README.zh.md removed; package files list updated).
|
|
27
44
|
- Releases now publish to npm automatically (NPM_TOKEN secret configured).
|
|
28
|
-
|
package/README.md
CHANGED
|
@@ -59,6 +59,8 @@ dsh plugin --profile web add dsh-agy-link
|
|
|
59
59
|
|
|
60
60
|
> 插件绝不读取/复制/移动 `~/.gemini/antigravity-cli/antigravity-oauth-token`;登录完全通过官方 CLI 自己的流程完成。
|
|
61
61
|
|
|
62
|
+
> 🖥 **跨平台**:Linux / macOS / Windows 均受支持——bin 探测按平台查找 `agy`/`agy.exe`(PATH、`~/.local/bin`、`/usr/local/bin`、`/opt/homebrew/bin`、`%LOCALAPPDATA%\Programs`,npm `.cmd` shim 自动经 cmd.exe 安全引号包裹启动);中断/超时杀树在 Windows 走 `taskkill /T /F`;CRLF 输出统一剥离;媒体目录与 MCP 桥路径全部 `fileURLToPath`/`join` 构造。
|
|
63
|
+
|
|
62
64
|
## ⚙️ 配置
|
|
63
65
|
|
|
64
66
|
配置在 `agy-link` 插件条目里(`/plugin` 或 profile patch 层编辑),环境变量优先:
|
|
@@ -145,6 +147,8 @@ dsh plugin --profile web add dsh-agy-link
|
|
|
145
147
|
|
|
146
148
|
> The plugin never reads, copies, or moves `~/.gemini/antigravity-cli/antigravity-oauth-token`; login is driven entirely through the official CLI's own flow.
|
|
147
149
|
|
|
150
|
+
> 🖥 **Cross-platform**: Linux / macOS / Windows — platform-aware binary discovery (`agy` / `agy.exe` across PATH, `~/.local/bin`, `/usr/local/bin`, `/opt/homebrew/bin`, `%LOCALAPPDATA%\Programs`; npm `.cmd` shims spawn through cmd.exe with safe quoting), tree-kill via `taskkill /T /F` on Windows, CRLF output normalized, media and bridge paths built with `fileURLToPath`/`join`.
|
|
151
|
+
|
|
148
152
|
## ⚙️ Configuration
|
|
149
153
|
|
|
150
154
|
Config lives in the `agy-link` plugin entry (edit via `/plugin` or the profile patch layer). Environment variables override the file:
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { execFileSync, spawn } from "node:child_process";
|
|
|
7
7
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
8
8
|
import { createServer } from "node:http";
|
|
9
9
|
import { randomBytes } from "node:crypto";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
10
11
|
//#region src/common/types.ts
|
|
11
12
|
const PROVIDER_ID = "antigravity";
|
|
12
13
|
const PLUGIN_ID = "agy-link";
|
|
@@ -1015,20 +1016,51 @@ async function sweepDir(dir, ttlMs, now = Date.now()) {
|
|
|
1015
1016
|
function defaultMediaDir(stateDir) {
|
|
1016
1017
|
return join(stateDir, "media");
|
|
1017
1018
|
}
|
|
1019
|
+
//#endregion
|
|
1020
|
+
//#region src/host/runner.ts
|
|
1021
|
+
const IS_WIN = process.platform === "win32";
|
|
1022
|
+
/** Executable candidates for one PATH entry, per-platform. Exported for tests. */
|
|
1023
|
+
function binCandidates(dir, platform = process.platform) {
|
|
1024
|
+
return (platform === "win32" ? [
|
|
1025
|
+
".exe",
|
|
1026
|
+
".cmd",
|
|
1027
|
+
".bat"
|
|
1028
|
+
] : [""]).map((e) => join(dir, "agy" + e));
|
|
1029
|
+
}
|
|
1030
|
+
/** True when the resolved bin is a Windows cmd shim (needs shell wrapping). */
|
|
1031
|
+
function isCmdShim(bin) {
|
|
1032
|
+
return /\.(cmd|bat)$/i.test(bin);
|
|
1033
|
+
}
|
|
1034
|
+
/** cmd.exe argument quoting (cross-spawn rules). Exported for tests. */
|
|
1035
|
+
function windowsQuote(arg) {
|
|
1036
|
+
if (/[ \t\n\v"]/.test(arg) === false) return arg;
|
|
1037
|
+
let escaped = arg.replace(/(\\+)\"/g, "$1$1\\\"").replace(/(\\+)$/, "$1$1");
|
|
1038
|
+
escaped = "\"" + escaped.replace(/"/g, "\\\"") + "\"";
|
|
1039
|
+
return escaped;
|
|
1040
|
+
}
|
|
1018
1041
|
function resolveAgyBin(cfg) {
|
|
1019
1042
|
const candidates = [];
|
|
1020
1043
|
if (cfg.agyBin !== "") candidates.push(cfg.agyBin);
|
|
1021
1044
|
const pathEnv = process.env.PATH ?? "";
|
|
1022
|
-
for (const dir of pathEnv.split(delimiter)) if (dir !== "") candidates.push(
|
|
1023
|
-
|
|
1024
|
-
|
|
1045
|
+
for (const dir of pathEnv.split(delimiter)) if (dir !== "") candidates.push(...binCandidates(dir));
|
|
1046
|
+
if (IS_WIN) {
|
|
1047
|
+
const local = process.env.LOCALAPPDATA ?? "";
|
|
1048
|
+
if (local !== "") candidates.push(join(local, "Programs", "agy", "agy.exe"));
|
|
1049
|
+
candidates.push(join(homedir(), ".local", "bin", "agy.exe"));
|
|
1050
|
+
candidates.push(join(homedir(), "AppData", "Roaming", "npm", "agy.cmd"));
|
|
1051
|
+
} else {
|
|
1052
|
+
candidates.push(join(homedir(), ".local", "bin", "agy"));
|
|
1053
|
+
candidates.push("/usr/local/bin/agy");
|
|
1054
|
+
candidates.push("/opt/homebrew/bin/agy");
|
|
1055
|
+
}
|
|
1056
|
+
const hits = [];
|
|
1025
1057
|
for (const c of candidates) try {
|
|
1026
|
-
accessSync(c, constants.
|
|
1027
|
-
|
|
1058
|
+
accessSync(c, constants.F_OK);
|
|
1059
|
+
hits.push(c);
|
|
1028
1060
|
} catch {
|
|
1029
1061
|
continue;
|
|
1030
1062
|
}
|
|
1031
|
-
return null;
|
|
1063
|
+
return hits.find((h) => !isCmdShim(h)) ?? hits[0] ?? null;
|
|
1032
1064
|
}
|
|
1033
1065
|
function compareVersions(a, b) {
|
|
1034
1066
|
const pa = a.split(/\./).map(Number);
|
|
@@ -1045,6 +1077,24 @@ function parseVersion(out) {
|
|
|
1045
1077
|
}
|
|
1046
1078
|
function killTree(child) {
|
|
1047
1079
|
if (child.pid === void 0) return;
|
|
1080
|
+
if (IS_WIN) {
|
|
1081
|
+
try {
|
|
1082
|
+
spawn("taskkill", [
|
|
1083
|
+
"/pid",
|
|
1084
|
+
String(child.pid),
|
|
1085
|
+
"/T",
|
|
1086
|
+
"/F"
|
|
1087
|
+
], {
|
|
1088
|
+
stdio: "ignore",
|
|
1089
|
+
windowsHide: true
|
|
1090
|
+
});
|
|
1091
|
+
} catch {
|
|
1092
|
+
try {
|
|
1093
|
+
child.kill();
|
|
1094
|
+
} catch {}
|
|
1095
|
+
}
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1048
1098
|
try {
|
|
1049
1099
|
process.kill(-child.pid, "SIGTERM");
|
|
1050
1100
|
} catch {
|
|
@@ -1055,15 +1105,31 @@ function killTree(child) {
|
|
|
1055
1105
|
}
|
|
1056
1106
|
function startAgyProcess(opts) {
|
|
1057
1107
|
const started = Date.now();
|
|
1058
|
-
const child =
|
|
1108
|
+
const child = IS_WIN && isCmdShim(opts.bin) ? spawn(process.env.ComSpec ?? "cmd.exe", [
|
|
1109
|
+
"/d",
|
|
1110
|
+
"/s",
|
|
1111
|
+
"/c",
|
|
1112
|
+
[opts.bin, ...opts.args].map(windowsQuote).join(" ")
|
|
1113
|
+
], {
|
|
1059
1114
|
cwd: opts.cwd,
|
|
1060
1115
|
env: process.env,
|
|
1061
|
-
detached: true,
|
|
1062
1116
|
stdio: [
|
|
1063
1117
|
"pipe",
|
|
1064
1118
|
"pipe",
|
|
1065
1119
|
"pipe"
|
|
1066
|
-
]
|
|
1120
|
+
],
|
|
1121
|
+
windowsVerbatimArguments: true,
|
|
1122
|
+
windowsHide: true
|
|
1123
|
+
}) : spawn(opts.bin, opts.args, {
|
|
1124
|
+
cwd: opts.cwd,
|
|
1125
|
+
env: process.env,
|
|
1126
|
+
detached: !IS_WIN,
|
|
1127
|
+
stdio: [
|
|
1128
|
+
"pipe",
|
|
1129
|
+
"pipe",
|
|
1130
|
+
"pipe"
|
|
1131
|
+
],
|
|
1132
|
+
windowsHide: true
|
|
1067
1133
|
});
|
|
1068
1134
|
let stdout = "";
|
|
1069
1135
|
let stderr = "";
|
|
@@ -1088,7 +1154,7 @@ function startAgyProcess(opts) {
|
|
|
1088
1154
|
pending += chunk;
|
|
1089
1155
|
let nl;
|
|
1090
1156
|
while ((nl = pending.indexOf("\n")) >= 0) {
|
|
1091
|
-
const line = pending.slice(0, nl);
|
|
1157
|
+
const line = pending.slice(0, nl).replace(/\r$/, "");
|
|
1092
1158
|
pending = pending.slice(nl + 1);
|
|
1093
1159
|
opts.onLine?.(line);
|
|
1094
1160
|
}
|
|
@@ -2715,7 +2781,7 @@ function apply(ctx, entryConfig = {}) {
|
|
|
2715
2781
|
const want = cfg.mcpBridge && cfg.enabled;
|
|
2716
2782
|
if (want && bridgeState.bridge === null) (async () => {
|
|
2717
2783
|
try {
|
|
2718
|
-
const script = new URL("./bridge.mjs", import.meta.url)
|
|
2784
|
+
const script = fileURLToPath(new URL("./bridge.mjs", import.meta.url));
|
|
2719
2785
|
const toolsSvc = ctx.get("tools");
|
|
2720
2786
|
const bridge = await startMcpBridge({
|
|
2721
2787
|
bridgeScript: script,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-agy-link",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Google Antigravity (agy CLI) models for DeepSeek Harness — stream Gemini/Claude/GPT-OSS subscriptions into DSH with thinking, tool activity, token usage and in-GUI Google OAuth login.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|