@vibecodr/cli 0.2.8 → 0.2.10

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.
@@ -42,12 +42,12 @@ async function readStdin() {
42
42
  }
43
43
  return Buffer.concat(chunks).toString("utf8");
44
44
  }
45
- export async function callToolWithRetry(context, toolName, input, allowLogin) {
45
+ export async function callToolWithRetry(context, toolName, input, allowLogin, options) {
46
46
  const { profileName, serverUrl } = await context.tokenManager.resolveProfile(context.globalOptions);
47
47
  const existingSession = await context.tokenManager.getSession(profileName, serverUrl);
48
48
  try {
49
49
  return {
50
- result: await context.runtimeClient.callTool(serverUrl, existingSession?.accessToken, toolName, input),
50
+ result: await context.runtimeClient.callTool(serverUrl, existingSession?.accessToken, toolName, input, options),
51
51
  ...(existingSession ? { session: existingSession } : {})
52
52
  };
53
53
  }
@@ -57,7 +57,7 @@ export async function callToolWithRetry(context, toolName, input, allowLogin) {
57
57
  if (error.machineCode === "auth.required" && existingSession?.refreshToken) {
58
58
  const refreshed = await context.tokenManager.refresh(profileName, existingSession);
59
59
  return {
60
- result: await context.runtimeClient.callTool(serverUrl, refreshed.session.accessToken, toolName, input),
60
+ result: await context.runtimeClient.callTool(serverUrl, refreshed.session.accessToken, toolName, input, options),
61
61
  session: refreshed.session
62
62
  };
63
63
  }
@@ -68,7 +68,7 @@ export async function callToolWithRetry(context, toolName, input, allowLogin) {
68
68
  });
69
69
  const nextSession = await context.tokenManager.getSession(profileName, serverUrl);
70
70
  return {
71
- result: await context.runtimeClient.callTool(serverUrl, nextSession?.accessToken, toolName, input),
71
+ result: await context.runtimeClient.callTool(serverUrl, nextSession?.accessToken, toolName, input, options),
72
72
  ...(nextSession ? { session: nextSession } : {})
73
73
  };
74
74
  }
@@ -98,17 +98,30 @@ async function listToolsWithRetry(context, allowLogin) {
98
98
  throw error;
99
99
  }
100
100
  }
