@yhong91/cpac 0.2.6 → 0.2.8
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 +19 -2
- package/dist/agents.js +13 -5
- package/dist/cpac.js +39 -4
- package/dist/models.js +8 -0
- package/dist/proxy.js +25 -0
- package/dist/targets/claude.js +2 -2
- package/dist/util.js +74 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -151,6 +151,23 @@ source ~/.zshrc # 使用引导实际显示的文件
|
|
|
151
151
|
export CPA_API_KEY='...'
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
+
### Windows
|
|
155
|
+
|
|
156
|
+
CPAC 会按平台自动选择处理方式:
|
|
157
|
+
|
|
158
|
+
- 密钥引导不写 shell profile(Windows 没有 `source` 语义)。引导会提示改用 `setx CPA_API_KEY "<your-key>"` 或「编辑账户的环境变量」持久化;也可以在 PowerShell 里直接执行:
|
|
159
|
+
|
|
160
|
+
```powershell
|
|
161
|
+
[Environment]::SetEnvironmentVariable("CPA_API_KEY", "<your-key>", "User")
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
设置后需要重开终端。若通过 npm 全局安装,PowerShell 首次运行需要允许本地脚本:`Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`。
|
|
165
|
+
|
|
166
|
+
- 检测 PATH 上的 agent 时使用 `where` 而非 `command -v`。
|
|
167
|
+
- 启动 agent 时自动解析 `.cmd` / `.bat` shim(npm 全局安装的 CLI),`.exe` 直接启动。
|
|
168
|
+
- `cpac upgrade` / `cpac uninstall` 内部调用 npm 时自动经 shell 启动 `npm.cmd`。
|
|
169
|
+
- 清理残留代理进程时通过 PowerShell CIM 读取进程命令行来确认身份,不会误杀无关进程。
|
|
170
|
+
|
|
154
171
|
## 命令
|
|
155
172
|
|
|
156
173
|
### 引导和帮助
|
|
@@ -166,7 +183,7 @@ cpac --help
|
|
|
166
183
|
|
|
167
184
|
```bash
|
|
168
185
|
cpac detect [--json]
|
|
169
|
-
cpac models [--json]
|
|
186
|
+
cpac models [--json] [--diff]
|
|
170
187
|
cpac install [codex|pi|kimi|grok|zed|hermes|codebuddy...] [--all] [--dry-run] [--force] [--v2_off] [--v2_models] [--max_context]
|
|
171
188
|
cpac sync [codex|pi|kimi|grok|zed|hermes|codebuddy...] [--all] [--dry-run] [--v2_off] [--v2_models] [--max_context]
|
|
172
189
|
cpac restore <codex|pi|kimi|grok|zed|hermes|codebuddy...> | --all [--dry-run]
|
|
@@ -175,7 +192,7 @@ cpac upgrade [--check]
|
|
|
175
192
|
cpac -v | --version
|
|
176
193
|
```
|
|
177
194
|
|
|
178
|
-
- `models` 从远端 CPA 拉取 rich catalog,默认一行一个 slug;`--json` 输出 `cpa_url` 和模型字段(`slug` / `display_name` / `context_window`)。用来核对 catalog 里有没有某个模型(例如 `*-fast
|
|
195
|
+
- `models` 从远端 CPA 拉取 rich catalog,默认一行一个 slug;`--json` 输出 `cpa_url` 和模型字段(`slug` / `display_name` / `context_window`)。用来核对 catalog 里有没有某个模型(例如 `*-fast`),不写本地配置。`--diff` 拿已保存的 `state_dir/models.json` 和当前 catalog 比 slug:`+` 是新增,`-` 是去掉;没有差别打印 `unchanged`。还没有保存列表时报错,先跑 `cpac sync`。
|
|
179
196
|
- `detect` 列出每个目标是否被检测到、是否已接入 CPAC;`--json` 输出机器可读结果。
|
|
180
197
|
- `install` 默认作用于检测到的目标;后面直接写 agent 名(`cpac install codex pi`),`--all` 全部。`codex` 写入 catalog 并拉起 loopback 代理;`pi`/`kimi`/`grok`/`zed`/`hermes`/`codebuddy` 写入各自配置。`--dry-run` 只打印将要做的事,`--force` 允许重装已安装目标。
|
|
181
198
|
- `sync` 把 CPA 模型列表和更新时间写到 `state_dir/models.json`(默认 `~/.cpac/models.json`)。这个时间戳就是模型版本:列表没变则时间戳不变,目标上次同步到的版本相同就跳过,不同才重写。不带参数只看已安装目标;`--all` 还会装上检测到但还没接入的目标。`install` 仍总是重写。`spawn_models`、`v2_off`、`max_context` 和代理端口不改这个版本,要马上生效用 `cpac install`。`--max_context` 仍只对 `cpac install codex` 有效。
|
package/dist/agents.js
CHANGED
|
@@ -12,7 +12,7 @@ import { installPiConfig, isPiConfigInstalled, piAgentDir, piModelsPath, uninsta
|
|
|
12
12
|
import { installZedConfig, isZedConfigInstalled, uninstallZedConfig, zedConfigPath, zedHome, } from "./targets/zed.js";
|
|
13
13
|
import { hermesConfigPaths, hermesHome, installHermesConfig, isHermesConfigInstalled, uninstallHermesConfig, } from "./targets/hermes.js";
|
|
14
14
|
import { codebuddyHome, codebuddyModelsPath, installCodebuddyConfig, isCodebuddyConfigInstalled, uninstallCodebuddyConfig, } from "./targets/codebuddy.js";
|
|
15
|
-
import { CPACError, atomicWrite, checkboxPicker, compareVersions, expandUserPath, objectValue, resolveApiKey, tabWizard, } from "./util.js";
|
|
15
|
+
import { CPACError, atomicWrite, checkboxPicker, compareVersions, expandUserPath, isWindows, objectValue, resolveApiKey, spawnAgent, spawnAgentSync, tabWizard, } from "./util.js";
|
|
16
16
|
const CODEX_CTX_STD = "Standard (default input budget, e.g. 200K~272K)";
|
|
17
17
|
const CODEX_CTX_MAX = "Max Context Window (lift to full upper bound, e.g. 921K~1M, 90% auto-compact limit)";
|
|
18
18
|
const CODEX_V2_ON = "Enabled (Default)";
|
|
@@ -139,7 +139,7 @@ export async function runTargetLauncher(config, targetId, args, executable = tar
|
|
|
139
139
|
const env = targetId === "zed"
|
|
140
140
|
? { ...process.env, CPAC_API_KEY: process.env.CPAC_API_KEY || "cpac" }
|
|
141
141
|
: undefined;
|
|
142
|
-
const child =
|
|
142
|
+
const child = spawnAgent(executable, args, { stdio: "inherit", env });
|
|
143
143
|
child.once("error", (error) => {
|
|
144
144
|
rejectPromise(new CPACError(error instanceof Error && "code" in error && error.code === "ENOENT"
|
|
145
145
|
? `${executable} not found`
|
|
@@ -163,7 +163,7 @@ export function detectClientVersion(binary) {
|
|
|
163
163
|
if (!/^[\w.-]+$/.test(binary))
|
|
164
164
|
return undefined;
|
|
165
165
|
try {
|
|
166
|
-
const res =
|
|
166
|
+
const res = spawnAgentSync(binary, ["--version"], {
|
|
167
167
|
encoding: "utf8",
|
|
168
168
|
timeout: 2000,
|
|
169
169
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -182,6 +182,14 @@ function binaryOnPath(name) {
|
|
|
182
182
|
if (!/^[\w.-]+$/.test(name))
|
|
183
183
|
return false;
|
|
184
184
|
try {
|
|
185
|
+
if (isWindows) {
|
|
186
|
+
// `command -v` is a POSIX shell builtin; cmd.exe only has `where`.
|
|
187
|
+
return (spawnSync("where", [name], {
|
|
188
|
+
stdio: "ignore",
|
|
189
|
+
timeout: 3_000,
|
|
190
|
+
windowsHide: true,
|
|
191
|
+
}).status === 0);
|
|
192
|
+
}
|
|
185
193
|
return (spawnSync(`command -v ${name}`, { stdio: "ignore", shell: true }).status ===
|
|
186
194
|
0);
|
|
187
195
|
}
|
|
@@ -512,7 +520,7 @@ export async function runUninstall(config, options) {
|
|
|
512
520
|
return 0;
|
|
513
521
|
}
|
|
514
522
|
console.log("Uninstalling @yhong91/cpac");
|
|
515
|
-
const result =
|
|
523
|
+
const result = spawnAgentSync("npm", ["uninstall", "-g", "@yhong91/cpac"], {
|
|
516
524
|
stdio: "inherit",
|
|
517
525
|
});
|
|
518
526
|
if (result.status !== 0) {
|
|
@@ -573,7 +581,7 @@ export async function runUpgrade(config, checkOnly) {
|
|
|
573
581
|
const args = ["install", "-g", `@yhong91/cpac@${latest}`];
|
|
574
582
|
if (prefix)
|
|
575
583
|
args.splice(2, 0, `--prefix=${prefix}`);
|
|
576
|
-
const result =
|
|
584
|
+
const result = spawnAgentSync("npm", args, { stdio: "inherit" });
|
|
577
585
|
if (result.status !== 0) {
|
|
578
586
|
console.error("npm install failed");
|
|
579
587
|
return result.status ?? 1;
|
package/dist/cpac.js
CHANGED
|
@@ -13,6 +13,7 @@ import { isZedConfigInstalled } from "./targets/zed.js";
|
|
|
13
13
|
import { isHermesConfigInstalled } from "./targets/hermes.js";
|
|
14
14
|
import { isCodebuddyConfigInstalled } from "./targets/codebuddy.js";
|
|
15
15
|
import { defaultConfigPath, loadConfig, originalBytes, pickSpawnModels, readState, saveSpawnModels, stateProxy, catalogMaxContextLive, catalogModelId, catalogModelRows, catalogSlugs, fetchCatalog, } from "./config.js";
|
|
16
|
+
import { canonicalModels, diffModelSlugs, readModelIndex } from "./models.js";
|
|
16
17
|
import { runPing } from "./ping.js";
|
|
17
18
|
import { proxyIsHealthy, runProxyChild } from "./proxy.js";
|
|
18
19
|
import { CPACError, expandUserPath, promptSecret, saveApiKeyExport, shellProfile, } from "./util.js";
|
|
@@ -93,11 +94,30 @@ export async function status(config) {
|
|
|
93
94
|
return 2;
|
|
94
95
|
return 0;
|
|
95
96
|
}
|
|
96
|
-
async function listModels(config, json) {
|
|
97
|
+
async function listModels(config, json, diff = false) {
|
|
97
98
|
const apiKey = process.env[config.api_key_env]?.trim();
|
|
98
99
|
if (!apiKey)
|
|
99
100
|
throw new CPACError(`environment variable ${config.api_key_env} is not set`);
|
|
100
101
|
const catalog = await fetchCatalog(config.cpa_url, apiKey);
|
|
102
|
+
if (diff) {
|
|
103
|
+
const index = readModelIndex(config.state_dir);
|
|
104
|
+
if (!index)
|
|
105
|
+
throw new CPACError("no saved model list; run cpac sync");
|
|
106
|
+
const { added, removed } = diffModelSlugs(index.models.map((model) => model.slug), canonicalModels(catalog.bytes).map((model) => model.slug));
|
|
107
|
+
if (json) {
|
|
108
|
+
console.log(JSON.stringify({ added, removed }, null, 2));
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
if (added.length === 0 && removed.length === 0) {
|
|
112
|
+
console.log("unchanged");
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
for (const slug of added)
|
|
116
|
+
console.log(`+ ${slug}`);
|
|
117
|
+
for (const slug of removed)
|
|
118
|
+
console.log(`- ${slug}`);
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
101
121
|
if (!json) {
|
|
102
122
|
for (const slug of catalogSlugs(catalog.bytes))
|
|
103
123
|
console.log(slug);
|
|
@@ -138,9 +158,15 @@ async function guide(config) {
|
|
|
138
158
|
if (process.env[config.api_key_env]?.trim())
|
|
139
159
|
return 0;
|
|
140
160
|
const apiKey = await promptSecret(config.api_key_env);
|
|
161
|
+
process.env[config.api_key_env] = apiKey;
|
|
162
|
+
if (process.platform === "win32") {
|
|
163
|
+
// Windows has no shell profile to source; persist via setx instead of
|
|
164
|
+
// writing an export line no native shell would read.
|
|
165
|
+
console.log(`\nTo persist ${config.api_key_env} across terminals, run in a new terminal:\n setx ${config.api_key_env} "<your-key>"\n(or use Start > "Edit environment variables for your account").\nThe key is active for this session only until then.`);
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
141
168
|
const profile = shellProfile();
|
|
142
169
|
saveApiKeyExport(profile, config.api_key_env, apiKey);
|
|
143
|
-
process.env[config.api_key_env] = apiKey;
|
|
144
170
|
console.log(`\nSaved ${config.api_key_env} to ${profile}. Open a new terminal or run:\n source ${profile}`);
|
|
145
171
|
return 0;
|
|
146
172
|
}
|
|
@@ -183,6 +209,7 @@ function usage() {
|
|
|
183
209
|
"",
|
|
184
210
|
"Command options:",
|
|
185
211
|
opt("--json", "JSON output", "[models, detect]"),
|
|
212
|
+
opt("--diff", "models added or removed since the saved list", "[models]"),
|
|
186
213
|
opt("--all", "every detected or installed agent", "[install, sync, restore]"),
|
|
187
214
|
opt("--dry-run", "print actions without writing", "[install, sync, restore, uninstall]"),
|
|
188
215
|
opt("--force", "allow reinstall of already-injected agents", "[install]"),
|
|
@@ -288,6 +315,11 @@ function parseArgs(args) {
|
|
|
288
315
|
}
|
|
289
316
|
if (arg === "--json")
|
|
290
317
|
parsed.json = true;
|
|
318
|
+
else if (arg === "--diff") {
|
|
319
|
+
if (parsed.command !== "models")
|
|
320
|
+
throw new CPACError("--diff is only valid with models");
|
|
321
|
+
parsed.diff = true;
|
|
322
|
+
}
|
|
291
323
|
else if (arg === "--all")
|
|
292
324
|
parsed.all = true;
|
|
293
325
|
else if (arg === "--dry-run")
|
|
@@ -648,13 +680,16 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
648
680
|
const parsed = parseArgs(args);
|
|
649
681
|
if (!parsed)
|
|
650
682
|
return 0;
|
|
651
|
-
if ("home" in parsed && parsed.home)
|
|
683
|
+
if ("home" in parsed && parsed.home) {
|
|
652
684
|
process.env.HOME = parsed.home;
|
|
685
|
+
if (process.platform === "win32")
|
|
686
|
+
process.env.USERPROFILE = parsed.home;
|
|
687
|
+
}
|
|
653
688
|
const config = loadConfig(parsed.configPath, true);
|
|
654
689
|
if (parsed.command === "guide")
|
|
655
690
|
return await guide(config);
|
|
656
691
|
if (parsed.command === "models")
|
|
657
|
-
return await listModels(config, parsed.json);
|
|
692
|
+
return await listModels(config, parsed.json, parsed.diff === true);
|
|
658
693
|
if (parsed.command === "detect")
|
|
659
694
|
return await runDetect(config, parsed.json);
|
|
660
695
|
if (parsed.command === "install") {
|
package/dist/models.js
CHANGED
|
@@ -33,6 +33,14 @@ function stableValue(value) {
|
|
|
33
33
|
stable[key] = stableValue(value[key]);
|
|
34
34
|
return stable;
|
|
35
35
|
}
|
|
36
|
+
export function diffModelSlugs(saved, live) {
|
|
37
|
+
const savedSet = new Set(saved);
|
|
38
|
+
const liveSet = new Set(live);
|
|
39
|
+
return {
|
|
40
|
+
added: live.filter((slug) => !savedSet.has(slug)),
|
|
41
|
+
removed: saved.filter((slug) => !liveSet.has(slug)),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
36
44
|
export function canonicalModels(bytes) {
|
|
37
45
|
let document;
|
|
38
46
|
try {
|
package/dist/proxy.js
CHANGED
|
@@ -3,6 +3,7 @@ import { randomBytes } from "node:crypto";
|
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { createServer, request as httpRequest, } from "node:http";
|
|
5
5
|
import { request as httpsRequest } from "node:https";
|
|
6
|
+
import { join } from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
import { apiBase, proxyFingerprint, readState, saveState, stateProxy, } from "./config.js";
|
|
8
9
|
import { CPACError, objectValue, resolveApiKey } from "./util.js";
|
|
@@ -246,6 +247,30 @@ export async function startProxyProcess(config, apiKey) {
|
|
|
246
247
|
function processCommandArgs(pid) {
|
|
247
248
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
248
249
|
return undefined;
|
|
250
|
+
if (process.platform === "win32") {
|
|
251
|
+
// No /proc and no ps on Windows; PowerShell CIM is the dependable way
|
|
252
|
+
// to read another process's command line (wmic is removed on newer
|
|
253
|
+
// Windows 11 builds). Only used to identify stale proxy processes.
|
|
254
|
+
const systemRoot = process.env.SystemRoot?.trim() || "C:\\Windows";
|
|
255
|
+
try {
|
|
256
|
+
const result = spawnSync(join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"), [
|
|
257
|
+
"-NoProfile",
|
|
258
|
+
"-NonInteractive",
|
|
259
|
+
"-Command",
|
|
260
|
+
`(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}').CommandLine`,
|
|
261
|
+
], {
|
|
262
|
+
encoding: "utf8",
|
|
263
|
+
timeout: 5_000,
|
|
264
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
265
|
+
windowsHide: true,
|
|
266
|
+
});
|
|
267
|
+
const line = result.stdout?.trim();
|
|
268
|
+
return line ? line.split(/\s+/) : undefined;
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
249
274
|
try {
|
|
250
275
|
const proc = `/proc/${pid}/cmdline`;
|
|
251
276
|
if (existsSync(proc)) {
|
package/dist/targets/claude.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createServer, request as httpRequest, } from "node:http";
|
|
|
4
4
|
import { request as httpsRequest } from "node:https";
|
|
5
5
|
import { apiBase, catalogModelId, catalogModelRows, catalogSlugs, fetchCatalog, loadConfig, } from "../config.js";
|
|
6
6
|
import { proxyHeaders, responseHeaders, upstreamUrl } from "../proxy.js";
|
|
7
|
-
import { CPACError, atomicWrite, objectValue, resolveApiKey, tabWizard, } from "../util.js";
|
|
7
|
+
import { CPACError, atomicWrite, objectValue, resolveApiKey, spawnAgent, tabWizard, } from "../util.js";
|
|
8
8
|
const CLAUDE_ALIAS_PREFIX = "claude-cpac--";
|
|
9
9
|
// Claude Code accepts CLAUDE_CODE_AUTO_COMPACT_WINDOW in 100K–1M (binary-verified).
|
|
10
10
|
// A single global window cannot be per-model; 350K is opencodex's user-approved
|
|
@@ -379,7 +379,7 @@ export async function runClaude(config, args, executable = "claude") {
|
|
|
379
379
|
delete env.CLAUDE_CODE_USE_FOUNDRY;
|
|
380
380
|
delete env.CLAUDE_CODE_USE_VERTEX;
|
|
381
381
|
return await new Promise((resolve, reject) => {
|
|
382
|
-
const child =
|
|
382
|
+
const child = spawnAgent(executable, args, { env, stdio: "inherit" });
|
|
383
383
|
child.once("error", (error) => {
|
|
384
384
|
stopProxy();
|
|
385
385
|
reject(new CPACError(error instanceof Error && "code" in error && error.code === "ENOENT"
|
package/dist/util.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawn, spawnSync, } from "node:child_process";
|
|
1
2
|
import { chmodSync, closeSync, copyFileSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import { basename, dirname, join, parse } from "node:path";
|
|
@@ -9,6 +10,79 @@ export class CPACError extends Error {
|
|
|
9
10
|
export function objectValue(value) {
|
|
10
11
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
12
|
}
|
|
13
|
+
export const isWindows = process.platform === "win32";
|
|
14
|
+
// Windows process launching: npm-installed CLIs are .cmd/.bat shims, and
|
|
15
|
+
// spawn without a shell only resolves .exe (CreateProcess), so those need a
|
|
16
|
+
// shell plus a correctly quoted command line. Everything else spawns directly.
|
|
17
|
+
function resolveWindowsExecutable(executable) {
|
|
18
|
+
if (!/^[\w.-]+$/.test(executable))
|
|
19
|
+
return undefined;
|
|
20
|
+
try {
|
|
21
|
+
const result = spawnSync("where", [executable], {
|
|
22
|
+
encoding: "utf8",
|
|
23
|
+
timeout: 3_000,
|
|
24
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
25
|
+
windowsHide: true,
|
|
26
|
+
});
|
|
27
|
+
if (result.status !== 0)
|
|
28
|
+
return undefined;
|
|
29
|
+
const lines = result.stdout
|
|
30
|
+
.split(/\r?\n/)
|
|
31
|
+
.map((entry) => entry.trim())
|
|
32
|
+
.filter(Boolean);
|
|
33
|
+
return (lines.find((entry) => /\.(exe|com|cmd|bat)$/i.test(entry)) || lines[0]);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Quotes a single argument for cmd.exe (MSDN rules): double backslashes that
|
|
40
|
+
// precede a quote, double trailing backslashes so they cannot escape the
|
|
41
|
+
// closing quote, and wrap tokens containing whitespace or quotes.
|
|
42
|
+
function cmdQuote(arg) {
|
|
43
|
+
if (arg !== "" && !/[\s"]/.test(arg))
|
|
44
|
+
return arg;
|
|
45
|
+
let out = "";
|
|
46
|
+
let backslashes = 0;
|
|
47
|
+
for (const ch of arg) {
|
|
48
|
+
if (ch === "\\") {
|
|
49
|
+
backslashes++;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (ch === '"')
|
|
53
|
+
out += `${"\\".repeat(backslashes * 2 + 1)}"`;
|
|
54
|
+
else
|
|
55
|
+
out += `${"\\".repeat(backslashes)}${ch}`;
|
|
56
|
+
backslashes = 0;
|
|
57
|
+
}
|
|
58
|
+
return `"${out}${"\\".repeat(backslashes * 2)}"`;
|
|
59
|
+
}
|
|
60
|
+
function spawnAgentPlan(executable, args) {
|
|
61
|
+
if (!isWindows)
|
|
62
|
+
return { command: executable, args };
|
|
63
|
+
const resolved = resolveWindowsExecutable(executable);
|
|
64
|
+
if (!resolved)
|
|
65
|
+
return null; // let the plain spawn produce the ENOENT error
|
|
66
|
+
if (!/\.(cmd|bat)$/i.test(resolved))
|
|
67
|
+
return { command: resolved, args };
|
|
68
|
+
return { line: [resolved, ...args].map(cmdQuote).join(" ") };
|
|
69
|
+
}
|
|
70
|
+
export function spawnAgent(executable, args, options) {
|
|
71
|
+
const plan = spawnAgentPlan(executable, args);
|
|
72
|
+
if (plan === null)
|
|
73
|
+
return spawn(executable, args, options);
|
|
74
|
+
if ("line" in plan)
|
|
75
|
+
return spawn(plan.line, { ...options, shell: true });
|
|
76
|
+
return spawn(plan.command, plan.args, options);
|
|
77
|
+
}
|
|
78
|
+
export function spawnAgentSync(executable, args, options) {
|
|
79
|
+
const plan = spawnAgentPlan(executable, args);
|
|
80
|
+
if (plan === null)
|
|
81
|
+
return spawnSync(executable, args, options);
|
|
82
|
+
if ("line" in plan)
|
|
83
|
+
return spawnSync(plan.line, { ...options, shell: true });
|
|
84
|
+
return spawnSync(plan.command, plan.args, options);
|
|
85
|
+
}
|
|
12
86
|
export function ensureCpacBackup(stateDir, agent, target, native = false) {
|
|
13
87
|
const dest = join(stateDir, agent, basename(target));
|
|
14
88
|
const sidecar = `${target}.cpac-backup`;
|