@genrupt/cli 0.1.2 → 0.1.4
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 +26 -1
- package/dist/agent.js +58 -46
- package/dist/constants.js +1 -1
- package/dist/doctor.js +59 -1
- package/dist/globalCli.js +189 -0
- package/dist/setup.js +25 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,14 +6,37 @@ Genrupt CLI for local-file workflows and local MCP clients.
|
|
|
6
6
|
|
|
7
7
|
Use this when setting up Genrupt for an agent:
|
|
8
8
|
|
|
9
|
+
Windows user-local launcher:
|
|
10
|
+
|
|
11
|
+
```powershell
|
|
12
|
+
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "irm https://genrupt.com/downloads/genrupt-cli/install.ps1 | iex"
|
|
13
|
+
genrupt agent install
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Universal one-time run:
|
|
17
|
+
|
|
9
18
|
```bash
|
|
10
19
|
npx -y @genrupt/cli@latest agent install
|
|
11
20
|
```
|
|
12
21
|
|
|
13
|
-
This
|
|
22
|
+
This checks auth, installs Genrupt skills for the detected agent, and writes a local runtime manifest. The user-local launcher avoids `npm install -g`; the `npx` runtime install still tries to install or update the global CLI, but global npm install failures do not block the skills/runtime setup.
|
|
14
23
|
|
|
15
24
|
## Install
|
|
16
25
|
|
|
26
|
+
User-local launcher without npm global writes:
|
|
27
|
+
|
|
28
|
+
```powershell
|
|
29
|
+
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "irm https://genrupt.com/downloads/genrupt-cli/install.ps1 | iex"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
macOS/Linux:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
curl -fsSL https://genrupt.com/downloads/genrupt-cli/install.sh | sh
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Optional global npm install:
|
|
39
|
+
|
|
17
40
|
```bash
|
|
18
41
|
npm install -g @genrupt/cli
|
|
19
42
|
```
|
|
@@ -52,6 +75,8 @@ genrupt mcp serve
|
|
|
52
75
|
|
|
53
76
|
Claude Code can then call Genrupt from chat while the local process can read user-approved local file paths.
|
|
54
77
|
|
|
78
|
+
If the global `genrupt` command is unavailable, setup registers the bridge through `npx -y @genrupt/cli@latest` so Claude Code can still start it.
|
|
79
|
+
|
|
55
80
|
## Doctor
|
|
56
81
|
|
|
57
82
|
```bash
|
package/dist/agent.js
CHANGED
|
@@ -1,74 +1,72 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { promisify } from "node:util";
|
|
5
1
|
import { getValidConfig, login } from "./auth.js";
|
|
6
2
|
import { DEFAULT_ORIGIN } from "./constants.js";
|
|
3
|
+
import { buildGlobalCliDiagnosticsLines, buildNoGlobalInstallRemediation, clearGlobalCliInstallFailure, collectGlobalCliDiagnostics, getGlobalCliPackageVersion, runCommandDetailed, summarizeCommandFailure, writeGlobalCliInstallFailure, } from "./globalCli.js";
|
|
7
4
|
import { downloadRuntimeManifest, installSkills } from "./skills.js";
|
|
8
5
|
import { classifyCliError, emitRuntimeTelemetry } from "./telemetry.js";
|
|
9
6
|
import { CLI_PACKAGE_NAME, isVersionAtLeast } from "./version.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return process.platform === "win32" ? `${command}.cmd` : command;
|
|
7
|
+
async function installGlobalCli() {
|
|
8
|
+
return runCommandDetailed("npm", ["install", "-g", `${CLI_PACKAGE_NAME}@latest`], 180_000);
|
|
13
9
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return result.stdout.trim();
|
|
10
|
+
function printGlobalCliInstallDiagnostics(params) {
|
|
11
|
+
console.log(`Could not install ${CLI_PACKAGE_NAME}@latest globally.`);
|
|
12
|
+
console.log(summarizeCommandFailure(params.failure));
|
|
13
|
+
if (params.failure.stdout) {
|
|
14
|
+
console.log();
|
|
15
|
+
console.log("npm stdout:");
|
|
16
|
+
console.log(params.failure.stdout);
|
|
22
17
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
throw new Error(`${command} ${args.join(" ")} failed${detail ? `: ${detail}` : ""}`);
|
|
18
|
+
if (params.failure.stderr) {
|
|
19
|
+
console.log();
|
|
20
|
+
console.log("npm stderr:");
|
|
21
|
+
console.log(params.failure.stderr);
|
|
28
22
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
async function getGlobalCliPackageVersion() {
|
|
34
|
-
try {
|
|
35
|
-
const globalRoot = await getGlobalNodeModulesPath();
|
|
36
|
-
const packageJsonPath = path.join(globalRoot, "@genrupt", "cli", "package.json");
|
|
37
|
-
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
38
|
-
return typeof packageJson.version === "string" ? packageJson.version : undefined;
|
|
23
|
+
console.log();
|
|
24
|
+
console.log("Global npm diagnostics:");
|
|
25
|
+
for (const line of buildGlobalCliDiagnosticsLines(params.diagnostics)) {
|
|
26
|
+
console.log(` ${line}`);
|
|
39
27
|
}
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
console.log();
|
|
29
|
+
for (const line of buildNoGlobalInstallRemediation()) {
|
|
30
|
+
console.log(line);
|
|
42
31
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
await runCommand("npm", ["install", "-g", `${CLI_PACKAGE_NAME}@latest`], 180_000);
|
|
32
|
+
console.log();
|
|
33
|
+
console.log("Continuing with the current npx CLI run.");
|
|
46
34
|
}
|
|
47
35
|
async function ensureGlobalCli(minimumVersion) {
|
|
48
36
|
const currentVersion = await getGlobalCliPackageVersion();
|
|
49
37
|
if (currentVersion && isVersionAtLeast(currentVersion, minimumVersion)) {
|
|
50
38
|
console.log(`Genrupt CLI is installed globally (${currentVersion}).`);
|
|
51
|
-
return;
|
|
39
|
+
return true;
|
|
52
40
|
}
|
|
53
41
|
const reason = currentVersion
|
|
54
42
|
? `Global Genrupt CLI ${currentVersion} is below required ${minimumVersion}.`
|
|
55
43
|
: "Genrupt CLI is not installed globally.";
|
|
56
44
|
console.log(reason);
|
|
57
45
|
console.log(`Installing ${CLI_PACKAGE_NAME}@latest globally...`);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
`Run manually: npm install -g ${CLI_PACKAGE_NAME}@latest`);
|
|
46
|
+
const installResult = await installGlobalCli();
|
|
47
|
+
if (!installResult.ok) {
|
|
48
|
+
const diagnostics = await collectGlobalCliDiagnostics();
|
|
49
|
+
await writeGlobalCliInstallFailure(installResult, diagnostics).catch(() => { });
|
|
50
|
+
printGlobalCliInstallDiagnostics({ failure: installResult, diagnostics });
|
|
51
|
+
return false;
|
|
65
52
|
}
|
|
66
53
|
const nextVersion = await getGlobalCliPackageVersion();
|
|
67
54
|
if (!nextVersion || !isVersionAtLeast(nextVersion, minimumVersion)) {
|
|
68
|
-
|
|
69
|
-
|
|
55
|
+
const diagnostics = await collectGlobalCliDiagnostics();
|
|
56
|
+
await writeGlobalCliInstallFailure(installResult, diagnostics).catch(() => { });
|
|
57
|
+
console.log(`Global ${CLI_PACKAGE_NAME} did not report a usable version after install.`);
|
|
58
|
+
console.log();
|
|
59
|
+
console.log("Global npm diagnostics:");
|
|
60
|
+
for (const line of buildGlobalCliDiagnosticsLines(diagnostics)) {
|
|
61
|
+
console.log(` ${line}`);
|
|
62
|
+
}
|
|
63
|
+
console.log();
|
|
64
|
+
console.log("Continuing with the current npx CLI run.");
|
|
65
|
+
return false;
|
|
70
66
|
}
|
|
67
|
+
await clearGlobalCliInstallFailure().catch(() => { });
|
|
71
68
|
console.log(`Genrupt CLI is installed globally (${nextVersion}).`);
|
|
69
|
+
return true;
|
|
72
70
|
}
|
|
73
71
|
async function ensureAuth(options) {
|
|
74
72
|
try {
|
|
@@ -87,6 +85,7 @@ export async function installAgentRuntime(options = {}) {
|
|
|
87
85
|
let stage = "download_runtime";
|
|
88
86
|
let runtimeVersion;
|
|
89
87
|
let requiredCliVersion;
|
|
88
|
+
let globalCliReady = Boolean(options.skipGlobalInstall);
|
|
90
89
|
await emitRuntimeTelemetry({
|
|
91
90
|
eventType: "agent_install_started",
|
|
92
91
|
command: "agent_install",
|
|
@@ -102,7 +101,7 @@ export async function installAgentRuntime(options = {}) {
|
|
|
102
101
|
requiredCliVersion = runtime.requiresCliVersion;
|
|
103
102
|
if (!options.skipGlobalInstall) {
|
|
104
103
|
stage = "global_cli_install";
|
|
105
|
-
await ensureGlobalCli(runtime.requiresCliVersion);
|
|
104
|
+
globalCliReady = await ensureGlobalCli(runtime.requiresCliVersion);
|
|
106
105
|
}
|
|
107
106
|
if (!options.skipAuth) {
|
|
108
107
|
stage = "auth";
|
|
@@ -128,6 +127,7 @@ export async function installAgentRuntime(options = {}) {
|
|
|
128
127
|
stage,
|
|
129
128
|
skipAuth: Boolean(options.skipAuth),
|
|
130
129
|
skipGlobalInstall: Boolean(options.skipGlobalInstall),
|
|
130
|
+
globalCliReady,
|
|
131
131
|
},
|
|
132
132
|
}, { origin });
|
|
133
133
|
throw error;
|
|
@@ -142,8 +142,20 @@ export async function installAgentRuntime(options = {}) {
|
|
|
142
142
|
metadata: {
|
|
143
143
|
skipAuth: Boolean(options.skipAuth),
|
|
144
144
|
skipGlobalInstall: Boolean(options.skipGlobalInstall),
|
|
145
|
+
globalCliReady,
|
|
145
146
|
},
|
|
146
147
|
}, { origin });
|
|
147
148
|
console.log();
|
|
148
149
|
console.log("Genrupt agent runtime is ready.");
|
|
150
|
+
if (!globalCliReady) {
|
|
151
|
+
console.log();
|
|
152
|
+
console.log("Global CLI install was not completed. Use npx commands on this machine:");
|
|
153
|
+
console.log(` npx -y ${CLI_PACKAGE_NAME}@latest doctor`);
|
|
154
|
+
console.log(` npx -y ${CLI_PACKAGE_NAME}@latest setup claude-code`);
|
|
155
|
+
console.log();
|
|
156
|
+
console.log("To install a user-local launcher without npm global writes:");
|
|
157
|
+
console.log(` powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "irm https://genrupt.com/downloads/genrupt-cli/install.ps1 | iex"`);
|
|
158
|
+
console.log();
|
|
159
|
+
console.log(`To repair the global npm command later: npm install -g ${CLI_PACKAGE_NAME}@latest`);
|
|
160
|
+
}
|
|
149
161
|
}
|
package/dist/constants.js
CHANGED
|
@@ -3,5 +3,5 @@ export const DEFAULT_MCP_SERVER_URL = `${DEFAULT_ORIGIN}/api/agent/mcp`;
|
|
|
3
3
|
export const OAUTH_DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
4
4
|
export const OAUTH_SCOPE = "mcp";
|
|
5
5
|
export const CLI_CLIENT_NAME = "Genrupt CLI";
|
|
6
|
-
export const CLI_VERSION = "0.1.
|
|
6
|
+
export const CLI_VERSION = "0.1.4";
|
|
7
7
|
export const ACCESS_TOKEN_REFRESH_SKEW_MS = 60_000;
|
package/dist/doctor.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { getValidConfig } from "./auth.js";
|
|
4
4
|
import { CLI_VERSION } from "./constants.js";
|
|
5
|
+
import { buildNoGlobalInstallRemediation, collectGlobalCliDiagnostics, readGlobalCliInstallFailure, } from "./globalCli.js";
|
|
5
6
|
import { listRemoteMcpTools } from "./mcpClient.js";
|
|
6
7
|
import { GENRUPT_SKILL_NAMES, computeInstalledSkillDigests, defaultTargetForAgent, detectAgent, getInstalledRuntimeManifestPath, readInstalledRuntimeManifest, } from "./skills.js";
|
|
7
8
|
import { emitRuntimeTelemetry } from "./telemetry.js";
|
|
@@ -40,6 +41,56 @@ function buildSkillsCheck(params) {
|
|
|
40
41
|
detail: `Installed for ${params.agent} at ${params.targetRoot}`,
|
|
41
42
|
};
|
|
42
43
|
}
|
|
44
|
+
function buildPackageManagerCheck(diagnostics) {
|
|
45
|
+
if (diagnostics.npmVersion && diagnostics.npxVersion) {
|
|
46
|
+
return {
|
|
47
|
+
label: "Node/npm",
|
|
48
|
+
status: "ok",
|
|
49
|
+
detail: `Node ${diagnostics.nodeVersion}, npm ${diagnostics.npmVersion}, npx ${diagnostics.npxVersion}`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
label: "Node/npm",
|
|
54
|
+
status: "warn",
|
|
55
|
+
detail: `Node ${diagnostics.nodeVersion}; npm ${diagnostics.npmVersion ?? "unavailable"}; npx ${diagnostics.npxVersion ?? "unavailable"}`,
|
|
56
|
+
remediation: "Install Node.js 20 or newer with npm/npx enabled.",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function buildGlobalCliCheck(diagnostics) {
|
|
60
|
+
if (diagnostics.globalCliVersion) {
|
|
61
|
+
if (isVersionNewer(CLI_VERSION, diagnostics.globalCliVersion)) {
|
|
62
|
+
return {
|
|
63
|
+
label: "Global CLI",
|
|
64
|
+
status: "warn",
|
|
65
|
+
detail: `Global ${CLI_PACKAGE_NAME} ${diagnostics.globalCliVersion} is older than this CLI (${CLI_VERSION}); npm prefix ${diagnostics.npmPrefix ?? "unknown"}`,
|
|
66
|
+
remediation: buildNoGlobalInstallRemediation().join(" "),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
label: "Global CLI",
|
|
71
|
+
status: "ok",
|
|
72
|
+
detail: `Global ${CLI_PACKAGE_NAME} ${diagnostics.globalCliVersion}; npm prefix ${diagnostics.npmPrefix ?? "unknown"}`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
label: "Global CLI",
|
|
77
|
+
status: "warn",
|
|
78
|
+
detail: `Global ${CLI_PACKAGE_NAME} is not installed; npm prefix ${diagnostics.npmPrefix ?? "unknown"}; npm global root ${diagnostics.npmGlobalRoot ?? "unknown"}`,
|
|
79
|
+
remediation: buildNoGlobalInstallRemediation().join(" "),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function buildLastGlobalInstallFailureCheck(failure) {
|
|
83
|
+
if (!failure)
|
|
84
|
+
return null;
|
|
85
|
+
const status = failure.status === null ? "unknown" : String(failure.status);
|
|
86
|
+
const output = failure.stderr || failure.stdout || failure.errorMessage || "no output captured";
|
|
87
|
+
return {
|
|
88
|
+
label: "Last global install failure",
|
|
89
|
+
status: "warn",
|
|
90
|
+
detail: `${failure.command} ${failure.args.join(" ")} failed at ${failure.recordedAt} with exit ${status}: ${output}`,
|
|
91
|
+
remediation: buildNoGlobalInstallRemediation().join(" "),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
43
94
|
async function buildRuntimeManifestCheck(targetRoot) {
|
|
44
95
|
const manifest = await readInstalledRuntimeManifest(targetRoot);
|
|
45
96
|
if (!manifest) {
|
|
@@ -106,6 +157,13 @@ export async function runDoctor(options = {}) {
|
|
|
106
157
|
status: "ok",
|
|
107
158
|
detail: `${CLI_PACKAGE_NAME} ${CLI_VERSION}`,
|
|
108
159
|
});
|
|
160
|
+
const globalCliDiagnostics = await collectGlobalCliDiagnostics();
|
|
161
|
+
checks.push(buildPackageManagerCheck(globalCliDiagnostics));
|
|
162
|
+
checks.push(buildGlobalCliCheck(globalCliDiagnostics));
|
|
163
|
+
const lastInstallFailureCheck = buildLastGlobalInstallFailureCheck(await readGlobalCliInstallFailure());
|
|
164
|
+
if (lastInstallFailureCheck) {
|
|
165
|
+
checks.push(lastInstallFailureCheck);
|
|
166
|
+
}
|
|
109
167
|
try {
|
|
110
168
|
const latestVersion = await fetchLatestCliVersion();
|
|
111
169
|
latestCliVersion = latestVersion;
|
|
@@ -115,7 +173,7 @@ export async function runDoctor(options = {}) {
|
|
|
115
173
|
label: "CLI update",
|
|
116
174
|
status: "warn",
|
|
117
175
|
detail: `Latest published CLI is ${latestVersion}; current is ${CLI_VERSION}`,
|
|
118
|
-
remediation: `Run:
|
|
176
|
+
remediation: `Run: npx -y ${CLI_PACKAGE_NAME}@latest agent install`,
|
|
119
177
|
});
|
|
120
178
|
}
|
|
121
179
|
else {
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { getConfigDir } from "./config.js";
|
|
7
|
+
import { CLI_PACKAGE_NAME } from "./version.js";
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
const MAX_CAPTURED_OUTPUT_LENGTH = 4000;
|
|
10
|
+
function resolveCommand(command, args) {
|
|
11
|
+
if (process.platform !== "win32" || (command !== "npm" && command !== "npx")) {
|
|
12
|
+
return { command, args };
|
|
13
|
+
}
|
|
14
|
+
const cliFileName = command === "npm" ? "npm-cli.js" : "npx-cli.js";
|
|
15
|
+
const candidates = [
|
|
16
|
+
path.join(path.dirname(process.execPath), "node_modules", "npm", "bin", cliFileName),
|
|
17
|
+
process.env.ProgramFiles
|
|
18
|
+
? path.join(process.env.ProgramFiles, "nodejs", "node_modules", "npm", "bin", cliFileName)
|
|
19
|
+
: null,
|
|
20
|
+
process.env["ProgramFiles(x86)"]
|
|
21
|
+
? path.join(process.env["ProgramFiles(x86)"], "nodejs", "node_modules", "npm", "bin", cliFileName)
|
|
22
|
+
: null,
|
|
23
|
+
].filter((candidate) => Boolean(candidate));
|
|
24
|
+
const cliPath = candidates.find((candidate) => existsSync(candidate));
|
|
25
|
+
if (cliPath) {
|
|
26
|
+
return {
|
|
27
|
+
command: process.execPath,
|
|
28
|
+
args: [cliPath, ...args],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
command: `${command}.cmd`,
|
|
33
|
+
args,
|
|
34
|
+
shell: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function trimCapturedOutput(value) {
|
|
38
|
+
if (!value)
|
|
39
|
+
return undefined;
|
|
40
|
+
const trimmed = value.trim();
|
|
41
|
+
if (!trimmed)
|
|
42
|
+
return undefined;
|
|
43
|
+
return trimmed.slice(0, MAX_CAPTURED_OUTPUT_LENGTH);
|
|
44
|
+
}
|
|
45
|
+
function normalizeStatus(error) {
|
|
46
|
+
if (error && typeof error === "object") {
|
|
47
|
+
if ("code" in error && typeof error.code === "number")
|
|
48
|
+
return error.code;
|
|
49
|
+
if ("status" in error && typeof error.status === "number")
|
|
50
|
+
return error.status;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
function readErrorString(error, key) {
|
|
55
|
+
if (!error || typeof error !== "object")
|
|
56
|
+
return "";
|
|
57
|
+
const value = error[key];
|
|
58
|
+
return typeof value === "string" ? value : "";
|
|
59
|
+
}
|
|
60
|
+
export async function runCommandDetailed(command, args, timeoutMs = 30_000) {
|
|
61
|
+
const resolved = resolveCommand(command, args);
|
|
62
|
+
try {
|
|
63
|
+
const result = await execFileAsync(resolved.command, resolved.args, {
|
|
64
|
+
encoding: "utf8",
|
|
65
|
+
timeout: timeoutMs,
|
|
66
|
+
windowsHide: true,
|
|
67
|
+
...(resolved.shell ? { shell: resolved.shell } : {}),
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
command,
|
|
71
|
+
args,
|
|
72
|
+
ok: true,
|
|
73
|
+
status: 0,
|
|
74
|
+
stdout: result.stdout.trim(),
|
|
75
|
+
stderr: result.stderr.trim(),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return {
|
|
80
|
+
command,
|
|
81
|
+
args,
|
|
82
|
+
ok: false,
|
|
83
|
+
status: normalizeStatus(error),
|
|
84
|
+
stdout: readErrorString(error, "stdout").trim(),
|
|
85
|
+
stderr: readErrorString(error, "stderr").trim(),
|
|
86
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function getCommandOutput(command, args, timeoutMs = 30_000) {
|
|
91
|
+
const result = await runCommandDetailed(command, args, timeoutMs);
|
|
92
|
+
if (result.ok && result.stdout) {
|
|
93
|
+
return { value: result.stdout };
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
value: undefined,
|
|
97
|
+
error: summarizeCommandFailure(result),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function summarizeCommandFailure(result) {
|
|
101
|
+
const status = result.status === null ? "unknown" : String(result.status);
|
|
102
|
+
const detail = result.stderr || result.stdout || result.errorMessage || "no output";
|
|
103
|
+
return `${result.command} ${result.args.join(" ")} failed (exit ${status}): ${detail}`;
|
|
104
|
+
}
|
|
105
|
+
export async function getGlobalNodeModulesPath() {
|
|
106
|
+
const result = await getCommandOutput("npm", ["root", "-g"]);
|
|
107
|
+
if (!result.value) {
|
|
108
|
+
throw new Error(result.error ?? "npm root -g failed");
|
|
109
|
+
}
|
|
110
|
+
return result.value;
|
|
111
|
+
}
|
|
112
|
+
export async function getGlobalCliPackageVersion() {
|
|
113
|
+
try {
|
|
114
|
+
const globalRoot = await getGlobalNodeModulesPath();
|
|
115
|
+
const packageJsonPath = path.join(globalRoot, "@genrupt", "cli", "package.json");
|
|
116
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
117
|
+
return typeof packageJson.version === "string" ? packageJson.version : undefined;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export async function collectGlobalCliDiagnostics() {
|
|
124
|
+
const [npmVersion, npxVersion, npmPrefix, npmGlobalRoot] = await Promise.all([
|
|
125
|
+
getCommandOutput("npm", ["--version"]),
|
|
126
|
+
getCommandOutput("npx", ["--version"]),
|
|
127
|
+
getCommandOutput("npm", ["config", "get", "prefix"]),
|
|
128
|
+
getCommandOutput("npm", ["root", "-g"]),
|
|
129
|
+
]);
|
|
130
|
+
return {
|
|
131
|
+
nodeVersion: process.version,
|
|
132
|
+
npmVersion: npmVersion.value,
|
|
133
|
+
npmVersionError: npmVersion.error,
|
|
134
|
+
npxVersion: npxVersion.value,
|
|
135
|
+
npxVersionError: npxVersion.error,
|
|
136
|
+
npmPrefix: npmPrefix.value,
|
|
137
|
+
npmPrefixError: npmPrefix.error,
|
|
138
|
+
npmGlobalRoot: npmGlobalRoot.value,
|
|
139
|
+
npmGlobalRootError: npmGlobalRoot.error,
|
|
140
|
+
globalCliVersion: await getGlobalCliPackageVersion(),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function getInstallFailurePath() {
|
|
144
|
+
return path.join(getConfigDir(), "global-cli-install-failure.json");
|
|
145
|
+
}
|
|
146
|
+
export async function writeGlobalCliInstallFailure(result, diagnostics) {
|
|
147
|
+
await mkdir(getConfigDir(), { recursive: true });
|
|
148
|
+
const record = {
|
|
149
|
+
recordedAt: new Date().toISOString(),
|
|
150
|
+
command: result.command,
|
|
151
|
+
args: result.args,
|
|
152
|
+
status: result.status,
|
|
153
|
+
stdout: trimCapturedOutput(result.stdout),
|
|
154
|
+
stderr: trimCapturedOutput(result.stderr),
|
|
155
|
+
errorMessage: trimCapturedOutput(result.errorMessage),
|
|
156
|
+
diagnostics,
|
|
157
|
+
};
|
|
158
|
+
await writeFile(getInstallFailurePath(), `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
159
|
+
}
|
|
160
|
+
export async function readGlobalCliInstallFailure() {
|
|
161
|
+
try {
|
|
162
|
+
return JSON.parse(await readFile(getInstallFailurePath(), "utf8"));
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
export async function clearGlobalCliInstallFailure() {
|
|
169
|
+
await rm(getInstallFailurePath(), { force: true });
|
|
170
|
+
}
|
|
171
|
+
export function buildGlobalCliDiagnosticsLines(diagnostics) {
|
|
172
|
+
return [
|
|
173
|
+
`Node: ${diagnostics.nodeVersion}`,
|
|
174
|
+
`npm: ${diagnostics.npmVersion ?? `unavailable (${diagnostics.npmVersionError ?? "unknown"})`}`,
|
|
175
|
+
`npx: ${diagnostics.npxVersion ?? `unavailable (${diagnostics.npxVersionError ?? "unknown"})`}`,
|
|
176
|
+
`npm prefix: ${diagnostics.npmPrefix ?? `unavailable (${diagnostics.npmPrefixError ?? "unknown"})`}`,
|
|
177
|
+
`npm global root: ${diagnostics.npmGlobalRoot ?? `unavailable (${diagnostics.npmGlobalRootError ?? "unknown"})`}`,
|
|
178
|
+
`global ${CLI_PACKAGE_NAME}: ${diagnostics.globalCliVersion ?? "not installed"}`,
|
|
179
|
+
];
|
|
180
|
+
}
|
|
181
|
+
export function buildNoGlobalInstallRemediation() {
|
|
182
|
+
return [
|
|
183
|
+
"Install the user-local launcher without npm global writes:",
|
|
184
|
+
` powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "irm https://genrupt.com/downloads/genrupt-cli/install.ps1 | iex"`,
|
|
185
|
+
"Or keep using npx:",
|
|
186
|
+
` npx -y ${CLI_PACKAGE_NAME}@latest doctor`,
|
|
187
|
+
"If you want the global npm command, check that npm's global prefix is writable or run the install from an elevated terminal.",
|
|
188
|
+
];
|
|
189
|
+
}
|
package/dist/setup.js
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { assertLoggedIn } from "./auth.js";
|
|
3
|
+
import { CLI_VERSION } from "./constants.js";
|
|
4
|
+
import { getGlobalCliPackageVersion } from "./globalCli.js";
|
|
5
|
+
import { CLI_PACKAGE_NAME, isVersionAtLeast } from "./version.js";
|
|
3
6
|
function runClaude(args) {
|
|
4
7
|
return spawnSync("claude", args, {
|
|
5
8
|
stdio: "inherit",
|
|
6
9
|
shell: process.platform === "win32",
|
|
7
10
|
});
|
|
8
11
|
}
|
|
12
|
+
async function resolveBridgeCommand() {
|
|
13
|
+
const globalVersion = await getGlobalCliPackageVersion();
|
|
14
|
+
if (globalVersion && isVersionAtLeast(globalVersion, CLI_VERSION)) {
|
|
15
|
+
return {
|
|
16
|
+
command: ["genrupt", "mcp", "serve"],
|
|
17
|
+
usingGlobalCli: true,
|
|
18
|
+
globalVersion,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
command: ["npx", "-y", `${CLI_PACKAGE_NAME}@latest`, "mcp", "serve"],
|
|
23
|
+
usingGlobalCli: false,
|
|
24
|
+
globalVersion,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
9
27
|
export async function setupClaudeCode(options = {}) {
|
|
10
28
|
await assertLoggedIn();
|
|
11
29
|
const scope = options.scope ?? "user";
|
|
30
|
+
const bridgeCommand = await resolveBridgeCommand();
|
|
12
31
|
if (options.replace) {
|
|
13
32
|
runClaude(["mcp", "remove", "genrupt"]);
|
|
14
33
|
}
|
|
@@ -21,9 +40,7 @@ export async function setupClaudeCode(options = {}) {
|
|
|
21
40
|
scope,
|
|
22
41
|
"genrupt",
|
|
23
42
|
"--",
|
|
24
|
-
|
|
25
|
-
"mcp",
|
|
26
|
-
"serve",
|
|
43
|
+
...bridgeCommand.command,
|
|
27
44
|
];
|
|
28
45
|
const result = runClaude(args);
|
|
29
46
|
if (result.error || result.status !== 0) {
|
|
@@ -40,5 +57,10 @@ export async function setupClaudeCode(options = {}) {
|
|
|
40
57
|
}
|
|
41
58
|
console.log();
|
|
42
59
|
console.log("Genrupt local MCP bridge is installed for Claude Code.");
|
|
60
|
+
if (!bridgeCommand.usingGlobalCli) {
|
|
61
|
+
console.log(bridgeCommand.globalVersion
|
|
62
|
+
? `Global Genrupt CLI ${bridgeCommand.globalVersion} is below ${CLI_VERSION}, so Claude Code will launch the bridge with npx.`
|
|
63
|
+
: "No usable global Genrupt CLI was found, so Claude Code will launch the bridge with npx.");
|
|
64
|
+
}
|
|
43
65
|
console.log("Start a new Claude Code chat, then ask Genrupt to upload local product files or run a workflow.");
|
|
44
66
|
}
|