@genrupt/cli 0.1.0 → 0.1.3

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
@@ -2,6 +2,16 @@
2
2
 
3
3
  Genrupt CLI for local-file workflows and local MCP clients.
4
4
 
5
+ ## Agent Runtime Install
6
+
7
+ Use this when setting up Genrupt for an agent:
8
+
9
+ ```bash
10
+ npx -y @genrupt/cli@latest agent install
11
+ ```
12
+
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.
14
+
5
15
  ## Install
6
16
 
7
17
  ```bash
@@ -42,6 +52,45 @@ genrupt mcp serve
42
52
 
43
53
  Claude Code can then call Genrupt from chat while the local process can read user-approved local file paths.
44
54
 
55
+ If the global `genrupt` command is unavailable, setup registers the bridge through `npx -y @genrupt/cli@latest` so Claude Code can still start it.
56
+
57
+ ## Doctor
58
+
59
+ ```bash
60
+ genrupt doctor
61
+ ```
62
+
63
+ Checks CLI freshness, auth, remote MCP reachability, installed skills, and the local Genrupt runtime manifest.
64
+
65
+ ## Runtime Health
66
+
67
+ The CLI sends small best-effort runtime health events for failed agent installs, stale CLI detection, doctor failures, and upload failures. Events are sanitized and do not include local paths, filenames, prompts, uploaded content, or tokens.
68
+
69
+ To disable runtime health telemetry on a machine:
70
+
71
+ ```bash
72
+ GENRUPT_DISABLE_TELEMETRY=1 genrupt doctor
73
+ ```
74
+
75
+ ## Install Genrupt Skills
76
+
77
+ Skills provide optional enhanced workflow guidance for agents. They use Genrupt MCP as the
78
+ source of truth and use this CLI/local MCP bridge when a workflow needs private files from the
79
+ user's machine.
80
+
81
+ ```bash
82
+ npx -y @genrupt/cli@latest agent install
83
+ ```
84
+
85
+ Skills-only fallback:
86
+
87
+ ```bash
88
+ npx -y skills add https://genrupt.com
89
+ ```
90
+
91
+ The Genrupt CLI still includes `genrupt skills install` as a compatibility fallback, but the
92
+ standard skills installer is the preferred distribution path.
93
+
45
94
  ## Upload Product Reference Images
46
95
 
47
96
  ```bash
@@ -54,10 +103,38 @@ Multiple files are supported:
54
103
  genrupt upload image "./front.jpg" "./detail.png" --asin B07QF3GNS2 --json
55
104
  ```
56
105
 
106
+ Folder paths are supported. The CLI uploads JPEG, PNG, and WebP files directly inside the folder:
107
+
108
+ ```bash
109
+ genrupt upload image "./product-photos" --asin B07QF3GNS2 --json
110
+ ```
111
+
57
112
  The command creates Genrupt product reference assets and returns `sourceAssetIds` that can be used by product reference sheet workflows.
58
113
 
114
+ It also returns `referenceImageUrls`, so the same uploaded images can be passed directly into video tools that expect image reference URLs.
115
+
116
+ ## Upload Reference Media
117
+
118
+ Use this for custom video reference images, videos, and audio. The CLI auto-detects supported media by extension and returns typed URL arrays for agent tools.
119
+
120
+ ```bash
121
+ genrupt upload media "./reference-video.mp4" --json
122
+ genrupt upload video "./reference-clips" --json
123
+ genrupt upload audio "./voiceover.m4a" --json
124
+ ```
125
+
126
+ Supported reference media: JPEG, PNG, WebP, MP4, MOV, WebM, MP3, WAV, M4A, and OGG, up to 50 MB per file. Folder paths include supported files directly inside the folder.
127
+
128
+ Use the JSON output fields with `generate_video`:
129
+
130
+ - `referenceImageUrls` -> `seedanceReferenceImageUrls`
131
+ - `referenceVideoUrls` -> `seedanceReferenceVideoUrls`
132
+ - `referenceAudioUrls` -> `seedanceReferenceAudioUrls`
133
+
59
134
  Remote MCP calls are audited by Genrupt and rate-limited per connected auth source. Product-reference upload capabilities are executed through Genrupt's capability registry, so hidden workflow tools are not directly exposed to stale MCP clients.
60
135
 
136
+ Remote-only MCP clients cannot read local file paths. Use this CLI command or the local MCP bridge when the agent is running on the user's machine; otherwise provide direct public URLs or upload through the Genrupt app.
137
+
61
138
  ## Remote MCP Calls
62
139
 
63
140
  ```bash
