@vibecodr/cli 0.2.2 → 0.2.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 +8 -6
- package/dist/bin/vibecodr-mcp.js +6 -0
- package/dist/commands/upload.js +270 -0
- package/dist/core/redaction.js +3 -0
- package/docs/commands.md +22 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,9 +12,10 @@ This repository is intentionally separate from the PolyForm-licensed server impl
|
|
|
12
12
|
The CLI is the permissively licensed public client surface for:
|
|
13
13
|
|
|
14
14
|
- direct CLI OAuth login
|
|
15
|
-
- live MCP tool discovery
|
|
16
|
-
- live MCP tool invocation
|
|
17
|
-
-
|
|
15
|
+
- live MCP tool discovery
|
|
16
|
+
- live MCP tool invocation
|
|
17
|
+
- direct-to-R2 staged ZIP and image uploads without base64 payloads
|
|
18
|
+
- environment and auth diagnostics
|
|
18
19
|
- thin client install and uninstall adapters
|
|
19
20
|
|
|
20
21
|
Currently implemented command surface:
|
|
@@ -22,9 +23,10 @@ Currently implemented command surface:
|
|
|
22
23
|
- `login`
|
|
23
24
|
- `logout`
|
|
24
25
|
- `status`
|
|
25
|
-
- `tools`
|
|
26
|
-
- `call`
|
|
27
|
-
- `
|
|
26
|
+
- `tools`
|
|
27
|
+
- `call`
|
|
28
|
+
- `upload`
|
|
29
|
+
- `pulse-setup`
|
|
28
30
|
- `pulse-publish`
|
|
29
31
|
- `pulse`
|
|
30
32
|
- `doctor`
|
package/dist/bin/vibecodr-mcp.js
CHANGED
|
@@ -18,6 +18,7 @@ import { runUninstallCommand } from "../commands/uninstall.js";
|
|
|
18
18
|
import { runPulseSetupCommand } from "../commands/pulse-setup.js";
|
|
19
19
|
import { runPulsePublishCommand } from "../commands/pulse-publish.js";
|
|
20
20
|
import { runPulseCommand } from "../commands/pulse.js";
|
|
21
|
+
import { runUploadCommand } from "../commands/upload.js";
|
|
21
22
|
function helpText() {
|
|
22
23
|
return [
|
|
23
24
|
"vibecodr <command> [options]",
|
|
@@ -29,6 +30,8 @@ function helpText() {
|
|
|
29
30
|
" status",
|
|
30
31
|
" tools [tool-name]",
|
|
31
32
|
" call <tool-name>",
|
|
33
|
+
" upload --zip <path>",
|
|
34
|
+
" upload --image <path> [--kind cover_image|avatar_image]",
|
|
32
35
|
" doctor",
|
|
33
36
|
" install <client>",
|
|
34
37
|
" uninstall <client>",
|
|
@@ -88,6 +91,9 @@ async function main() {
|
|
|
88
91
|
case "call":
|
|
89
92
|
await runCallCommand(commandArgs, context);
|
|
90
93
|
return;
|
|
94
|
+
case "upload":
|
|
95
|
+
await runUploadCommand(commandArgs, context);
|
|
96
|
+
return;
|
|
91
97
|
case "doctor":
|
|
92
98
|
await runDoctorCommand(commandArgs, context);
|
|
93
99
|
return;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { basename } from "node:path";
|
|
4
|
+
import { CliError, EXIT_CODES } from "../cli/errors.js";
|
|
5
|
+
import { parseFlags } from "../cli/parse.js";
|
|
6
|
+
import { redactForOutput } from "../core/redaction.js";
|
|
7
|
+
import { callToolWithRetry } from "./call.js";
|
|
8
|
+
import { showHelpIfRequested } from "./help.js";
|
|
9
|
+
const CREATE_STAGED_UPLOAD_TOOL_NAME = "create_staged_upload";
|
|
10
|
+
const COMPLETE_STAGED_UPLOAD_TOOL_NAME = "complete_staged_upload";
|
|
11
|
+
const ABORT_STAGED_UPLOAD_TOOL_NAME = "abort_staged_upload";
|
|
12
|
+
const SOURCE_ZIP_CONTENT_TYPE = "application/zip";
|
|
13
|
+
const SOURCE_ZIP_CONTENT_TYPES = new Set(["application/zip", "application/x-zip-compressed"]);
|
|
14
|
+
const COVER_IMAGE_CONTENT_TYPES = new Set(["image/png", "image/jpeg", "image/webp", "image/avif"]);
|
|
15
|
+
const AVATAR_IMAGE_CONTENT_TYPES = new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
|
|
16
|
+
const COVER_IMAGE_CONTENT_TYPE_HELP = "image/png, image/jpeg, image/webp, or image/avif";
|
|
17
|
+
const AVATAR_IMAGE_CONTENT_TYPE_HELP = "image/png, image/jpeg, image/webp, or image/gif";
|
|
18
|
+
const IMAGE_CONTENT_TYPE_BY_EXT = new Map([
|
|
19
|
+
[".png", "image/png"],
|
|
20
|
+
[".jpg", "image/jpeg"],
|
|
21
|
+
[".jpeg", "image/jpeg"],
|
|
22
|
+
[".webp", "image/webp"],
|
|
23
|
+
[".avif", "image/avif"],
|
|
24
|
+
[".gif", "image/gif"],
|
|
25
|
+
]);
|
|
26
|
+
function readObject(value, label) {
|
|
27
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
28
|
+
throw new CliError("mcp.staged_upload_contract", `${label} was missing from the MCP response.`, EXIT_CODES.protocol);
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function readString(value) {
|
|
33
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
34
|
+
}
|
|
35
|
+
function readNumber(value) {
|
|
36
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
37
|
+
}
|
|
38
|
+
function readHeaders(value) {
|
|
39
|
+
const raw = readObject(value, "staged upload PUT headers");
|
|
40
|
+
const headers = {};
|
|
41
|
+
for (const [key, nested] of Object.entries(raw)) {
|
|
42
|
+
if (typeof nested === "string")
|
|
43
|
+
headers[key] = nested;
|
|
44
|
+
}
|
|
45
|
+
return headers;
|
|
46
|
+
}
|
|
47
|
+
function readCreateResult(result) {
|
|
48
|
+
const toolResult = readObject(result, "create_staged_upload result");
|
|
49
|
+
const structuredContent = readObject(toolResult["structuredContent"], "create_staged_upload structuredContent");
|
|
50
|
+
const meta = readObject(toolResult["_meta"], "create_staged_upload metadata");
|
|
51
|
+
const directPut = readObject(meta["stagedUploadDirectPut"], "staged upload direct PUT metadata");
|
|
52
|
+
const uploadId = readString(structuredContent["uploadId"]);
|
|
53
|
+
const fileName = readString(structuredContent["fileName"]);
|
|
54
|
+
const contentType = readString(structuredContent["contentType"]);
|
|
55
|
+
const sizeBytes = readNumber(structuredContent["sizeBytes"]);
|
|
56
|
+
const presignedUrl = readString(directPut["presignedUrl"]);
|
|
57
|
+
if (!uploadId || !fileName || !contentType || sizeBytes === undefined || !presignedUrl) {
|
|
58
|
+
throw new CliError("mcp.staged_upload_contract", "create_staged_upload returned an incomplete staged upload contract.", EXIT_CODES.protocol);
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
uploadId,
|
|
62
|
+
fileName,
|
|
63
|
+
contentType,
|
|
64
|
+
sizeBytes,
|
|
65
|
+
directPut: {
|
|
66
|
+
presignedUrl,
|
|
67
|
+
headers: readHeaders(directPut["headers"] ?? {}),
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function readCompleteResult(result, fallbackUploadId) {
|
|
72
|
+
const toolResult = readObject(result, "complete_staged_upload result");
|
|
73
|
+
const structuredContent = readObject(toolResult["structuredContent"], "complete_staged_upload structuredContent");
|
|
74
|
+
return {
|
|
75
|
+
uploadId: readString(structuredContent["uploadId"]) ?? fallbackUploadId,
|
|
76
|
+
status: readString(structuredContent["status"]) ?? "verified",
|
|
77
|
+
sha256: readString(structuredContent["sha256"]) ?? "",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function parseUploadArgs(args) {
|
|
81
|
+
const { flags, positionals } = parseFlags(args, {
|
|
82
|
+
valueFlags: ["zip", "image", "file", "kind", "content-type", "idempotency-key", "root-hint", "entry-hint"],
|
|
83
|
+
booleanFlags: ["no-login"]
|
|
84
|
+
});
|
|
85
|
+
const zipPath = readString(flags["zip"]);
|
|
86
|
+
const imagePath = readString(flags["image"]);
|
|
87
|
+
if (zipPath && imagePath) {
|
|
88
|
+
throw new CliError("usage.upload_single_source", "Use either --zip or --image, not both.", EXIT_CODES.usage);
|
|
89
|
+
}
|
|
90
|
+
const filePath = zipPath ?? imagePath ?? readString(flags["file"]) ?? readString(positionals[0]);
|
|
91
|
+
if (!filePath) {
|
|
92
|
+
throw new CliError("usage.upload_zip_required", "Usage: vibecodr upload --zip <path> or vibecodr upload --image <path> [--kind cover_image|avatar_image]", EXIT_CODES.usage);
|
|
93
|
+
}
|
|
94
|
+
if (positionals.length > 1) {
|
|
95
|
+
throw new CliError("usage.unexpected_argument", `Unexpected argument: ${positionals[1]}`, EXIT_CODES.usage);
|
|
96
|
+
}
|
|
97
|
+
const idempotencyKey = readString(flags["idempotency-key"]);
|
|
98
|
+
const rootHint = readString(flags["root-hint"]);
|
|
99
|
+
const entryHint = readString(flags["entry-hint"]);
|
|
100
|
+
const contentType = readString(flags["content-type"]);
|
|
101
|
+
const rawKind = readString(flags["kind"]);
|
|
102
|
+
let kind = imagePath ? "cover_image" : "source_zip";
|
|
103
|
+
if (rawKind) {
|
|
104
|
+
if (rawKind !== "source_zip" && rawKind !== "cover_image" && rawKind !== "avatar_image") {
|
|
105
|
+
throw new CliError("usage.upload_kind_invalid", "--kind must be source_zip, cover_image, or avatar_image.", EXIT_CODES.usage);
|
|
106
|
+
}
|
|
107
|
+
kind = rawKind;
|
|
108
|
+
}
|
|
109
|
+
if (imagePath && kind === "source_zip") {
|
|
110
|
+
throw new CliError("usage.upload_kind_invalid", "--image must use --kind cover_image or --kind avatar_image.", EXIT_CODES.usage);
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
filePath,
|
|
114
|
+
kind,
|
|
115
|
+
...(contentType ? { contentType } : {}),
|
|
116
|
+
...(idempotencyKey ? { idempotencyKey } : {}),
|
|
117
|
+
...(rootHint ? { rootHint } : {}),
|
|
118
|
+
...(entryHint ? { entryHint } : {}),
|
|
119
|
+
allowLogin: flags["no-login"] !== true,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function sha256Hex(bytes) {
|
|
123
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
124
|
+
}
|
|
125
|
+
function normalizeContentType(value) {
|
|
126
|
+
return value.split(";")[0]?.trim().toLowerCase() || "";
|
|
127
|
+
}
|
|
128
|
+
function assertContentTypeForKind(kind, contentType) {
|
|
129
|
+
const normalized = normalizeContentType(contentType);
|
|
130
|
+
if (kind === "source_zip") {
|
|
131
|
+
if (!SOURCE_ZIP_CONTENT_TYPES.has(normalized)) {
|
|
132
|
+
throw new CliError("usage.upload_content_type_invalid", "ZIP uploads must use application/zip or application/x-zip-compressed.", EXIT_CODES.usage);
|
|
133
|
+
}
|
|
134
|
+
return normalized;
|
|
135
|
+
}
|
|
136
|
+
const allowed = kind === "cover_image" ? COVER_IMAGE_CONTENT_TYPES : AVATAR_IMAGE_CONTENT_TYPES;
|
|
137
|
+
if (!allowed.has(normalized)) {
|
|
138
|
+
throw new CliError("usage.upload_content_type_invalid", kind === "cover_image"
|
|
139
|
+
? `Cover images must use ${COVER_IMAGE_CONTENT_TYPE_HELP}.`
|
|
140
|
+
: `Avatar images must use ${AVATAR_IMAGE_CONTENT_TYPE_HELP}.`, EXIT_CODES.usage);
|
|
141
|
+
}
|
|
142
|
+
return normalized;
|
|
143
|
+
}
|
|
144
|
+
function inferContentType(kind, fileName, override) {
|
|
145
|
+
if (override)
|
|
146
|
+
return assertContentTypeForKind(kind, override);
|
|
147
|
+
if (kind === "source_zip")
|
|
148
|
+
return assertContentTypeForKind(kind, SOURCE_ZIP_CONTENT_TYPE);
|
|
149
|
+
const lower = fileName.toLowerCase();
|
|
150
|
+
for (const [ext, contentType] of IMAGE_CONTENT_TYPE_BY_EXT) {
|
|
151
|
+
if (lower.endsWith(ext))
|
|
152
|
+
return assertContentTypeForKind(kind, contentType);
|
|
153
|
+
}
|
|
154
|
+
throw new CliError("usage.upload_image_type_required", `Could not infer image content type. Cover images support ${COVER_IMAGE_CONTENT_TYPE_HELP}; avatar images support ${AVATAR_IMAGE_CONTENT_TYPE_HELP}.`, EXIT_CODES.usage);
|
|
155
|
+
}
|
|
156
|
+
function toBlob(bytes, contentType) {
|
|
157
|
+
const buffer = new ArrayBuffer(bytes.byteLength);
|
|
158
|
+
new Uint8Array(buffer).set(bytes);
|
|
159
|
+
return new Blob([buffer], { type: contentType });
|
|
160
|
+
}
|
|
161
|
+
async function abortBestEffort(context, uploadId, allowLogin) {
|
|
162
|
+
try {
|
|
163
|
+
await callToolWithRetry(context, ABORT_STAGED_UPLOAD_TOOL_NAME, { uploadId }, allowLogin);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Best-effort cleanup only. Preserve the upload failure as the surfaced error.
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async function putBytesToStagedUpload(input) {
|
|
170
|
+
const response = await fetch(input.directPut.presignedUrl, {
|
|
171
|
+
method: "PUT",
|
|
172
|
+
headers: input.directPut.headers,
|
|
173
|
+
body: toBlob(input.bytes, input.contentType),
|
|
174
|
+
});
|
|
175
|
+
if (!response.ok) {
|
|
176
|
+
throw new CliError("upload.put_failed", `Upload failed for ${input.fileName} with HTTP ${response.status}.`, EXIT_CODES.network, { nextStep: "Retry the command. If this repeats, run vibecodr doctor and check upload service status." });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
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]"))
|
|
181
|
+
return;
|
|
182
|
+
const input = parseUploadArgs(args);
|
|
183
|
+
const fileInfo = await stat(input.filePath).catch((error) => {
|
|
184
|
+
throw new CliError("usage.upload_file_unreadable", `Could not read upload file: ${error instanceof Error ? error.message : String(error)}`, EXIT_CODES.usage);
|
|
185
|
+
});
|
|
186
|
+
if (!fileInfo.isFile()) {
|
|
187
|
+
throw new CliError("usage.upload_file_required", "Upload path must be a file.", EXIT_CODES.usage);
|
|
188
|
+
}
|
|
189
|
+
if (fileInfo.size <= 0) {
|
|
190
|
+
throw new CliError("usage.upload_file_empty", "Upload file must not be empty.", EXIT_CODES.usage);
|
|
191
|
+
}
|
|
192
|
+
const bytes = await readFile(input.filePath);
|
|
193
|
+
const fileName = basename(input.filePath) || (input.kind === "source_zip" ? "source.zip" : "image");
|
|
194
|
+
const contentType = inferContentType(input.kind, fileName, input.contentType);
|
|
195
|
+
const hash = sha256Hex(bytes);
|
|
196
|
+
const { result: createResult } = await callToolWithRetry(context, CREATE_STAGED_UPLOAD_TOOL_NAME, {
|
|
197
|
+
kind: input.kind,
|
|
198
|
+
fileName,
|
|
199
|
+
contentType,
|
|
200
|
+
sizeBytes: bytes.byteLength,
|
|
201
|
+
sha256: hash,
|
|
202
|
+
createdBySurface: input.kind === "source_zip" ? "cli.upload.zip" : "cli.upload.image",
|
|
203
|
+
...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
|
|
204
|
+
}, input.allowLogin);
|
|
205
|
+
const created = readCreateResult(createResult);
|
|
206
|
+
try {
|
|
207
|
+
await putBytesToStagedUpload({
|
|
208
|
+
fileName: created.fileName,
|
|
209
|
+
bytes,
|
|
210
|
+
contentType: created.contentType,
|
|
211
|
+
directPut: created.directPut,
|
|
212
|
+
});
|
|
213
|
+
const { result: completeResult } = await callToolWithRetry(context, COMPLETE_STAGED_UPLOAD_TOOL_NAME, {
|
|
214
|
+
uploadId: created.uploadId,
|
|
215
|
+
sizeBytes: bytes.byteLength,
|
|
216
|
+
sha256: hash,
|
|
217
|
+
}, input.allowLogin);
|
|
218
|
+
const completed = readCompleteResult(completeResult, created.uploadId);
|
|
219
|
+
const quickPublishPayload = {
|
|
220
|
+
...(input.kind === "source_zip"
|
|
221
|
+
? {
|
|
222
|
+
importMode: "staged_upload",
|
|
223
|
+
stagedUpload: {
|
|
224
|
+
uploadId: completed.uploadId,
|
|
225
|
+
fileName: created.fileName,
|
|
226
|
+
async: true,
|
|
227
|
+
...(input.rootHint ? { rootHint: input.rootHint } : {}),
|
|
228
|
+
...(input.entryHint ? { entryHint: input.entryHint } : {}),
|
|
229
|
+
},
|
|
230
|
+
}
|
|
231
|
+
: input.kind === "cover_image"
|
|
232
|
+
? {
|
|
233
|
+
thumbnailStagedUpload: {
|
|
234
|
+
uploadId: completed.uploadId,
|
|
235
|
+
fileName: created.fileName,
|
|
236
|
+
},
|
|
237
|
+
}
|
|
238
|
+
: {
|
|
239
|
+
avatarStagedUpload: {
|
|
240
|
+
uploadId: completed.uploadId,
|
|
241
|
+
fileName: created.fileName,
|
|
242
|
+
},
|
|
243
|
+
}),
|
|
244
|
+
};
|
|
245
|
+
context.output.success({
|
|
246
|
+
schemaVersion: 1,
|
|
247
|
+
upload: {
|
|
248
|
+
uploadId: completed.uploadId,
|
|
249
|
+
status: completed.status,
|
|
250
|
+
kind: input.kind,
|
|
251
|
+
fileName: created.fileName,
|
|
252
|
+
contentType: created.contentType,
|
|
253
|
+
sizeBytes: bytes.byteLength,
|
|
254
|
+
sha256: completed.sha256 || hash,
|
|
255
|
+
},
|
|
256
|
+
quickPublishPayload: redactForOutput(quickPublishPayload),
|
|
257
|
+
}, [
|
|
258
|
+
`Uploaded and verified ${created.fileName}.`,
|
|
259
|
+
input.kind === "source_zip"
|
|
260
|
+
? `Use uploadId ${completed.uploadId} with payload.importMode="staged_upload".`
|
|
261
|
+
: input.kind === "cover_image"
|
|
262
|
+
? `Use uploadId ${completed.uploadId} with thumbnailStagedUpload.uploadId.`
|
|
263
|
+
: `Use uploadId ${completed.uploadId} with an avatar image promotion flow.`,
|
|
264
|
+
]);
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
await abortBestEffort(context, created.uploadId, input.allowLogin);
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
package/dist/core/redaction.js
CHANGED
|
@@ -12,6 +12,8 @@ const SENSITIVE_KEY_PATTERNS = [
|
|
|
12
12
|
/^private[-_]?key$/i,
|
|
13
13
|
/^refresh[-_]?token$/i,
|
|
14
14
|
/^access[-_]?token$/i,
|
|
15
|
+
/^presigned[-_]?url$/i,
|
|
16
|
+
/^signature$/i,
|
|
15
17
|
/^fileBase64$/i,
|
|
16
18
|
/^code$/i,
|
|
17
19
|
/^content$/i,
|
|
@@ -19,6 +21,7 @@ const SENSITIVE_KEY_PATTERNS = [
|
|
|
19
21
|
];
|
|
20
22
|
const SENSITIVE_STRING_PATTERNS = [
|
|
21
23
|
/\bBearer\s+[A-Za-z0-9._~+/=-]+/i,
|
|
24
|
+
/[?&]X-Amz-Signature=[^&\s]+/i,
|
|
22
25
|
/\btok_[A-Za-z0-9._-]+/i,
|
|
23
26
|
/\bsk-[A-Za-z0-9._-]+/i,
|
|
24
27
|
/\b(token|secret|api[-_ ]?key)\s*[:=]\s*\S+/i
|
package/docs/commands.md
CHANGED
|
@@ -54,9 +54,9 @@ Syntax:
|
|
|
54
54
|
|
|
55
55
|
This always reads the live tool catalog from the MCP server.
|
|
56
56
|
|
|
57
|
-
### `call`
|
|
58
|
-
|
|
59
|
-
Syntax:
|
|
57
|
+
### `call`
|
|
58
|
+
|
|
59
|
+
Syntax:
|
|
60
60
|
|
|
61
61
|
`vibecodr call <tool-name> [--input-json <json>] [--input-file <path>] [--stdin] [--interactive] [--no-login] [--confirm]`
|
|
62
62
|
|
|
@@ -64,9 +64,25 @@ Syntax:
|
|
|
64
64
|
|
|
65
65
|
For `quick_publish_creation` with `payload.importMode: "direct_files"`, pass file paths as normal slash-separated project paths such as `src/main.tsx` or `src/server/binding-proof.js`. Do not pre-encode slashes as `%2F`; the hosted MCP gateway encodes each URL segment when it writes files to Vibecodr.
|
|
66
66
|
|
|
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
|
-
|
|
69
|
-
### `
|
|
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
|
+
|
|
69
|
+
### `upload`
|
|
70
|
+
|
|
71
|
+
Syntax:
|
|
72
|
+
|
|
73
|
+
`vibecodr upload --zip <path> [--idempotency-key <key>] [--root-hint <path>] [--entry-hint <path>] [--no-login]`
|
|
74
|
+
|
|
75
|
+
`vibecodr upload --image <path> [--kind cover_image|avatar_image] [--content-type <mime>] [--no-login]`
|
|
76
|
+
|
|
77
|
+
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
|
+
|
|
79
|
+
ZIP uploads print a `quickPublishPayload` snippet using `payload.importMode: "staged_upload"`. Cover image uploads print a `thumbnailStagedUpload` snippet that can be passed to publish metadata tools. Avatar image uploads print an `avatarStagedUpload` identifier for avatar promotion flows.
|
|
80
|
+
|
|
81
|
+
Cover images support PNG, JPEG, WebP, and AVIF. Avatar images support PNG, JPEG, WebP, and GIF.
|
|
82
|
+
|
|
83
|
+
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
|
+
|
|
85
|
+
### `pulse-setup`
|
|
70
86
|
|
|
71
87
|
Syntax:
|
|
72
88
|
|