@genrupt/cli 0.1.3 → 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 checks auth, installs Genrupt skills for the detected agent, and writes a local runtime manifest. It also tries to install or update the global CLI, but global npm install failures do not block the skills/runtime setup; use the printed `npx` commands when global npm install is restricted on the machine.
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
  ```
package/dist/agent.js CHANGED
@@ -1,48 +1,36 @@
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
- export 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();
@@ -55,21 +43,28 @@ async function ensureGlobalCli(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
- console.log(`Could not install ${CLI_PACKAGE_NAME}@latest globally.\n${message}\n` +
64
- "Continuing with the current npx CLI run.");
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 });
65
51
  return false;
66
52
  }
67
53
  const nextVersion = await getGlobalCliPackageVersion();
68
54
  if (!nextVersion || !isVersionAtLeast(nextVersion, minimumVersion)) {
69
- console.log(`Global ${CLI_PACKAGE_NAME} did not report a usable version after install.\n` +
70
- "Continuing with the current npx CLI run.");
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.");
71
65
  return false;
72
66
  }
67
+ await clearGlobalCliInstallFailure().catch(() => { });
73
68
  console.log(`Genrupt CLI is installed globally (${nextVersion}).`);
74
69
  return true;
75
70
  }
@@ -158,6 +153,9 @@ export async function installAgentRuntime(options = {}) {
158
153
  console.log(` npx -y ${CLI_PACKAGE_NAME}@latest doctor`);
159
154
  console.log(` npx -y ${CLI_PACKAGE_NAME}@latest setup claude-code`);
160
155
  console.log();
161
- console.log(`To repair the global command later: npm install -g ${CLI_PACKAGE_NAME}@latest`);
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`);
162
160
  }
163
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.3";
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,7 +1,7 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { assertLoggedIn } from "./auth.js";
3
3
  import { CLI_VERSION } from "./constants.js";
4
- import { getGlobalCliPackageVersion } from "./agent.js";
4
+ import { getGlobalCliPackageVersion } from "./globalCli.js";
5
5
  import { CLI_PACKAGE_NAME, isVersionAtLeast } from "./version.js";
6
6
  function runClaude(args) {
7
7
  return spawnSync("claude", args, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genrupt/cli",
3
- "version": "0.1.3",
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": {