package/dist/agent.js ADDED
@@ -0,0 +1,163 @@
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
+ import { getValidConfig, login } from "./auth.js";
6
+ import { DEFAULT_ORIGIN } from "./constants.js";
7
+ import { downloadRuntimeManifest, installSkills } from "./skills.js";
8
+ import { classifyCliError, emitRuntimeTelemetry } from "./telemetry.js";
9
+ 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;
13
+ }
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();
22
+ }
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}` : ""}`);
28
+ }
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;
39
+ }
40
+ catch {
41
+ return undefined;
42
+ }
43
+ }
44
+ async function installGlobalCli() {
45
+ await runCommand("npm", ["install", "-g", `${CLI_PACKAGE_NAME}@latest`], 180_000);
46
+ }
47
+ async function ensureGlobalCli(minimumVersion) {
48
+ const currentVersion = await getGlobalCliPackageVersion();
49
+ if (currentVersion && isVersionAtLeast(currentVersion, minimumVersion)) {
50
+ console.log(`Genrupt CLI is installed globally (${currentVersion}).`);
51
+ return true;
52
+ }
53
+ const reason = currentVersion
54
+ ? `Global Genrupt CLI ${currentVersion} is below required ${minimumVersion}.`
55
+ : "Genrupt CLI is not installed globally.";
56
+ console.log(reason);
57
+ 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.");
65
+ return false;
66
+ }
67
+ const nextVersion = await getGlobalCliPackageVersion();
68
+ 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.");
71
+ return false;
72
+ }
73
+ console.log(`Genrupt CLI is installed globally (${nextVersion}).`);
74
+ return true;
75
+ }
76
+ async function ensureAuth(options) {
77
+ try {
78
+ const config = await getValidConfig();
79
+ console.log(`Genrupt CLI is authenticated (${config.origin}).`);
80
+ return;
81
+ }
82
+ catch {
83
+ console.log("Genrupt CLI is not authenticated.");
84
+ }
85
+ await login({ origin: options.origin, noOpen: options.noOpen });
86
+ }
87
+ export async function installAgentRuntime(options = {}) {
88
+ const origin = (options.origin ?? DEFAULT_ORIGIN).replace(/\/$/, "");
89
+ const startedAt = Date.now();
90
+ let stage = "download_runtime";
91
+ let runtimeVersion;
92
+ let requiredCliVersion;
93
+ let globalCliReady = Boolean(options.skipGlobalInstall);
94
+ await emitRuntimeTelemetry({
95
+ eventType: "agent_install_started",
96
+ command: "agent_install",
97
+ agent: options.agent,
98
+ metadata: {
99
+ skipAuth: Boolean(options.skipAuth),
100
+ skipGlobalInstall: Boolean(options.skipGlobalInstall),
101
+ },
102
+ }, { origin });
103
+ try {
104
+ const runtime = await downloadRuntimeManifest(origin);
105
+ runtimeVersion = runtime.version;
106
+ requiredCliVersion = runtime.requiresCliVersion;
107
+ if (!options.skipGlobalInstall) {
108
+ stage = "global_cli_install";
109
+ globalCliReady = await ensureGlobalCli(runtime.requiresCliVersion);
110
+ }
111
+ if (!options.skipAuth) {
112
+ stage = "auth";
113
+ await ensureAuth({ origin, noOpen: options.noOpen });
114
+ }
115
+ stage = "skills_install";
116
+ await installSkills({
117
+ agent: options.agent,
118
+ target: options.target,
119
+ origin,
120
+ });
121
+ }
122
+ catch (error) {
123
+ await emitRuntimeTelemetry({
124
+ eventType: "agent_install_failed",
125
+ command: "agent_install",
126
+ agent: options.agent,
127
+ runtimeVersion,
128
+ requiredCliVersion,
129
+ errorCode: classifyCliError(error, "AGENT_INSTALL_FAILED"),
130
+ durationMs: Date.now() - startedAt,
131
+ metadata: {
132
+ stage,
133
+ skipAuth: Boolean(options.skipAuth),
134
+ skipGlobalInstall: Boolean(options.skipGlobalInstall),
135
+ globalCliReady,
136
+ },
137
+ }, { origin });
138
+ throw error;
139
+ }
140
+ await emitRuntimeTelemetry({
141
+ eventType: "agent_install_succeeded",
142
+ command: "agent_install",
143
+ agent: options.agent,
144
+ runtimeVersion,
145
+ requiredCliVersion,
146
+ durationMs: Date.now() - startedAt,
147
+ metadata: {
148
+ skipAuth: Boolean(options.skipAuth),
149
+ skipGlobalInstall: Boolean(options.skipGlobalInstall),
150
+ globalCliReady,
151
+ },
152
+ }, { origin });
153
+ console.log();
154
+ console.log("Genrupt agent runtime is ready.");
155
+ if (!globalCliReady) {
156
+ console.log();
157
+ console.log("Global CLI install was not completed. Use npx commands on this machine:");
158
+ console.log(` npx -y ${CLI_PACKAGE_NAME}@latest doctor`);
159
+ console.log(` npx -y ${CLI_PACKAGE_NAME}@latest setup claude-code`);
160
+ console.log();
161
+ console.log(`To repair the global command later: npm install -g ${CLI_PACKAGE_NAME}@latest`);
162
+ }
163
+ }
package/dist/cli.js CHANGED
@@ -1,11 +1,42 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { CLI_VERSION } from "./constants.js";
4
+ import { installAgentRuntime } from "./agent.js";
4
5
  import { login, logout, printAuthStatus } from "./auth.js";
