@aictiq/cli 0.1.0 → 0.2.0
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/dist/index.js +120 -51
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -299,7 +299,7 @@ async function promptSecret(question) {
|
|
|
299
299
|
const muted = rl;
|
|
300
300
|
const original = muted.output.write.bind(muted.output);
|
|
301
301
|
muted.output.write = (() => true);
|
|
302
|
-
const answer = await new Promise((
|
|
302
|
+
const answer = await new Promise((resolve3) => rl.question("", resolve3));
|
|
303
303
|
muted.output.write = original;
|
|
304
304
|
process.stdout.write("\n");
|
|
305
305
|
return answer.trim();
|
|
@@ -948,7 +948,7 @@ function mcpCommand(globals) {
|
|
|
948
948
|
};
|
|
949
949
|
process.on("SIGINT", () => void shutdown());
|
|
950
950
|
process.on("SIGTERM", () => void shutdown());
|
|
951
|
-
await new Promise((
|
|
951
|
+
await new Promise((resolve3) => process.stdin.once("close", resolve3));
|
|
952
952
|
await shutdown();
|
|
953
953
|
});
|
|
954
954
|
}
|
|
@@ -1146,7 +1146,7 @@ async function streamLog(ctx, orgSlug, runId, follow, pollMs) {
|
|
|
1146
1146
|
if (page.items.length < logPageSize) break;
|
|
1147
1147
|
}
|
|
1148
1148
|
if (finished) return;
|
|
1149
|
-
await new Promise((
|
|
1149
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollMs));
|
|
1150
1150
|
}
|
|
1151
1151
|
}
|
|
1152
1152
|
function formatLogEntry(entry) {
|
|
@@ -1203,7 +1203,7 @@ async function resolveAgentId(ctx, agent) {
|
|
|
1203
1203
|
import { Command as Command7 } from "commander";
|
|
1204
1204
|
import { existsSync as existsSync3 } from "fs";
|
|
1205
1205
|
import { arch, platform } from "os";
|
|
1206
|
-
import { resolve } from "path";
|
|
1206
|
+
import { resolve as resolve2 } from "path";
|
|
1207
1207
|
|
|
1208
1208
|
// src/runner/client.ts
|
|
1209
1209
|
var RunnerHttpError = class extends Error {
|
|
@@ -1344,9 +1344,9 @@ import { dirname as dirname2, join as join3 } from "path";
|
|
|
1344
1344
|
|
|
1345
1345
|
// src/runner/workspace.ts
|
|
1346
1346
|
import { spawn } from "child_process";
|
|
1347
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1347
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, realpathSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1348
1348
|
import { homedir as homedir2 } from "os";
|
|
1349
|
-
import { join as join2 } from "path";
|
|
1349
|
+
import { isAbsolute, join as join2, relative, resolve } from "path";
|
|
1350
1350
|
|
|
1351
1351
|
// src/runner/types.ts
|
|
1352
1352
|
var RunFailure = class extends Error {
|
|
@@ -1521,18 +1521,52 @@ function uniqueFileName(value, existing) {
|
|
|
1521
1521
|
function formatBytes(value) {
|
|
1522
1522
|
return value >= 1024 * 1024 ? `${(value / (1024 * 1024)).toFixed(1)} MiB` : `${value} bytes`;
|
|
1523
1523
|
}
|
|
1524
|
+
function resolveLocalRepository(run, options) {
|
|
1525
|
+
const mapped = options.repositories[run.projectKey];
|
|
1526
|
+
if (mapped !== void 0) return { path: mapped, root: null };
|
|
1527
|
+
const hint = run.repo.localPathHint?.trim();
|
|
1528
|
+
const roots = options.repoRoots ?? [];
|
|
1529
|
+
if (hint) {
|
|
1530
|
+
const expanded = expandHome(hint);
|
|
1531
|
+
if (isAbsolute(expanded)) {
|
|
1532
|
+
const real = realpathOrSelf(expanded);
|
|
1533
|
+
for (const configured of roots) {
|
|
1534
|
+
const expandedRoot = expandHome(configured);
|
|
1535
|
+
if (!isAbsolute(expandedRoot)) continue;
|
|
1536
|
+
const root = realpathOrSelf(expandedRoot);
|
|
1537
|
+
if (isWithin(real, root)) return { path: real, root };
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
const example = `("${run.projectKey}": "/path/to/repository")`;
|
|
1542
|
+
const detail = !hint ? ` Add it to the \`workspaces\` map in runner.json ${example}, or set the project's path hint and list its parent directory in \`repoRoots\`.` : roots.length === 0 ? ` The project suggests ${hint}. Add its parent directory to \`repoRoots\` in runner.json (\`aictiq runner root <path>\`), or map it in \`workspaces\` ${example}.` : ` The project suggests ${hint}, which is not under any of this runner's repoRoots (${roots.join(", ")}). Add a root that contains it, or map it in \`workspaces\` ${example}.`;
|
|
1543
|
+
throw new RunFailure(
|
|
1544
|
+
"no-local-repository",
|
|
1545
|
+
`No local repository is mapped for project ${run.projectKey}.${detail}`
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
function expandHome(path) {
|
|
1549
|
+
if (path === "~") return homedir2();
|
|
1550
|
+
return path.startsWith("~/") ? join2(homedir2(), path.slice(2)) : path;
|
|
1551
|
+
}
|
|
1552
|
+
function realpathOrSelf(path) {
|
|
1553
|
+
const normalised = resolve(path);
|
|
1554
|
+
try {
|
|
1555
|
+
return realpathSync(normalised);
|
|
1556
|
+
} catch {
|
|
1557
|
+
return normalised;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
function isWithin(path, root) {
|
|
1561
|
+
const rel = relative(root, path);
|
|
1562
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
1563
|
+
}
|
|
1524
1564
|
function message(error) {
|
|
1525
1565
|
return error instanceof Error ? error.message : String(error);
|
|
1526
1566
|
}
|
|
1527
1567
|
async function provisionLocal(run, checkout, options, env) {
|
|
1528
|
-
const mapped = options
|
|
1529
|
-
|
|
1530
|
-
const hint = run.repo.localPathHint ? ` The project suggests ${run.repo.localPathHint}.` : "";
|
|
1531
|
-
throw new RunFailure(
|
|
1532
|
-
"no-local-repository",
|
|
1533
|
-
`No local repository is mapped for project ${run.projectKey}. Add it to the \`workspaces\` map in runner.json ("${run.projectKey}": "/path/to/repository").${hint}`
|
|
1534
|
-
);
|
|
1535
|
-
}
|
|
1568
|
+
const { path: mapped, root } = resolveLocalRepository(run, options);
|
|
1569
|
+
const origin = root === null ? "mapped for project" : "the path hint for project";
|
|
1536
1570
|
const toplevel = await git(["rev-parse", "--show-toplevel"], {
|
|
1537
1571
|
cwd: mapped,
|
|
1538
1572
|
env,
|
|
@@ -1541,9 +1575,15 @@ async function provisionLocal(run, checkout, options, env) {
|
|
|
1541
1575
|
if (isAbort(error)) throw error;
|
|
1542
1576
|
throw new RunFailure(
|
|
1543
1577
|
"no-local-repository",
|
|
1544
|
-
`${mapped},
|
|
1578
|
+
`${mapped}, ${origin} ${run.projectKey}, is not a git repository.`
|
|
1545
1579
|
);
|
|
1546
1580
|
});
|
|
1581
|
+
if (root !== null && !isWithin(realpathOrSelf(toplevel.trim()), root)) {
|
|
1582
|
+
throw new RunFailure(
|
|
1583
|
+
"no-local-repository",
|
|
1584
|
+
`${mapped}, the path hint for project ${run.projectKey}, is not a git repository under ${root}.`
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1547
1587
|
const repo = toplevel.trim();
|
|
1548
1588
|
const call = (args) => gitStep(args, { cwd: repo, env, signal: options.signal });
|
|
1549
1589
|
const hasOrigin = await git(["remote", "get-url", "origin"], {
|
|
@@ -1672,7 +1712,7 @@ ${tail}` : error.message;
|
|
|
1672
1712
|
}
|
|
1673
1713
|
}
|
|
1674
1714
|
function git(args, options) {
|
|
1675
|
-
return new Promise((
|
|
1715
|
+
return new Promise((resolve3, reject) => {
|
|
1676
1716
|
const child = spawn("git", args, {
|
|
1677
1717
|
cwd: options.cwd,
|
|
1678
1718
|
env: { ...options.env, GIT_TERMINAL_PROMPT: "0" },
|
|
@@ -1687,7 +1727,7 @@ function git(args, options) {
|
|
|
1687
1727
|
});
|
|
1688
1728
|
child.on("error", reject);
|
|
1689
1729
|
child.on("close", (code) => {
|
|
1690
|
-
if (code === 0)
|
|
1730
|
+
if (code === 0) resolve3(stdout);
|
|
1691
1731
|
else reject(new GitError(args, code, stderr));
|
|
1692
1732
|
});
|
|
1693
1733
|
});
|
|
@@ -1710,7 +1750,7 @@ function readRunnerConfig(path = runnerConfigPath()) {
|
|
|
1710
1750
|
return null;
|
|
1711
1751
|
}
|
|
1712
1752
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1713
|
-
const { url, token, name, workspaces, attachments } = parsed;
|
|
1753
|
+
const { url, token, name, workspaces, repoRoots, attachments } = parsed;
|
|
1714
1754
|
if (typeof url !== "string" || typeof token !== "string") return null;
|
|
1715
1755
|
const mapped = {};
|
|
1716
1756
|
if (typeof workspaces === "object" && workspaces !== null) {
|
|
@@ -1718,12 +1758,14 @@ function readRunnerConfig(path = runnerConfigPath()) {
|
|
|
1718
1758
|
if (typeof value === "string") mapped[key] = value;
|
|
1719
1759
|
}
|
|
1720
1760
|
}
|
|
1761
|
+
const roots = Array.isArray(repoRoots) ? repoRoots.filter((root) => typeof root === "string" && root.trim() !== "") : [];
|
|
1721
1762
|
const rawLimits = typeof attachments === "object" && attachments !== null ? attachments : {};
|
|
1722
1763
|
return {
|
|
1723
1764
|
url,
|
|
1724
1765
|
token,
|
|
1725
1766
|
...typeof name === "string" ? { name } : {},
|
|
1726
1767
|
workspaces: mapped,
|
|
1768
|
+
repoRoots: roots,
|
|
1727
1769
|
attachments: {
|
|
1728
1770
|
maxCount: validLimit(rawLimits.maxCount, DefaultAttachmentMaxCount),
|
|
1729
1771
|
maxBytes: validLimit(rawLimits.maxBytes, DefaultAttachmentMaxBytes)
|
|
@@ -1872,7 +1914,7 @@ function splitByBytes(text, maxBytes) {
|
|
|
1872
1914
|
return pieces;
|
|
1873
1915
|
}
|
|
1874
1916
|
function delay(ms) {
|
|
1875
|
-
return new Promise((
|
|
1917
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1876
1918
|
}
|
|
1877
1919
|
|
|
1878
1920
|
// src/runner/process.ts
|
|
@@ -1885,12 +1927,12 @@ function spawnSupervised(options) {
|
|
|
1885
1927
|
detached: process.platform !== "win32",
|
|
1886
1928
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1887
1929
|
});
|
|
1888
|
-
const exited = new Promise((
|
|
1930
|
+
const exited = new Promise((resolve3) => {
|
|
1889
1931
|
let settled = false;
|
|
1890
1932
|
const settle = (code) => {
|
|
1891
1933
|
if (settled) return;
|
|
1892
1934
|
settled = true;
|
|
1893
|
-
|
|
1935
|
+
resolve3(code);
|
|
1894
1936
|
};
|
|
1895
1937
|
child.on("error", (error) => {
|
|
1896
1938
|
options.onLine("stderr", `Could not start ${options.command}: ${error.message}`);
|
|
@@ -2210,7 +2252,7 @@ async function sendFinish(client, run, report, local) {
|
|
|
2210
2252
|
return true;
|
|
2211
2253
|
} catch (error) {
|
|
2212
2254
|
if (error instanceof RunnerNetworkError && attempt < 30) {
|
|
2213
|
-
await new Promise((
|
|
2255
|
+
await new Promise((resolve3) => setTimeout(resolve3, Math.min(3e4, 1e3 * 2 ** attempt)));
|
|
2214
2256
|
continue;
|
|
2215
2257
|
}
|
|
2216
2258
|
if (error instanceof RunnerHttpError && error.revoked) throw error;
|
|
@@ -2220,14 +2262,14 @@ async function sendFinish(client, run, report, local) {
|
|
|
2220
2262
|
}
|
|
2221
2263
|
}
|
|
2222
2264
|
function findPullRequest(checkout, branch, env) {
|
|
2223
|
-
return new Promise((
|
|
2265
|
+
return new Promise((resolve3) => {
|
|
2224
2266
|
execFile(
|
|
2225
2267
|
"gh",
|
|
2226
2268
|
["pr", "view", branch, "--json", "url", "--jq", ".url"],
|
|
2227
2269
|
{ cwd: checkout, env, timeout: 15e3 },
|
|
2228
2270
|
(error, stdout) => {
|
|
2229
2271
|
const url = error ? "" : stdout.trim();
|
|
2230
|
-
|
|
2272
|
+
resolve3(/^https:\/\/\S+\/pull\/\d+$/.test(url) ? url : null);
|
|
2231
2273
|
}
|
|
2232
2274
|
);
|
|
2233
2275
|
});
|
|
@@ -2245,12 +2287,12 @@ import { execFile as execFile2 } from "child_process";
|
|
|
2245
2287
|
var limit2 = (value, length) => value.length > length ? `${value.slice(0, Math.max(0, length - 1))}\u2026` : value;
|
|
2246
2288
|
var recordOf = (value) => typeof value === "object" && value !== null ? value : null;
|
|
2247
2289
|
function versionOf(command, env = process.env) {
|
|
2248
|
-
return new Promise((
|
|
2290
|
+
return new Promise((resolve3) => {
|
|
2249
2291
|
execFile2(command, ["--version"], { env, timeout: 5e3 }, (error, stdout, stderr) => {
|
|
2250
|
-
if (error) return
|
|
2292
|
+
if (error) return resolve3(null);
|
|
2251
2293
|
const output = String(stdout || stderr).trim();
|
|
2252
2294
|
const firstLine = output.split(/\r?\n/)[0]?.slice(0, 100) ?? "";
|
|
2253
|
-
|
|
2295
|
+
resolve3(firstLine || null);
|
|
2254
2296
|
});
|
|
2255
2297
|
});
|
|
2256
2298
|
}
|
|
@@ -2696,20 +2738,20 @@ var RunnerLoop = class {
|
|
|
2696
2738
|
return null;
|
|
2697
2739
|
}
|
|
2698
2740
|
waitForSlot() {
|
|
2699
|
-
return new Promise((
|
|
2741
|
+
return new Promise((resolve3) => {
|
|
2700
2742
|
this.wake = () => {
|
|
2701
2743
|
this.wake = void 0;
|
|
2702
|
-
|
|
2744
|
+
resolve3();
|
|
2703
2745
|
};
|
|
2704
2746
|
});
|
|
2705
2747
|
}
|
|
2706
2748
|
sleep(ms) {
|
|
2707
|
-
return new Promise((
|
|
2749
|
+
return new Promise((resolve3) => {
|
|
2708
2750
|
const timer = setTimeout(done, ms);
|
|
2709
2751
|
this.wake = done;
|
|
2710
2752
|
function done() {
|
|
2711
2753
|
clearTimeout(timer);
|
|
2712
|
-
|
|
2754
|
+
resolve3();
|
|
2713
2755
|
}
|
|
2714
2756
|
});
|
|
2715
2757
|
}
|
|
@@ -2748,6 +2790,7 @@ function runnerCommand(globals) {
|
|
|
2748
2790
|
...options.name ? { name: options.name } : existing?.name ? { name: existing.name } : {},
|
|
2749
2791
|
// Re-registering (a rotated secret) keeps the repository map someone built up.
|
|
2750
2792
|
workspaces: existing?.workspaces ?? {},
|
|
2793
|
+
repoRoots: existing?.repoRoots ?? [],
|
|
2751
2794
|
attachments: existing?.attachments ?? { maxCount: DefaultAttachmentMaxCount, maxBytes: DefaultAttachmentMaxBytes }
|
|
2752
2795
|
};
|
|
2753
2796
|
writeRunnerConfig(config);
|
|
@@ -2769,7 +2812,7 @@ Config written to ${runnerConfigPath()}. Start it with \`aictiq runner start\`.`
|
|
|
2769
2812
|
if (options.remove) {
|
|
2770
2813
|
delete config.workspaces[key];
|
|
2771
2814
|
} else {
|
|
2772
|
-
const absolute =
|
|
2815
|
+
const absolute = resolve2(path ?? ".");
|
|
2773
2816
|
if (!existsSync3(absolute))
|
|
2774
2817
|
throw new CliError(`${absolute} does not exist.`, ExitCode.Validation);
|
|
2775
2818
|
config.workspaces[key] = absolute;
|
|
@@ -2779,6 +2822,24 @@ Config written to ${runnerConfigPath()}. Start it with \`aictiq runner start\`.`
|
|
|
2779
2822
|
options.remove ? `Removed the mapping for ${key}.` : `${key} \u2192 ${config.workspaces[key]}`
|
|
2780
2823
|
);
|
|
2781
2824
|
});
|
|
2825
|
+
runner.command("root").description(
|
|
2826
|
+
"Trust the web UI's path hint for local projects whose clone is under this directory"
|
|
2827
|
+
).argument("[path]", "Directory that holds your clones (default: the current directory)").option("--remove", "Stop trusting path hints under this directory").action((path, options) => {
|
|
2828
|
+
const config = requireConfig();
|
|
2829
|
+
const absolute = resolve2(path ?? ".");
|
|
2830
|
+
if (options.remove) {
|
|
2831
|
+
config.repoRoots = config.repoRoots.filter((root) => resolve2(root) !== absolute);
|
|
2832
|
+
} else {
|
|
2833
|
+
if (!existsSync3(absolute))
|
|
2834
|
+
throw new CliError(`${absolute} does not exist.`, ExitCode.Validation);
|
|
2835
|
+
if (!config.repoRoots.some((root) => resolve2(root) === absolute))
|
|
2836
|
+
config.repoRoots.push(absolute);
|
|
2837
|
+
}
|
|
2838
|
+
writeRunnerConfig(config);
|
|
2839
|
+
print(
|
|
2840
|
+
options.remove ? `Path hints under ${absolute} are no longer used.` : `Path hints under ${absolute} are used for projects without a mapping.`
|
|
2841
|
+
);
|
|
2842
|
+
});
|
|
2782
2843
|
runner.command("status").description("Show the registration, detected harnesses and mapped repositories").action(async () => {
|
|
2783
2844
|
const config = requireConfig();
|
|
2784
2845
|
const capabilities = await probe(1);
|
|
@@ -2796,7 +2857,7 @@ Config written to ${runnerConfigPath()}. Start it with \`aictiq runner start\`.`
|
|
|
2796
2857
|
if (error instanceof RunnerHttpError && error.revoked) process.exitCode = ExitCode.Auth;
|
|
2797
2858
|
}
|
|
2798
2859
|
if (globals().json) {
|
|
2799
|
-
printJson({ url: config.url, registration, capabilities, workspaces: config.workspaces, attachments: config.attachments });
|
|
2860
|
+
printJson({ url: config.url, registration, capabilities, workspaces: config.workspaces, repoRoots: config.repoRoots, attachments: config.attachments });
|
|
2800
2861
|
return;
|
|
2801
2862
|
}
|
|
2802
2863
|
print(
|
|
@@ -2824,6 +2885,10 @@ Config written to ${runnerConfigPath()}. Start it with \`aictiq runner start\`.`
|
|
|
2824
2885
|
{ header: "REPOSITORY", value: ([, path]) => path }
|
|
2825
2886
|
])
|
|
2826
2887
|
);
|
|
2888
|
+
print("");
|
|
2889
|
+
print(
|
|
2890
|
+
config.repoRoots.length === 0 ? "No repository roots; path hints from the web UI are not used (`aictiq runner root <path>`)." : renderTable(config.repoRoots, [{ header: "REPOSITORY ROOT", value: (root) => root }])
|
|
2891
|
+
);
|
|
2827
2892
|
});
|
|
2828
2893
|
runner.command("start").description("Poll for runs and execute them until interrupted").option("--parallel <n>", "Runs executed at the same time", "1").option("--keep-workspaces", "Leave each run\u2019s checkout on disk after it finishes").option("--workspace-root <path>", "Where run checkouts are created").action(
|
|
2829
2894
|
async (options) => {
|
|
@@ -2835,28 +2900,32 @@ Config written to ${runnerConfigPath()}. Start it with \`aictiq runner start\`.`
|
|
|
2835
2900
|
const client = new RunnerClient({ baseUrl: config.url, token: config.token });
|
|
2836
2901
|
const local = (message3) => process.stderr.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${message3}
|
|
2837
2902
|
`);
|
|
2838
|
-
const workspaceRoot =
|
|
2903
|
+
const workspaceRoot = resolve2(options.workspaceRoot ?? defaultWorkspaceRoot());
|
|
2839
2904
|
const loop = new RunnerLoop({
|
|
2840
2905
|
client,
|
|
2841
2906
|
parallel,
|
|
2842
2907
|
probe: () => probe(parallel),
|
|
2843
2908
|
local,
|
|
2844
|
-
execute: (run, hello, shutdown) =>
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2909
|
+
execute: (run, hello, shutdown) => {
|
|
2910
|
+
const current = readRunnerConfig() ?? config;
|
|
2911
|
+
return executeRun(run, {
|
|
2912
|
+
client,
|
|
2913
|
+
hello,
|
|
2914
|
+
adapters: harnesses,
|
|
2915
|
+
runnerToken: config.token,
|
|
2916
|
+
shutdown,
|
|
2917
|
+
local,
|
|
2918
|
+
workspace: {
|
|
2919
|
+
root: workspaceRoot,
|
|
2920
|
+
repositories: current.workspaces,
|
|
2921
|
+
repoRoots: current.repoRoots,
|
|
2922
|
+
keep: options.keepWorkspaces === true,
|
|
2923
|
+
mcpServer: mcpServerCommand(),
|
|
2924
|
+
attachmentMaxCount: current.attachments.maxCount,
|
|
2925
|
+
attachmentMaxBytes: current.attachments.maxBytes
|
|
2926
|
+
}
|
|
2927
|
+
});
|
|
2928
|
+
}
|
|
2860
2929
|
});
|
|
2861
2930
|
let interrupts = 0;
|
|
2862
2931
|
const onSignal = (signal2) => {
|
|
@@ -2930,7 +2999,7 @@ async function probe(maxParallel) {
|
|
|
2930
2999
|
};
|
|
2931
3000
|
}
|
|
2932
3001
|
function cliEntry() {
|
|
2933
|
-
return
|
|
3002
|
+
return resolve2(process.argv[1] ?? "aictiq");
|
|
2934
3003
|
}
|
|
2935
3004
|
function mcpServerCommand() {
|
|
2936
3005
|
return { command: process.execPath, args: [cliEntry(), "mcp"] };
|