@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 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 installs or updates the global CLI, checks auth, installs Genrupt skills for the detected agent, and writes a local runtime manifest.
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
- const execFileAsync = promisify(execFile);
11
- function binName(command) {
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
- async function runCommand(command, args, timeoutMs = 120_000) {
15
- try {
16
- const result = await execFileAsync(binName(command), args, {
17
- encoding: "utf8",
18
- timeout: timeoutMs,
19
- windowsHide: true,
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
- catch (error) {
24
- const detail = error && typeof error === "object" && "stderr" in error && typeof error.stderr === "string"
25
- ? error.stderr.trim()
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
- async function getGlobalNodeModulesPath() {
31
- return runCommand("npm", ["root", "-g"]);
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
- catch {
41
- return undefined;
28
+ console.log();
29
+ for (const line of buildNoGlobalInstallRemediation()) {
30
+ console.log(line);
42
31
  }
43
- }
44
- async function installGlobalCli() {
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
- try {
59
- await installGlobalCli();
60
- }
61
- catch (error) {
62
- const message = error instanceof Error ? error.message : String(error);
63
- throw new Error(`Could not install ${CLI_PACKAGE_NAME}@latest globally.\n${message}\n` +
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
- throw new Error(`Global ${CLI_PACKAGE_NAME} did not report a usable version after install.\n` +
69
- `Run manually: npm install -g ${CLI_PACKAGE_NAME}@latest`);
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.2";
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: npm install -g ${CLI_PACKAGE_NAME}@latest`,
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
- "genrupt",
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genrupt/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Genrupt CLI for OAuth login, local file uploads, and local MCP bridge setup.",
5
5
  "license": "MIT",
6
6
  "repository": {