5
6
  import { callRemoteMcpTool, assertSuccessfulToolPayload } from "./mcpClient.js";
6
7
  import { serveLocalMcp } from "./localMcpServer.js";
7
8
  import { setupClaudeCode } from "./setup.js";
8
- import { uploadProductReferenceImages } from "./upload.js";
9
+ import { installSkills } from "./skills.js";
10
+ import { classifyCliError, emitRuntimeTelemetry } from "./telemetry.js";
11
+ import { maybeWarnAboutCliUpdate } from "./updateCheck.js";
12
+ import { uploadProductReferenceImages, uploadReferenceMedia } from "./upload.js";
13
+ import { runDoctor } from "./doctor.js";
14
+ import { CLI_PACKAGE_NAME, fetchLatestCliVersion, isVersionNewer } from "./version.js";
15
+ const UNKNOWN_COMMAND_PREFIX = "Unknown";
16
+ async function buildUnknownCommandUpdateHint() {
17
+ try {
18
+ const latestVersion = await fetchLatestCliVersion();
19
+ if (!latestVersion || !isVersionNewer(latestVersion, CLI_VERSION)) {
20
+ return "";
21
+ }
22
+ const attemptedArgs = process.argv.slice(2).join(" ").trim();
23
+ const npxCommand = attemptedArgs
24
+ ? `npx -y ${CLI_PACKAGE_NAME}@latest ${attemptedArgs}`
25
+ : `npx -y ${CLI_PACKAGE_NAME}@latest --help`;
26
+ await emitRuntimeTelemetry({
27
+ eventType: "stale_cli_detected",
28
+ command: "unknown_command",
29
+ latestCliVersion: latestVersion,
30
+ metadata: {
31
+ attemptedCommand: process.argv[2] ?? "unknown",
32
+ },
33
+ });
34
+ return `\n\nA newer Genrupt CLI is available: ${latestVersion} (installed: ${CLI_VERSION}).\nUpdate your global install:\n npm install -g ${CLI_PACKAGE_NAME}@latest\nOr run the latest CLI once:\n ${npxCommand}`;
35
+ }
36
+ catch {
37
+ return "";
38
+ }
39
+ }
9
40
  function parseFlags(args) {
10
41
  const flags = {};
11
42
  const positionals = [];
@@ -45,10 +76,17 @@ Usage:
45
76
  genrupt auth login [--origin https://genrupt.com] [--no-open]
46
77
  genrupt auth status
47
78
  genrupt auth logout [--local]
79
+ genrupt version
80
+
81
+ genrupt agent install [--agent claude|codex|cursor] [--target PATH] [--skip-auth] [--skip-global-install]
82
+ genrupt doctor [--agent claude|codex|cursor] [--target PATH] [--json]
48
83
 
49
84
  genrupt setup claude-code [--replace] [--scope user|project|local]
50
85
 
51
- genrupt upload image <path...> [--asin ASIN] [--project-id ID] [--product-profile-id ID] [--domain amazon.com] [--json]
86
+ genrupt upload media <path-or-folder...> [--project-id ID] [--json]
87
+ genrupt upload video <path-or-folder...> [--project-id ID] [--json]
88
+ genrupt upload audio <path-or-folder...> [--project-id ID] [--json]
89
+ genrupt upload image <path-or-folder...> [--asin ASIN] [--project-id ID] [--product-profile-id ID] [--domain amazon.com] [--json]
52
90
 
53
91
  genrupt mcp serve
54
92
  genrupt mcp call <tool_name> '<json_args>'
@@ -56,12 +94,45 @@ Usage:
56
94
  genrupt mcp call <tool_name> --json-file ./args.json
57
95
 
58
96
  Examples:
97
+ npx -y @genrupt/cli@latest agent install
59
98
  genrupt auth login
99
+ genrupt doctor
60
100
  genrupt setup claude-code
61
- genrupt upload image "G:\\My Drive\\product.jpg" --asin B07QF3GNS2
101
+ genrupt upload media "G:\\My Drive\\product-video.mp4" --json
102
+ genrupt upload video "G:\\My Drive\\reference-clips" --json
103
+ genrupt upload image "G:\\My Drive\\product-photos" --asin B07QF3GNS2 --json
62
104
  genrupt mcp call search_capabilities --query "product reference upload" --includeUnavailable --limit 10
63
105
  `);
64
106
  }
107
+ async function handleAgent(args) {
108
+ const subcommand = args[0];
109
+ const { flags } = parseFlags(args.slice(1));
110
+ if (subcommand === "install" || subcommand === "update") {
111
+ await installAgentRuntime({
112
+ agent: getStringFlag(flags, "agent"),
113
+ target: getStringFlag(flags, "target"),
114
+ origin: getStringFlag(flags, "origin"),
115
+ skipAuth: hasFlag(flags, "skip-auth"),
116
+ skipGlobalInstall: hasFlag(flags, "skip-global-install"),
117
+ noOpen: hasFlag(flags, "no-open"),
118
+ });
119
+ return;
120
+ }
121
+ throw new Error("Unknown agent command. Run `genrupt --help`.");
122
+ }
123
+ async function handleSkills(args) {
124
+ const subcommand = args[0];
125
+ const { flags } = parseFlags(args.slice(1));
126
+ if (subcommand === "install") {
127
+ await installSkills({
128
+ agent: getStringFlag(flags, "agent"),
129
+ target: getStringFlag(flags, "target"),
130
+ origin: getStringFlag(flags, "origin"),
131
+ });
132
+ return;
133
+ }
134
+ throw new Error("Unknown skills command. Run `genrupt --help`.");
135
+ }
65
136
  async function handleAuth(args) {
66
137
  const subcommand = args[0];
67
138
  const { flags } = parseFlags(args.slice(1));
@@ -102,10 +173,55 @@ async function handleSetup(args) {
102
173
  }
103
174
  async function handleUpload(args) {
104
175
  const type = args[0];
105
- if (type !== "image") {
106
- throw new Error("Only `genrupt upload image` is supported.");
176
+ const startedAt = Date.now();
177
+ try {
178
+ await runUpload(args);
179
+ }
180
+ catch (error) {
181
+ await emitRuntimeTelemetry({
182
+ eventType: "upload_failed",
183
+ command: `upload_${type ?? "unknown"}`,
184
+ errorCode: classifyCliError(error, "UPLOAD_FAILED"),
185
+ durationMs: Date.now() - startedAt,
186
+ metadata: {
187
+ uploadType: type ?? "unknown",
188
+ },
189
+ });
190
+ throw error;
107
191
  }
192
+ }
193
+ async function runUpload(args) {
194
+ const type = args[0];
108
195
  const { flags, positionals } = parseFlags(args.slice(1));
196
+ if (type === "media" || type === "video" || type === "audio") {
197
+ const result = await uploadReferenceMedia({
198
+ paths: positionals,
199
+ mode: type,
200
+ projectId: getStringFlag(flags, "project-id"),
201
+ });
202
+ if (hasFlag(flags, "json")) {
203
+ console.log(JSON.stringify(result, null, 2));
204
+ return;
205
+ }
206
+ console.log();
207
+ console.log(`Uploaded ${result.uploads.length} media file${result.uploads.length === 1 ? "" : "s"}.`);
208
+ if (result.referenceImageUrls.length > 0) {
209
+ console.log(`referenceImageUrls: ${result.referenceImageUrls.join(", ")}`);
210
+ }
211
+ if (result.referenceVideoUrls.length > 0) {
212
+ console.log(`referenceVideoUrls: ${result.referenceVideoUrls.join(", ")}`);
213
+ }
214
+ if (result.referenceAudioUrls.length > 0) {
215
+ console.log(`referenceAudioUrls: ${result.referenceAudioUrls.join(", ")}`);
216
+ }
217
+ for (const upload of result.uploads) {
218
+ console.log(`- ${upload.fileName} (${upload.mediaKind}): ${upload.url}`);
219
+ }
220
+ return;
221
+ }
222
+ if (type !== "image") {
223
+ throw new Error("Unknown upload command. Use `genrupt upload media`, `video`, `audio`, or `image`.");
224
+ }
109
225
  const result = await uploadProductReferenceImages({
110
226
  paths: positionals,
111
227
  asin: getStringFlag(flags, "asin"),
@@ -120,6 +236,7 @@ async function handleUpload(args) {
120
236
  console.log();
121
237
  console.log(`Uploaded ${result.uploads.length} image${result.uploads.length === 1 ? "" : "s"}.`);
122
238
  console.log(`sourceAssetIds: ${result.sourceAssetIds.join(", ") || "(none returned)"}`);
239
+ console.log(`referenceImageUrls: ${result.referenceImageUrls.join(", ") || "(none returned)"}`);
123
240
  for (const upload of result.uploads) {
124
241
  console.log(`- ${upload.fileName}: ${upload.assetId ?? "no asset id"} ${upload.imageUrl}`);
125
242
  }
@@ -178,14 +295,14 @@ async function handleMcp(args) {
178
295
  }
179
296
  throw new Error("Unknown mcp command. Run `genrupt --help`.");
180
297
  }
181
- async function main() {
298
+ async function runCommand() {
182
299
  const args = process.argv.slice(2);
183
300
  const command = args[0];
184
301
  if (!command || command === "--help" || command === "-h") {
185
302
  printHelp();
186
303
  return;
187
304
  }
188
- if (command === "--version" || command === "-v") {
305
+ if (command === "--version" || command === "-v" || command === "version") {
189
306
  console.log(CLI_VERSION);
190
307
  return;
191
308
  }
@@ -193,10 +310,27 @@ async function main() {
193
310
  await handleAuth(args.slice(1));
194
311
  return;
195
312
  }
313
+ if (command === "agent") {
314
+ await handleAgent(args.slice(1));
315
+ return;
316
+ }
317
+ if (command === "doctor") {
318
+ const { flags } = parseFlags(args.slice(1));
319
+ await runDoctor({
320
+ agent: getStringFlag(flags, "agent"),
321
+ target: getStringFlag(flags, "target"),
322
+ json: hasFlag(flags, "json"),
323
+ });
324
+ return;
325
+ }
196
326
  if (command === "setup") {
197
327
  await handleSetup(args.slice(1));
198
328
  return;
199
329
  }
330
+ if (command === "skills") {
331
+ await handleSkills(args.slice(1));
332
+ return;
333
+ }
200
334
  if (command === "upload") {
201
335
  await handleUpload(args.slice(1));
202
336
  return;
@@ -207,7 +341,24 @@ async function main() {
207
341
  }
208
342
  throw new Error(`Unknown command: ${command}. Run \`genrupt --help\`.`);
209
343
  }
210
- main().catch((error) => {
211
- console.error(error instanceof Error ? error.message : String(error));
344
+ async function main() {
345
+ await runCommand();
346
+ const command = process.argv[2];
347
+ if (command &&
348
+ command !== "--help" &&
349
+ command !== "-h" &&
350
+ command !== "--version" &&
351
+ command !== "-v" &&
352
+ command !== "version" &&
353
+ command !== "doctor") {
354
+ await maybeWarnAboutCliUpdate();
355
+ }
356
+ }
357
+ main().catch(async (error) => {
358
+ const message = error instanceof Error ? error.message : String(error);
359
+ const updateHint = message.startsWith(UNKNOWN_COMMAND_PREFIX)
360
+ ? await buildUnknownCommandUpdateHint()
361
+ : "";
362
+ console.error(`${message}${updateHint}`);
212
363
  process.exit(1);
213
364
  });
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.0";
6
+ export const CLI_VERSION = "0.1.3";
7
7
  export const ACCESS_TOKEN_REFRESH_SKEW_MS = 60_000;