vibeview 0.27.1 → 0.28.1
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/dist/{chunk-7IOGMM5W.js → chunk-3DKY443B.js} +154 -123
- package/dist/{chunk-FTTWLCTE.js → chunk-PBMB6TG4.js} +120 -15
- package/dist/cli.js +386 -90
- package/dist/{server-ZIUZ7U5T.js → server-BGQJOVSN.js} +298 -53
- package/dist/{upload-app-MDKTSG4O.js → upload-app-FEZG23NU.js} +1 -1
- package/package.json +2 -2
- package/skills/vibeview-agent/SKILL.md +10 -5
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/commands/upload-app.ts
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
-
import { existsSync } from "fs";
|
|
5
|
+
import { existsSync as existsSync2 } from "fs";
|
|
6
6
|
|
|
7
7
|
// src/lib/config.ts
|
|
8
8
|
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
@@ -83,9 +83,9 @@ function reportCommandError(json, err) {
|
|
|
83
83
|
process.stderr.write(`error: ${message}
|
|
84
84
|
`);
|
|
85
85
|
}
|
|
86
|
-
function printJsonError(err) {
|
|
86
|
+
function printJsonError(err, extra) {
|
|
87
87
|
const message = err instanceof Error ? err.message : String(err);
|
|
88
|
-
console.log(JSON.stringify({ event: "error", message }));
|
|
88
|
+
console.log(JSON.stringify({ event: "error", message, ...extra }));
|
|
89
89
|
}
|
|
90
90
|
var NotFoundError = class extends Error {
|
|
91
91
|
constructor(message) {
|
|
@@ -93,6 +93,19 @@ var NotFoundError = class extends Error {
|
|
|
93
93
|
this.name = "NotFoundError";
|
|
94
94
|
}
|
|
95
95
|
};
|
|
96
|
+
var ApiStatusError = class extends Error {
|
|
97
|
+
constructor(status, message) {
|
|
98
|
+
super(message);
|
|
99
|
+
this.status = status;
|
|
100
|
+
this.name = "ApiStatusError";
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var NetworkError = class extends Error {
|
|
104
|
+
constructor(message) {
|
|
105
|
+
super(message);
|
|
106
|
+
this.name = "NetworkError";
|
|
107
|
+
}
|
|
108
|
+
};
|
|
96
109
|
function extractDetail(text) {
|
|
97
110
|
try {
|
|
98
111
|
const parsed = JSON.parse(text);
|
|
@@ -103,6 +116,18 @@ function extractDetail(text) {
|
|
|
103
116
|
}
|
|
104
117
|
return void 0;
|
|
105
118
|
}
|
|
119
|
+
function httpErrorMessage(status, text) {
|
|
120
|
+
const body = text.trim();
|
|
121
|
+
const looksLikeHtml = /^<(!doctype|html|head|body)\b/i.test(body) || /<\/html>\s*$/i.test(body);
|
|
122
|
+
const meaning = {
|
|
123
|
+
502: "the server could not be reached (bad gateway) \u2014 try again in a moment",
|
|
124
|
+
503: "the server is unavailable right now \u2014 try again in a moment",
|
|
125
|
+
504: "the server took too long to answer (gateway timeout) \u2014 the action may still have run; check before retrying"
|
|
126
|
+
};
|
|
127
|
+
if (meaning[status]) return `API error ${status}: ${meaning[status]}`;
|
|
128
|
+
if (!body || looksLikeHtml) return `API error ${status}`;
|
|
129
|
+
return `API error ${status}: ${body.length > 300 ? `${body.slice(0, 300)}\u2026` : body}`;
|
|
130
|
+
}
|
|
106
131
|
async function parseJsonBody(resp) {
|
|
107
132
|
if (resp.status === 204 || resp.status === 205) return void 0;
|
|
108
133
|
return withUtcTimestamps(await resp.json());
|
|
@@ -125,7 +150,7 @@ var ApiClient = class {
|
|
|
125
150
|
} catch (e) {
|
|
126
151
|
const cause = e.cause;
|
|
127
152
|
const detail = cause?.code ? ` (${cause.code})` : "";
|
|
128
|
-
throw new
|
|
153
|
+
throw new NetworkError(
|
|
129
154
|
`Cannot reach ${this.baseUrl}${detail}. Check the server URL (--base-url flag, VIBEVIEW_BASE_URL, or ~/.vibeview/config.json) and your network.`
|
|
130
155
|
);
|
|
131
156
|
}
|
|
@@ -145,7 +170,10 @@ var ApiClient = class {
|
|
|
145
170
|
const text = await resp.text().catch(() => "");
|
|
146
171
|
if (resp.status === 401) throw new AuthError();
|
|
147
172
|
if (resp.status === 404) throw new NotFoundError(`Not found: ${extractDetail(text) ?? path}`);
|
|
148
|
-
throw new
|
|
173
|
+
throw new ApiStatusError(
|
|
174
|
+
resp.status,
|
|
175
|
+
extractDetail(text) ?? httpErrorMessage(resp.status, text)
|
|
176
|
+
);
|
|
149
177
|
}
|
|
150
178
|
return parseJsonBody(resp);
|
|
151
179
|
}
|
|
@@ -163,9 +191,10 @@ var ApiClient = class {
|
|
|
163
191
|
model: options?.model,
|
|
164
192
|
provider: options?.provider,
|
|
165
193
|
mode: options?.mode,
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
|
|
194
|
+
// Omitted unless asked for: the backend picks tv for a TV suite and
|
|
195
|
+
// phone otherwise. Never sent with a pinned device (the backend
|
|
196
|
+
// rejects the combo).
|
|
197
|
+
device_category: options?.device ? void 0 : options?.deviceCategory,
|
|
169
198
|
device_id: options?.device,
|
|
170
199
|
build_id: options?.build,
|
|
171
200
|
metadata: options?.metadata
|
|
@@ -178,7 +207,7 @@ var ApiClient = class {
|
|
|
178
207
|
model: options?.model,
|
|
179
208
|
provider: options?.provider,
|
|
180
209
|
mode: options?.mode,
|
|
181
|
-
device_category: options?.device ? void 0 : options?.deviceCategory
|
|
210
|
+
device_category: options?.device ? void 0 : options?.deviceCategory,
|
|
182
211
|
device_id: options?.device,
|
|
183
212
|
build_id: options?.build,
|
|
184
213
|
metadata: options?.metadata
|
|
@@ -217,7 +246,10 @@ var ApiClient = class {
|
|
|
217
246
|
const text = await resp.text().catch(() => "");
|
|
218
247
|
if (resp.status === 401) throw new AuthError();
|
|
219
248
|
if (resp.status === 404) throw new NotFoundError(`Not found: ${extractDetail(text) ?? path}`);
|
|
220
|
-
throw new
|
|
249
|
+
throw new ApiStatusError(
|
|
250
|
+
resp.status,
|
|
251
|
+
extractDetail(text) ?? httpErrorMessage(resp.status, text)
|
|
252
|
+
);
|
|
221
253
|
}
|
|
222
254
|
return parseJsonBody(resp);
|
|
223
255
|
}
|
|
@@ -237,7 +269,10 @@ var ApiClient = class {
|
|
|
237
269
|
const text = await resp.text().catch(() => "");
|
|
238
270
|
if (resp.status === 401) throw new AuthError();
|
|
239
271
|
if (resp.status === 404) throw new NotFoundError(`Not found: ${extractDetail(text) ?? path}`);
|
|
240
|
-
throw new
|
|
272
|
+
throw new ApiStatusError(
|
|
273
|
+
resp.status,
|
|
274
|
+
extractDetail(text) ?? httpErrorMessage(resp.status, text)
|
|
275
|
+
);
|
|
241
276
|
}
|
|
242
277
|
return parseJsonBody(resp);
|
|
243
278
|
}
|
|
@@ -397,7 +432,10 @@ var ApiClient = class {
|
|
|
397
432
|
if (resp.status === 401) throw new AuthError();
|
|
398
433
|
if (resp.status === 404)
|
|
399
434
|
throw new NotFoundError(`Not found: ${extractDetail(text) ?? `/builds/${id}/artifact`}`);
|
|
400
|
-
throw new
|
|
435
|
+
throw new ApiStatusError(
|
|
436
|
+
resp.status,
|
|
437
|
+
extractDetail(text) ?? httpErrorMessage(resp.status, text)
|
|
438
|
+
);
|
|
401
439
|
}
|
|
402
440
|
const rawFilename = parseContentDispositionFilename(resp.headers.get("content-disposition"));
|
|
403
441
|
const filename = sanitizeDownloadFilename(rawFilename) ?? `${id}.bin`;
|
|
@@ -670,6 +708,63 @@ function sanitizeDownloadFilename(raw) {
|
|
|
670
708
|
return base;
|
|
671
709
|
}
|
|
672
710
|
|
|
711
|
+
// src/lib/artifact.ts
|
|
712
|
+
import { existsSync, mkdtempSync, readdirSync, rmSync as rmSync2, statSync } from "fs";
|
|
713
|
+
import { spawnSync } from "child_process";
|
|
714
|
+
import { tmpdir } from "os";
|
|
715
|
+
import { basename as basename2, dirname, isAbsolute, join as join3 } from "path";
|
|
716
|
+
function resolveArtifact(cwd, pattern) {
|
|
717
|
+
const abs = isAbsolute(pattern) ? pattern : join3(cwd, pattern);
|
|
718
|
+
if (!basename2(abs).includes("*")) {
|
|
719
|
+
if (!existsSync(abs)) {
|
|
720
|
+
throw new Error(
|
|
721
|
+
`artifact not found: ${pattern} (looked at ${abs}) \u2014 did the build produce it?`
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
return abs;
|
|
725
|
+
}
|
|
726
|
+
const dir = dirname(abs);
|
|
727
|
+
const re = new RegExp("^" + basename2(abs).split("*").map(escapeRegExp).join(".*") + "$");
|
|
728
|
+
let entries;
|
|
729
|
+
try {
|
|
730
|
+
entries = readdirSync(dir);
|
|
731
|
+
} catch {
|
|
732
|
+
throw new Error(`artifact not found: ${pattern} (directory ${dir} does not exist)`);
|
|
733
|
+
}
|
|
734
|
+
const matches = entries.filter((e) => re.test(e)).map((e) => join3(dir, e)).sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs);
|
|
735
|
+
if (matches.length === 0) {
|
|
736
|
+
throw new Error(`artifact not found: no match for ${pattern} in ${dir}`);
|
|
737
|
+
}
|
|
738
|
+
return matches[0];
|
|
739
|
+
}
|
|
740
|
+
function escapeRegExp(s) {
|
|
741
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
742
|
+
}
|
|
743
|
+
function prepareUploadArtifact(path) {
|
|
744
|
+
if (!(statSync(path).isDirectory() && path.endsWith(".app"))) {
|
|
745
|
+
return { uploadPath: path, cleanup: () => {
|
|
746
|
+
} };
|
|
747
|
+
}
|
|
748
|
+
const tmp = mkdtempSync(join3(tmpdir(), "vibeview-zip-"));
|
|
749
|
+
const out = join3(tmp, `${basename2(path)}.zip`);
|
|
750
|
+
const res = spawnSync("zip", ["-qry", out, basename2(path)], { cwd: dirname(path) });
|
|
751
|
+
if (res.error) {
|
|
752
|
+
rmSync2(tmp, { recursive: true, force: true });
|
|
753
|
+
const errorCode = res.error.code;
|
|
754
|
+
const errorMsg = res.error.message;
|
|
755
|
+
throw new Error(
|
|
756
|
+
`failed to zip ${path}: spawn zip ${errorCode} \u2014 ${errorMsg} \u2014 is the \`zip\` command installed?`
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
if (res.status !== 0) {
|
|
760
|
+
rmSync2(tmp, { recursive: true, force: true });
|
|
761
|
+
throw new Error(
|
|
762
|
+
`failed to zip ${path}: ${res.stderr?.toString().trim() || `zip exited ${res.status}`}`
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
return { uploadPath: out, cleanup: () => rmSync2(tmp, { recursive: true, force: true }) };
|
|
766
|
+
}
|
|
767
|
+
|
|
673
768
|
// src/lib/spinner.ts
|
|
674
769
|
import ora from "ora";
|
|
675
770
|
function createSpinner(text, opts = {}) {
|
|
@@ -706,13 +801,19 @@ function queueReasonCopy(reason) {
|
|
|
706
801
|
|
|
707
802
|
// src/commands/upload-app.ts
|
|
708
803
|
async function uploadAppBuild(filePath, options) {
|
|
709
|
-
if (!
|
|
804
|
+
if (!existsSync2(filePath)) {
|
|
710
805
|
throw new Error(`File not found: ${filePath}`);
|
|
711
806
|
}
|
|
712
807
|
const { token, baseUrl } = resolveAuth(options);
|
|
713
808
|
const client = new ApiClient(baseUrl, token);
|
|
714
809
|
const note = options.note?.trim() || void 0;
|
|
715
|
-
const
|
|
810
|
+
const { uploadPath, cleanup } = prepareUploadArtifact(filePath);
|
|
811
|
+
let result;
|
|
812
|
+
try {
|
|
813
|
+
result = await client.uploadApp(uploadPath, options.name, note);
|
|
814
|
+
} finally {
|
|
815
|
+
cleanup();
|
|
816
|
+
}
|
|
716
817
|
if (note && result.deduplicated && result.build_id != null) {
|
|
717
818
|
await client.updateBuildNote(result.public_id, result.build_id, note);
|
|
718
819
|
}
|
|
@@ -725,7 +826,7 @@ function describeBuild(result) {
|
|
|
725
826
|
return `Build ${chalk.cyan(String(result.build_id))}${version ? ` ${version}` : ""}${dedup}`;
|
|
726
827
|
}
|
|
727
828
|
async function uploadApp(filePath, options) {
|
|
728
|
-
if (!
|
|
829
|
+
if (!existsSync2(filePath)) {
|
|
729
830
|
console.error(chalk.red(`Error: File not found: ${filePath}`));
|
|
730
831
|
process.exit(EXIT_INFRA_ERROR);
|
|
731
832
|
}
|
|
@@ -792,6 +893,8 @@ export {
|
|
|
792
893
|
reportCommandError,
|
|
793
894
|
printJsonError,
|
|
794
895
|
NotFoundError,
|
|
896
|
+
ApiStatusError,
|
|
897
|
+
NetworkError,
|
|
795
898
|
ApiClient,
|
|
796
899
|
createSpinner,
|
|
797
900
|
EXIT_PASS,
|
|
@@ -800,6 +903,8 @@ export {
|
|
|
800
903
|
UsageError,
|
|
801
904
|
DEVICE_STATUS_FILTERS,
|
|
802
905
|
queueReasonCopy,
|
|
906
|
+
resolveArtifact,
|
|
907
|
+
prepareUploadArtifact,
|
|
803
908
|
uploadAppBuild,
|
|
804
909
|
describeBuild,
|
|
805
910
|
uploadApp
|