101
+ function parseCallTimeoutSeconds(value) {
102
+ if (value === undefined)
103
+ return undefined;
104
+ if (typeof value !== "string" || !value.trim()) {
105
+ throw new CliError("usage.call_timeout_invalid", "--timeout-sec must be a positive number of seconds.", EXIT_CODES.usage);
106
+ }
107
+ const parsed = Number(value);
108
+ if (!Number.isFinite(parsed) || parsed <= 0) {
109
+ throw new CliError("usage.call_timeout_invalid", "--timeout-sec must be a positive number of seconds.", EXIT_CODES.usage);
110
+ }
111
+ return parsed;
112
+ }
101
113
  export async function runCallCommand(args, context) {
102
- if (showHelpIfRequested(args, context, "Usage: vibecodr call <tool-name> [--input-json <json>] [--input-file <path>] [--stdin] [--interactive] [--no-login] [--confirm]"))
114
+ if (showHelpIfRequested(args, context, "Usage: vibecodr call <tool-name> [--input-json <json>] [--input-file <path>] [--stdin] [--interactive] [--timeout-sec <seconds>] [--no-login] [--confirm]"))
103
115
  return;
104
116
  const { flags, positionals } = parseFlags(args, {
105
- valueFlags: ["input-json", "input-file"],
117
+ valueFlags: ["input-json", "input-file", "timeout-sec"],
106
118
  booleanFlags: ["stdin", "interactive", "no-login", "confirm"]
107
119
  });
108
120
  const toolName = positionals[0];
109
121
  if (!toolName) {
110
122
  throw new CliError("usage.tool_name_required", "A tool name is required.", EXIT_CODES.usage);
111
123
  }
124
+ const timeoutSeconds = parseCallTimeoutSeconds(flags["timeout-sec"]);
112
125
  let input = {};
113
126
  if (typeof flags["input-json"] === "string") {
114
127
  input = JSON.parse(flags["input-json"]);
@@ -133,7 +146,7 @@ export async function runCallCommand(args, context) {
133
146
  if (CONFIRMED_TOOL_NAMES.has(toolName)) {
134
147
  input = { ...input, confirmed: true };
135
148
  }
136
- const { result } = await callToolWithRetry(context, toolName, input, !flags["no-login"]);
149
+ const { result } = await callToolWithRetry(context, toolName, input, !flags["no-login"], timeoutSeconds === undefined ? undefined : { timeoutSeconds });
137
150
  context.output.success({
138
151
  schemaVersion: 1,
139
152
  tool: toolName,
@@ -10,6 +10,7 @@ const CREATE_STAGED_UPLOAD_TOOL_NAME = "create_staged_upload";
10
10
  const COMPLETE_STAGED_UPLOAD_TOOL_NAME = "complete_staged_upload";
11
11
  const ABORT_STAGED_UPLOAD_TOOL_NAME = "abort_staged_upload";
12
12
  const SOURCE_ZIP_CONTENT_TYPE = "application/zip";
13
+ const DEFAULT_STAGED_UPLOAD_TIMEOUT_SECONDS = 600;
13
14
  const SOURCE_ZIP_CONTENT_TYPES = new Set(["application/zip", "application/x-zip-compressed"]);
14
15
  const COVER_IMAGE_CONTENT_TYPES = new Set(["image/png", "image/jpeg", "image/webp", "image/avif"]);
15
16
  const AVATAR_IMAGE_CONTENT_TYPES = new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
@@ -79,7 +80,7 @@ function readCompleteResult(result, fallbackUploadId) {
79
80
  }
80
81
  function parseUploadArgs(args) {
81
82
  const { flags, positionals } = parseFlags(args, {
82
- valueFlags: ["zip", "image", "file", "kind", "content-type", "idempotency-key", "root-hint", "entry-hint"],
83
+ valueFlags: ["zip", "image", "file", "kind", "content-type", "idempotency-key", "root-hint", "entry-hint", "timeout-sec"],
83
84
  booleanFlags: ["no-login"]
84
85
  });
85
86
  const zipPath = readString(flags["zip"]);
@@ -98,6 +99,7 @@ function parseUploadArgs(args) {
98
99
  const rootHint = readString(flags["root-hint"]);
99
100
  const entryHint = readString(flags["entry-hint"]);
100
101
  const contentType = readString(flags["content-type"]);
102
+ const timeoutSeconds = parseUploadTimeoutSeconds(flags["timeout-sec"]);
101
103
  const rawKind = readString(flags["kind"]);
102
104
  let kind = imagePath ? "cover_image" : "source_zip";
103
105
  if (rawKind) {
@@ -116,9 +118,22 @@ function parseUploadArgs(args) {
116
118
  ...(idempotencyKey ? { idempotencyKey } : {}),
117
119
  ...(rootHint ? { rootHint } : {}),
118
120
  ...(entryHint ? { entryHint } : {}),
121
+ timeoutSeconds,
119
122
  allowLogin: flags["no-login"] !== true,
120
123
  };
121
124
  }
125
+ function parseUploadTimeoutSeconds(value) {
126
+ if (value === undefined)
127
+ return DEFAULT_STAGED_UPLOAD_TIMEOUT_SECONDS;
128
+ if (typeof value !== "string" || !value.trim()) {
129
+ throw new CliError("usage.upload_timeout_invalid", "--timeout-sec must be a positive number of seconds.", EXIT_CODES.usage);
130
+ }
131
+ const parsed = Number(value);
132
+ if (!Number.isFinite(parsed) || parsed <= 0) {
133
+ throw new CliError("usage.upload_timeout_invalid", "--timeout-sec must be a positive number of seconds.", EXIT_CODES.usage);
134
+ }
135
+ return parsed;
136
+ }
122
137
  function sha256Hex(bytes) {
123
138
  return createHash("sha256").update(bytes).digest("hex");
124
139
  }
@@ -158,9 +173,9 @@ function toBlob(bytes, contentType) {
158
173
  new Uint8Array(buffer).set(bytes);
159
174
  return new Blob([buffer], { type: contentType });
160
175
  }
161
- async function abortBestEffort(context, uploadId, allowLogin) {
176
+ async function abortBestEffort(context, uploadId, allowLogin, timeoutSeconds) {
162
177
  try {
163
- await callToolWithRetry(context, ABORT_STAGED_UPLOAD_TOOL_NAME, { uploadId }, allowLogin);
178
+ await callToolWithRetry(context, ABORT_STAGED_UPLOAD_TOOL_NAME, { uploadId }, allowLogin, { timeoutSeconds });
164
179
  }
165
180
  catch {
166
181
  // Best-effort cleanup only. Preserve the upload failure as the surfaced error.
@@ -177,7 +192,7 @@ async function putBytesToStagedUpload(input) {
177
192
  }
178
193
  }
179
194
  export async function runUploadCommand(args, context) {
180
- if (showHelpIfRequested(args, context, "Usage: vibecodr upload --zip <path> [--idempotency-key <key>] [--root-hint <path>] [--entry-hint <path>] [--no-login]\n vibecodr upload --image <path> [--kind cover_image|avatar_image] [--content-type <mime>] [--no-login]"))
195
+ if (showHelpIfRequested(args, context, "Usage: vibecodr upload --zip <path> [--idempotency-key <key>] [--root-hint <path>] [--entry-hint <path>] [--timeout-sec <n>] [--no-login]\n vibecodr upload --image <path> [--kind cover_image|avatar_image] [--content-type <mime>] [--timeout-sec <n>] [--no-login]"))
181
196
  return;
182
197
  const input = parseUploadArgs(args);
183
198
  const fileInfo = await stat(input.filePath).catch((error) => {
@@ -193,6 +208,7 @@ export async function runUploadCommand(args, context) {
193
208
  const fileName = basename(input.filePath) || (input.kind === "source_zip" ? "source.zip" : "image");
194
209
  const contentType = inferContentType(input.kind, fileName, input.contentType);
195
210
  const hash = sha256Hex(bytes);
211
+ const mcpRequestOptions = { timeoutSeconds: input.timeoutSeconds };
196
212
  const { result: createResult } = await callToolWithRetry(context, CREATE_STAGED_UPLOAD_TOOL_NAME, {
197
213
  kind: input.kind,
198
214
  fileName,
@@ -201,7 +217,7 @@ export async function runUploadCommand(args, context) {
201
217
  sha256: hash,
202
218
  createdBySurface: input.kind === "source_zip" ? "cli.upload.zip" : "cli.upload.image",
203
219
  ...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
204
- }, input.allowLogin);
220
+ }, input.allowLogin, mcpRequestOptions);
205
221
  const created = readCreateResult(createResult);
206
222
  try {
207
223
  await putBytesToStagedUpload({
@@ -214,7 +230,7 @@ export async function runUploadCommand(args, context) {
214
230
  uploadId: created.uploadId,
215
231
  sizeBytes: bytes.byteLength,
216
232
  sha256: hash,
217
- }, input.allowLogin);
233
+ }, input.allowLogin, mcpRequestOptions);
218
234
  const completed = readCompleteResult(completeResult, created.uploadId);
219
235
  const quickPublishPayload = {
220
236
  ...(input.kind === "source_zip"
@@ -264,7 +280,7 @@ export async function runUploadCommand(args, context) {
264
280
  ]);
265
281
  }
266
282
  catch (error) {
267
- await abortBestEffort(context, created.uploadId, input.allowLogin);
283
+ await abortBestEffort(context, created.uploadId, input.allowLogin, input.timeoutSeconds);
268
284
  throw error;
269
285
  }
270
286
  }
@@ -13,8 +13,8 @@ export const CLIENT_INFO = {
13
13
  name: "vibecodr-mcp",
14
14
  version: packageVersion
15
15
  };
16
- export function resolveToolRequestTimeoutMs(args) {
17
- const raw = args["timeoutSeconds"];
16
+ export function resolveToolRequestTimeoutMs(args, options) {
17
+ const raw = options?.timeoutSeconds ?? args["timeoutSeconds"];
18
18
  if (raw === undefined)
19
19
  return undefined;
20
20
  if (typeof raw !== "number" || !Number.isFinite(raw))
@@ -32,9 +32,9 @@ export class McpRuntimeClient {
32
32
  return result.tools;
33
33
  });
34
34
  }
35
- async callTool(serverUrl, accessToken, name, args) {
35
+ async callTool(serverUrl, accessToken, name, args, options) {
36
36
  return await this.withClient(serverUrl, accessToken, async (client) => {
37
- const timeout = resolveToolRequestTimeoutMs(args);
37
+ const timeout = resolveToolRequestTimeoutMs(args, options);
38
38
  return await client.callTool({
39
39
  name,
40
40
  arguments: args
package/docs/commands.md CHANGED
@@ -58,7 +58,7 @@ This always reads the live tool catalog from the MCP server.
58
58
 
59
59
  Syntax:
60
60
 
61
- `vibecodr call <tool-name> [--input-json <json>] [--input-file <path>] [--stdin] [--interactive] [--no-login] [--confirm]`
61
+ `vibecodr call <tool-name> [--input-json <json>] [--input-file <path>] [--stdin] [--interactive] [--timeout-sec <n>] [--no-login] [--confirm]`
62
62
 
63
63
  `--interactive` currently supports top-level scalar object fields.
64
64
 
@@ -66,13 +66,15 @@ For `quick_publish_creation` with `payload.importMode: "direct_files"`, pass fil
66
66
 
67
67
  Known mutating tools require explicit confirmation through `--confirm`. The CLI redacts secret, token, source, descriptor, and inline file-content fields from displayed arguments and results; the MCP gateway remains the authority boundary for OAuth, owner checks, confirmation, and output shaping.
68
68
 
69
+ Use `--timeout-sec <n>` when a protected tool is expected to run longer than the default client wait, such as a build-backed publish retry. This changes only the local MCP transport timeout and is not forwarded as a server tool argument.
70
+
69
71
  ### `upload`
70
72
 
71
73
  Syntax:
72
74
 
73
- `vibecodr upload --zip <path> [--idempotency-key <key>] [--root-hint <path>] [--entry-hint <path>] [--no-login]`
75
+ `vibecodr upload --zip <path> [--idempotency-key <key>] [--root-hint <path>] [--entry-hint <path>] [--timeout-sec <n>] [--no-login]`
74
76
 
75
- `vibecodr upload --image <path> [--kind cover_image|avatar_image] [--content-type <mime>] [--no-login]`
77
+ `vibecodr upload --image <path> [--kind cover_image|avatar_image] [--content-type <mime>] [--timeout-sec <n>] [--no-login]`
76
78
 
77
79
  Stages a local ZIP or image through Vibecodr's API-owned upload session flow. The CLI asks the MCP gateway for a short-lived direct R2 PUT URL, uploads the bytes directly to R2, completes server-side verification, and prints safe identifiers only.
78
80
 
@@ -80,6 +82,8 @@ ZIP uploads print a `quickPublishPayload` snippet using `payload.importMode: "st
80
82
 
81
83
  Cover images support PNG, JPEG, WebP, and AVIF. Avatar images support PNG, JPEG, WebP, and GIF.
82
84
 
85
+ Staged upload MCP setup and completion calls use a longer client-side wait by default so large ZIP verification does not fail only because the local CLI stopped waiting. Use `--timeout-sec <n>` only when a slower network needs a different local wait; this value is transport behavior and is not forwarded as a server tool argument.
86
+
83
87
  The presigned URL is a bearer credential and is never printed in command output. Legacy `zip_import` / `fileBase64` remains a compatibility path for small payloads, not the preferred CLI path for whole repos or launch images.
84
88
 
85
89
  ### `pulse-setup`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecodr/cli",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Vibecodr CLI for login, live MCP tool discovery, and live tool invocation.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",