vibeview 0.15.0 → 0.17.0
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-B7GWF2BT.js → chunk-3IMJ7FNN.js} +5 -5
- package/dist/{chunk-2WTLJ66O.js → chunk-SYFMOPJW.js} +32 -0
- package/dist/cli.js +348 -39
- package/dist/{server-CSXGGDJL.js → server-AZLB25BY.js} +3 -3
- package/dist/{upload-app-NVPQ7KRO.js → upload-app-XRB7CKA3.js} +1 -1
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
loadConfig,
|
|
6
6
|
resolveAuth,
|
|
7
7
|
uploadAppBuild
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-SYFMOPJW.js";
|
|
9
9
|
|
|
10
10
|
// src/lib/dev-state.ts
|
|
11
11
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
@@ -424,6 +424,9 @@ function productionDefaultsFor(cwd, platform) {
|
|
|
424
424
|
if (platform === "android" || platform === "androidtv") {
|
|
425
425
|
return { buildCommand: ANDROID_PRODUCTION_COMMAND, artifact: ANDROID_PRODUCTION_ARTIFACT };
|
|
426
426
|
}
|
|
427
|
+
if (platform === "tvos") {
|
|
428
|
+
return { artifact: IOS_PRODUCTION_ARTIFACT };
|
|
429
|
+
}
|
|
427
430
|
if (platform === "ios") {
|
|
428
431
|
const ws = iosWorkspaceName(cwd);
|
|
429
432
|
if (!ws) return { artifact: IOS_PRODUCTION_ARTIFACT };
|
|
@@ -711,11 +714,8 @@ function runTar(args) {
|
|
|
711
714
|
|
|
712
715
|
// src/commands/build.ts
|
|
713
716
|
var CLOUD_PLATFORMS = ["ios", "android", "tvos", "androidtv"];
|
|
714
|
-
var PRODUCTION_SIGNING_PLATFORMS = ["ios", "android", "androidtv"];
|
|
717
|
+
var PRODUCTION_SIGNING_PLATFORMS = ["ios", "android", "androidtv", "tvos"];
|
|
715
718
|
function productionUnsupportedReason(platform) {
|
|
716
|
-
if (platform === "tvos") {
|
|
717
|
-
return "--production doesn't support tvos yet \u2014 tvOS production builds are not yet enabled";
|
|
718
|
-
}
|
|
719
719
|
return `--production doesn't support ${platform} \u2014 production signing supports ${PRODUCTION_SIGNING_PLATFORMS.join(", ")}`;
|
|
720
720
|
}
|
|
721
721
|
function isCloudPlatform(p) {
|
|
@@ -424,6 +424,38 @@ var ApiClient = class {
|
|
|
424
424
|
async deleteSigningCredential(id) {
|
|
425
425
|
await this.requestRaw("DELETE", `/signing-credentials/${id}`);
|
|
426
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* POST /api/v1/signing-credentials/generate. Requires ADMIN role. Generates an Android
|
|
429
|
+
* keystore server-side and stores it as a credential; the response is the
|
|
430
|
+
* ONLY time the keystore bytes and passwords are ever returned. As with
|
|
431
|
+
* addSigningCredential, the CLI's camelCase (`keyAlias`) is converted to
|
|
432
|
+
* the route's snake_case at this boundary and nowhere else.
|
|
433
|
+
*/
|
|
434
|
+
async generateSigningCredential(body) {
|
|
435
|
+
return this.requestRaw("POST", "/signing-credentials/generate", {
|
|
436
|
+
name: body.name,
|
|
437
|
+
...body.app !== void 0 ? { app: body.app } : {},
|
|
438
|
+
...body.keyAlias !== void 0 ? { key_alias: body.keyAlias } : {},
|
|
439
|
+
...body.overwrite !== void 0 ? { overwrite: body.overwrite } : {}
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
async listWebhooks() {
|
|
443
|
+
return this.requestRaw("GET", "/webhooks");
|
|
444
|
+
}
|
|
445
|
+
async addWebhook(body) {
|
|
446
|
+
return this.requestRaw("POST", "/webhooks", {
|
|
447
|
+
url: body.url,
|
|
448
|
+
...body.secret !== void 0 ? { secret: body.secret } : {}
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
async deleteWebhook(id) {
|
|
452
|
+
await this.requestRaw("DELETE", `/webhooks/${id}`);
|
|
453
|
+
}
|
|
454
|
+
/** Kicks off an async `ping` delivery; the result lands in the endpoint's
|
|
455
|
+
* last_delivery within ~10–15 s — poll listWebhooks() for it. */
|
|
456
|
+
async testWebhook(id) {
|
|
457
|
+
return this.requestRaw("POST", `/webhooks/${id}/test`);
|
|
458
|
+
}
|
|
427
459
|
};
|
|
428
460
|
function parseContentDispositionFilename(header) {
|
|
429
461
|
if (!header) return void 0;
|
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
readDevState,
|
|
14
14
|
readMetroState,
|
|
15
15
|
runAgentVerb
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-3IMJ7FNN.js";
|
|
17
17
|
import {
|
|
18
18
|
ApiClient,
|
|
19
19
|
EXIT_INFRA_ERROR,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
resolveAuth,
|
|
27
27
|
saveConfig,
|
|
28
28
|
uploadApp
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-SYFMOPJW.js";
|
|
30
30
|
|
|
31
31
|
// src/cli.ts
|
|
32
32
|
import { createRequire } from "module";
|
|
@@ -1173,13 +1173,13 @@ function formatBuildsHuman(builds2) {
|
|
|
1173
1173
|
]);
|
|
1174
1174
|
const widths = HEADERS.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1175
1175
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
1176
|
-
const
|
|
1177
|
-
const lines = [chalk10.bold(
|
|
1176
|
+
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
1177
|
+
const lines = [chalk10.bold(join3(padCells(HEADERS)))];
|
|
1178
1178
|
for (const row of rows) {
|
|
1179
1179
|
const padded = padCells(row);
|
|
1180
1180
|
const colorFn = STATUS_COLOR[row[2]] ?? ((s) => s);
|
|
1181
1181
|
padded[2] = colorFn(row[2]) + padded[2].slice(row[2].length);
|
|
1182
|
-
lines.push(
|
|
1182
|
+
lines.push(join3(padded));
|
|
1183
1183
|
}
|
|
1184
1184
|
return lines.join("\n") + "\n";
|
|
1185
1185
|
}
|
|
@@ -1526,9 +1526,9 @@ function formatEnvVarsHuman(variables) {
|
|
|
1526
1526
|
]);
|
|
1527
1527
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1528
1528
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
1529
|
-
const
|
|
1530
|
-
const lines = [chalk13.bold(
|
|
1531
|
-
for (const row of rows) lines.push(
|
|
1529
|
+
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
1530
|
+
const lines = [chalk13.bold(join3(padCells(HEADERS3)))];
|
|
1531
|
+
for (const row of rows) lines.push(join3(padCells(row)));
|
|
1532
1532
|
return lines.join("\n") + "\n";
|
|
1533
1533
|
}
|
|
1534
1534
|
async function envSet(name, value, opts, deps) {
|
|
@@ -1662,8 +1662,14 @@ function printError2(opts, err) {
|
|
|
1662
1662
|
}
|
|
1663
1663
|
|
|
1664
1664
|
// src/commands/credentials.ts
|
|
1665
|
-
import {
|
|
1666
|
-
|
|
1665
|
+
import {
|
|
1666
|
+
access,
|
|
1667
|
+
chmod as chmodAsync,
|
|
1668
|
+
readFile as readFileAsync2,
|
|
1669
|
+
writeFile as writeFileAsync
|
|
1670
|
+
} from "fs/promises";
|
|
1671
|
+
import { mkdirSync } from "fs";
|
|
1672
|
+
import { basename as basename2, extname, join } from "path";
|
|
1667
1673
|
import chalk14 from "chalk";
|
|
1668
1674
|
import { input, password as password2 } from "@inquirer/prompts";
|
|
1669
1675
|
function clientFor2(opts) {
|
|
@@ -1703,9 +1709,19 @@ function defaultDeps4(client) {
|
|
|
1703
1709
|
}
|
|
1704
1710
|
await client.deleteSigningCredential(match.id);
|
|
1705
1711
|
},
|
|
1712
|
+
generateCredential: (body) => client.generateSigningCredential(body),
|
|
1706
1713
|
prompt: ({ message, hidden }) => hidden ? password2({ message }) : input({ message }),
|
|
1707
1714
|
isTTY: Boolean(process.stdin.isTTY),
|
|
1708
1715
|
readFile: (path) => readFileAsync2(path),
|
|
1716
|
+
fileExists: (path) => access(path).then(
|
|
1717
|
+
() => true,
|
|
1718
|
+
() => false
|
|
1719
|
+
),
|
|
1720
|
+
// Same precedent as api-client.ts's artifact download: recursive, so an
|
|
1721
|
+
// already-existing directory is a no-op rather than an error.
|
|
1722
|
+
mkdir: (dir) => mkdirSync(dir, { recursive: true }),
|
|
1723
|
+
writeFile: (path, data, mode) => writeFileAsync(path, data, mode !== void 0 ? { mode } : void 0),
|
|
1724
|
+
chmod: (path, mode) => chmodAsync(path, mode),
|
|
1709
1725
|
warn: (message) => console.error(chalk14.yellow(`warning: ${message}`))
|
|
1710
1726
|
};
|
|
1711
1727
|
}
|
|
@@ -1783,15 +1799,86 @@ async function credentialsAdd(opts, deps) {
|
|
|
1783
1799
|
process.exitCode = 1;
|
|
1784
1800
|
}
|
|
1785
1801
|
}
|
|
1802
|
+
async function credentialsGenerate(opts, deps) {
|
|
1803
|
+
const d = deps ?? defaultDeps4(clientFor2(opts));
|
|
1804
|
+
if (!opts.platform) {
|
|
1805
|
+
throw new UsageError('--platform is required ("android" is the only platform available)');
|
|
1806
|
+
}
|
|
1807
|
+
if (opts.platform !== "android") {
|
|
1808
|
+
throw new UsageError(
|
|
1809
|
+
"iOS/tvOS generation isn't available yet because it requires an Apple Developer account; upload an exported certificate with `vibeview credentials add` instead"
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
if (!opts.name) {
|
|
1813
|
+
throw new UsageError("--name is required");
|
|
1814
|
+
}
|
|
1815
|
+
const outDir = opts.out ?? process.cwd();
|
|
1816
|
+
try {
|
|
1817
|
+
d.mkdir(outDir);
|
|
1818
|
+
const expectedPath = join(outDir, `${opts.name}.keystore`);
|
|
1819
|
+
if (await d.fileExists(expectedPath)) {
|
|
1820
|
+
throw new Error(
|
|
1821
|
+
`${expectedPath} already exists \u2014 refusing to overwrite it. Move it away or pass a different --out/--name.`
|
|
1822
|
+
);
|
|
1823
|
+
}
|
|
1824
|
+
const result = await d.generateCredential({
|
|
1825
|
+
name: opts.name,
|
|
1826
|
+
...opts.app !== void 0 ? { app: opts.app } : {},
|
|
1827
|
+
...opts.alias !== void 0 ? { keyAlias: opts.alias } : {},
|
|
1828
|
+
// Only ever sent as `overwrite: true` — omitting the field entirely
|
|
1829
|
+
// otherwise keeps the server's default (409 on a name collision) in
|
|
1830
|
+
// charge, so a stale CLI can never accidentally opt out of it.
|
|
1831
|
+
...opts.force ? { overwrite: true } : {}
|
|
1832
|
+
});
|
|
1833
|
+
const savedTo = join(outDir, basename2(result.filename));
|
|
1834
|
+
if (opts.json) {
|
|
1835
|
+
console.log(JSON.stringify({ ...result, saved_to: savedTo }));
|
|
1836
|
+
}
|
|
1837
|
+
try {
|
|
1838
|
+
if (await d.fileExists(savedTo)) {
|
|
1839
|
+
throw new Error(`${savedTo} already exists \u2014 refusing to overwrite it.`);
|
|
1840
|
+
}
|
|
1841
|
+
await d.writeFile(savedTo, Buffer.from(result.keystore_base64, "base64"), 384);
|
|
1842
|
+
await d.chmod(savedTo, 384);
|
|
1843
|
+
} catch (writeErr) {
|
|
1844
|
+
printKeystoreWriteFailure(opts, writeErr, result);
|
|
1845
|
+
process.exitCode = 1;
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
if (opts.json) {
|
|
1849
|
+
} else {
|
|
1850
|
+
console.log(chalk14.green(`\u2713 generated ${result.name} (android)`));
|
|
1851
|
+
console.log("");
|
|
1852
|
+
console.log(` Keystore saved to: ${chalk14.bold(savedTo)}`);
|
|
1853
|
+
console.log(` Key alias: ${chalk14.bold(result.key_alias)}`);
|
|
1854
|
+
console.log(` Store password: ${chalk14.bold(result.store_password)}`);
|
|
1855
|
+
console.log(` Key password: ${chalk14.bold(result.key_password)}`);
|
|
1856
|
+
console.log("");
|
|
1857
|
+
console.log(
|
|
1858
|
+
chalk14.yellow(
|
|
1859
|
+
" These are shown ONCE \u2014 VibeView cannot recover the keystore file or its passwords. Back them up somewhere safe."
|
|
1860
|
+
)
|
|
1861
|
+
);
|
|
1862
|
+
console.log(
|
|
1863
|
+
chalk14.yellow(
|
|
1864
|
+
" The credential is already uploaded and active for android and androidtv production builds."
|
|
1865
|
+
)
|
|
1866
|
+
);
|
|
1867
|
+
}
|
|
1868
|
+
process.exitCode = 0;
|
|
1869
|
+
} catch (err) {
|
|
1870
|
+
printError3(opts, err);
|
|
1871
|
+
process.exitCode = 1;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1786
1874
|
async function credentialsList(opts, deps) {
|
|
1787
1875
|
const d = deps ?? defaultDeps4(clientFor2(opts));
|
|
1788
1876
|
try {
|
|
1789
1877
|
const { credentials: credentials2 } = await d.listCredentials(opts.app);
|
|
1790
1878
|
for (const c of credentials2) {
|
|
1791
1879
|
if (c.expires_soon) {
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
);
|
|
1880
|
+
const soonest = earliestExpiry(c);
|
|
1881
|
+
d.warn(`${c.name} (${c.platform}) is expiring soon${soonest ? ` \u2014 ${soonest}` : ""}.`);
|
|
1795
1882
|
}
|
|
1796
1883
|
}
|
|
1797
1884
|
if (opts.json) {
|
|
@@ -1813,6 +1900,23 @@ function formatIdentity(c) {
|
|
|
1813
1900
|
if (tag === "file-sha256") return `keystore-file sha256:${value}`;
|
|
1814
1901
|
return c.platform === "android" ? `keystore-file sha256:${c.fingerprint}` : `cert sha1:${c.fingerprint}`;
|
|
1815
1902
|
}
|
|
1903
|
+
function earliestExpiry(c) {
|
|
1904
|
+
const dates = [c.expires_at, c.profile_expires_at].filter((d) => d != null);
|
|
1905
|
+
if (dates.length === 0) return null;
|
|
1906
|
+
return dates.sort()[0];
|
|
1907
|
+
}
|
|
1908
|
+
function formatExpires(c) {
|
|
1909
|
+
const parts = [];
|
|
1910
|
+
if (c.profile_expires_at) {
|
|
1911
|
+
if (c.expires_at) parts.push(`cert ${c.expires_at}`);
|
|
1912
|
+
parts.push(`profile ${c.profile_expires_at}`);
|
|
1913
|
+
} else if (c.expires_at) {
|
|
1914
|
+
parts.push(c.expires_at);
|
|
1915
|
+
}
|
|
1916
|
+
if (parts.length === 0) return "";
|
|
1917
|
+
const cell = parts.join(" \xB7 ");
|
|
1918
|
+
return c.expires_soon ? `${cell} (soon)` : cell;
|
|
1919
|
+
}
|
|
1816
1920
|
function formatCredentialsHuman(credentials2) {
|
|
1817
1921
|
if (credentials2.length === 0) {
|
|
1818
1922
|
return "No signing credentials found.\n";
|
|
@@ -1823,13 +1927,13 @@ function formatCredentialsHuman(credentials2) {
|
|
|
1823
1927
|
c.platform,
|
|
1824
1928
|
c.app_public_id ?? "org-wide",
|
|
1825
1929
|
formatIdentity(c),
|
|
1826
|
-
c
|
|
1930
|
+
formatExpires(c)
|
|
1827
1931
|
]);
|
|
1828
1932
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1829
1933
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
1830
|
-
const
|
|
1831
|
-
const lines = [chalk14.bold(
|
|
1832
|
-
for (const row of rows) lines.push(
|
|
1934
|
+
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
1935
|
+
const lines = [chalk14.bold(join3(padCells(HEADERS3)))];
|
|
1936
|
+
for (const row of rows) lines.push(join3(padCells(row)));
|
|
1833
1937
|
return lines.join("\n") + "\n";
|
|
1834
1938
|
}
|
|
1835
1939
|
async function credentialsRm(name, opts, deps) {
|
|
@@ -1847,14 +1951,198 @@ async function credentialsRm(name, opts, deps) {
|
|
|
1847
1951
|
process.exitCode = 1;
|
|
1848
1952
|
}
|
|
1849
1953
|
}
|
|
1954
|
+
function printKeystoreWriteFailure(opts, err, result) {
|
|
1955
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1956
|
+
if (opts.json) {
|
|
1957
|
+
console.log(JSON.stringify({ event: "error", message }));
|
|
1958
|
+
console.error(chalk14.red(`error: ${message}`));
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
console.error(chalk14.red(`error: ${message}`));
|
|
1962
|
+
console.error(
|
|
1963
|
+
chalk14.red(
|
|
1964
|
+
`The keystore could NOT be saved locally, but the credential "${result.name}" WAS created server-side and its key material can never be re-fetched.`
|
|
1965
|
+
)
|
|
1966
|
+
);
|
|
1967
|
+
console.error(chalk14.red("Recover it NOW from the values below \u2014 they will not be shown again:"));
|
|
1968
|
+
console.error("");
|
|
1969
|
+
console.error(` Key alias: ${result.key_alias}`);
|
|
1970
|
+
console.error(` Store password: ${result.store_password}`);
|
|
1971
|
+
console.error(` Key password: ${result.key_password}`);
|
|
1972
|
+
console.error(` Keystore (base64): ${result.keystore_base64}`);
|
|
1973
|
+
console.error("");
|
|
1974
|
+
console.error(
|
|
1975
|
+
` Restore the file with: echo '${result.keystore_base64}' | base64 -d > ${result.name}.keystore`
|
|
1976
|
+
);
|
|
1977
|
+
}
|
|
1850
1978
|
function printError3(opts, err) {
|
|
1851
1979
|
const message = err instanceof Error ? err.message : String(err);
|
|
1852
1980
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
1853
1981
|
console.error(chalk14.red(`error: ${message}`));
|
|
1854
1982
|
}
|
|
1855
1983
|
|
|
1856
|
-
// src/
|
|
1984
|
+
// src/commands/webhooks.ts
|
|
1857
1985
|
import chalk15 from "chalk";
|
|
1986
|
+
function clientFor3(opts) {
|
|
1987
|
+
const { token, baseUrl } = resolveAuth(opts);
|
|
1988
|
+
return new ApiClient(baseUrl, token);
|
|
1989
|
+
}
|
|
1990
|
+
function defaultDeps5(client) {
|
|
1991
|
+
return {
|
|
1992
|
+
addWebhook: (body) => client.addWebhook(body),
|
|
1993
|
+
listWebhooks: () => client.listWebhooks(),
|
|
1994
|
+
deleteWebhook: (id) => client.deleteWebhook(id),
|
|
1995
|
+
testWebhook: (id) => client.testWebhook(id),
|
|
1996
|
+
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
1997
|
+
now: Date.now
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
async function webhooksAdd(opts, deps) {
|
|
2001
|
+
if (!opts.url) {
|
|
2002
|
+
throw new UsageError("--url is required");
|
|
2003
|
+
}
|
|
2004
|
+
if (!opts.url.startsWith("https://")) {
|
|
2005
|
+
throw new UsageError(`--url must start with https:// (got "${opts.url}")`);
|
|
2006
|
+
}
|
|
2007
|
+
const d = deps ?? defaultDeps5(clientFor3(opts));
|
|
2008
|
+
try {
|
|
2009
|
+
const result = await d.addWebhook({
|
|
2010
|
+
url: opts.url,
|
|
2011
|
+
...opts.secret !== void 0 ? { secret: opts.secret } : {}
|
|
2012
|
+
});
|
|
2013
|
+
if (opts.json) {
|
|
2014
|
+
console.log(JSON.stringify(result));
|
|
2015
|
+
} else {
|
|
2016
|
+
console.log(chalk15.green(`\u2713 added ${result.id} ${result.url}`));
|
|
2017
|
+
console.log("");
|
|
2018
|
+
console.log(` Signing secret: ${chalk15.bold(result.secret)}`);
|
|
2019
|
+
if (opts.secret !== void 0) {
|
|
2020
|
+
console.log(chalk15.yellow(" (This is the secret you provided.)"));
|
|
2021
|
+
}
|
|
2022
|
+
console.log(
|
|
2023
|
+
chalk15.yellow(" This secret is shown ONCE and cannot be retrieved again \u2014 store it now.")
|
|
2024
|
+
);
|
|
2025
|
+
}
|
|
2026
|
+
process.exitCode = 0;
|
|
2027
|
+
} catch (err) {
|
|
2028
|
+
printError4(opts, err);
|
|
2029
|
+
process.exitCode = 1;
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
function formatLastDelivery(ld) {
|
|
2033
|
+
if (!ld) return "\u2014";
|
|
2034
|
+
if (ld.status === "pending") return "pending";
|
|
2035
|
+
const code = ld.response_code != null ? ` (${ld.response_code})` : "";
|
|
2036
|
+
const at = ld.at != null ? ` ${ld.at}` : "";
|
|
2037
|
+
return `${ld.status}${code}${at}`;
|
|
2038
|
+
}
|
|
2039
|
+
function formatWebhooksHuman(webhooks2) {
|
|
2040
|
+
if (webhooks2.length === 0) {
|
|
2041
|
+
return "No webhooks found.\n";
|
|
2042
|
+
}
|
|
2043
|
+
const HEADERS3 = ["ID", "URL", "ACTIVE", "LAST DELIVERY", "CREATED"];
|
|
2044
|
+
const rows = webhooks2.map((w) => [
|
|
2045
|
+
w.id,
|
|
2046
|
+
w.url,
|
|
2047
|
+
w.active ? "yes" : "no",
|
|
2048
|
+
formatLastDelivery(w.last_delivery),
|
|
2049
|
+
w.created_at
|
|
2050
|
+
]);
|
|
2051
|
+
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2052
|
+
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2053
|
+
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
2054
|
+
const lines = [chalk15.bold(join3(padCells(HEADERS3)))];
|
|
2055
|
+
for (const row of rows) lines.push(join3(padCells(row)));
|
|
2056
|
+
return lines.join("\n") + "\n";
|
|
2057
|
+
}
|
|
2058
|
+
async function webhooksList(opts, deps) {
|
|
2059
|
+
const d = deps ?? defaultDeps5(clientFor3(opts));
|
|
2060
|
+
try {
|
|
2061
|
+
const { webhooks: webhooks2 } = await d.listWebhooks();
|
|
2062
|
+
if (opts.json) {
|
|
2063
|
+
console.log(JSON.stringify(webhooks2, null, 2));
|
|
2064
|
+
} else {
|
|
2065
|
+
process.stdout.write(formatWebhooksHuman(webhooks2));
|
|
2066
|
+
}
|
|
2067
|
+
process.exitCode = 0;
|
|
2068
|
+
} catch (err) {
|
|
2069
|
+
printError4(opts, err);
|
|
2070
|
+
process.exitCode = 1;
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
async function webhooksRm(id, opts, deps) {
|
|
2074
|
+
const d = deps ?? defaultDeps5(clientFor3(opts));
|
|
2075
|
+
try {
|
|
2076
|
+
await d.deleteWebhook(id);
|
|
2077
|
+
if (opts.json) {
|
|
2078
|
+
console.log(JSON.stringify({ event: "deleted", id }));
|
|
2079
|
+
} else {
|
|
2080
|
+
console.log(chalk15.green(`\u2713 removed ${id}`));
|
|
2081
|
+
}
|
|
2082
|
+
process.exitCode = 0;
|
|
2083
|
+
} catch (err) {
|
|
2084
|
+
printError4(opts, err);
|
|
2085
|
+
process.exitCode = 1;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
function parseUtcTimestamp(iso) {
|
|
2089
|
+
if (iso.endsWith("Z") || iso.endsWith("z")) {
|
|
2090
|
+
return Date.parse(iso);
|
|
2091
|
+
}
|
|
2092
|
+
const timePart = iso.slice(10);
|
|
2093
|
+
if (timePart.includes("+") || timePart.includes("-")) {
|
|
2094
|
+
return Date.parse(iso);
|
|
2095
|
+
}
|
|
2096
|
+
return Date.parse(iso + "Z");
|
|
2097
|
+
}
|
|
2098
|
+
var TEST_POLL_INTERVAL_MS = 3e3;
|
|
2099
|
+
var TEST_POLL_BUDGET_MS = 6e4;
|
|
2100
|
+
async function webhooksTest(id, opts, deps) {
|
|
2101
|
+
const d = deps ?? defaultDeps5(clientFor3(opts));
|
|
2102
|
+
try {
|
|
2103
|
+
const startedAt = d.now();
|
|
2104
|
+
await d.testWebhook(id);
|
|
2105
|
+
while (d.now() - startedAt < TEST_POLL_BUDGET_MS) {
|
|
2106
|
+
await d.sleep(TEST_POLL_INTERVAL_MS);
|
|
2107
|
+
const { webhooks: webhooks2 } = await d.listWebhooks();
|
|
2108
|
+
const ld = webhooks2.find((w) => w.id === id)?.last_delivery;
|
|
2109
|
+
if (ld && ld.status !== "pending" && ld.at != null && parseUtcTimestamp(ld.at) >= startedAt) {
|
|
2110
|
+
if (opts.json) console.log(JSON.stringify(ld));
|
|
2111
|
+
if (ld.status === "succeeded") {
|
|
2112
|
+
if (!opts.json) {
|
|
2113
|
+
console.log(chalk15.green(`\u2713 Delivered (HTTP ${ld.response_code})`));
|
|
2114
|
+
}
|
|
2115
|
+
process.exitCode = 0;
|
|
2116
|
+
} else {
|
|
2117
|
+
if (!opts.json) {
|
|
2118
|
+
const code = ld.response_code != null ? `HTTP ${ld.response_code}` : "no response";
|
|
2119
|
+
console.error(chalk15.red(`\u2717 Failed (${code}) at ${ld.at}`));
|
|
2120
|
+
}
|
|
2121
|
+
process.exitCode = 1;
|
|
2122
|
+
}
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
if (opts.json) console.log(JSON.stringify({ status: "pending" }));
|
|
2127
|
+
console.error(
|
|
2128
|
+
chalk15.yellow(
|
|
2129
|
+
`Delivery still pending after ${TEST_POLL_BUDGET_MS / 1e3}s \u2014 check \`vibeview webhooks list\` later for the result.`
|
|
2130
|
+
)
|
|
2131
|
+
);
|
|
2132
|
+
process.exitCode = 1;
|
|
2133
|
+
} catch (err) {
|
|
2134
|
+
printError4(opts, err);
|
|
2135
|
+
process.exitCode = 1;
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
function printError4(opts, err) {
|
|
2139
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2140
|
+
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2141
|
+
console.error(chalk15.red(`error: ${message}`));
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
// src/lib/formatters/devices.ts
|
|
2145
|
+
import chalk16 from "chalk";
|
|
1858
2146
|
var HEADERS2 = ["DEVICE_ID", "NAME", "TYPE", "CATEGORY", "OS", "STATUS"];
|
|
1859
2147
|
function formatDevicesHuman(devices) {
|
|
1860
2148
|
if (devices.length === 0) {
|
|
@@ -1870,9 +2158,9 @@ function formatDevicesHuman(devices) {
|
|
|
1870
2158
|
]);
|
|
1871
2159
|
const widths = HEADERS2.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1872
2160
|
const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
|
|
1873
|
-
const lines = [
|
|
2161
|
+
const lines = [chalk16.bold(fmt(HEADERS2))];
|
|
1874
2162
|
for (const row of rows.sort(rowSort)) {
|
|
1875
|
-
const statusColored = row[5] === "available" ?
|
|
2163
|
+
const statusColored = row[5] === "available" ? chalk16.green(row[5]) : row[5] === "busy" ? chalk16.yellow(row[5]) : chalk16.dim(row[5]);
|
|
1876
2164
|
lines.push(fmt([row[0], row[1], row[2], row[3], row[4], statusColored]));
|
|
1877
2165
|
}
|
|
1878
2166
|
return lines.join("\n") + "\n";
|
|
@@ -1956,28 +2244,28 @@ function readMetadataFile(path) {
|
|
|
1956
2244
|
}
|
|
1957
2245
|
|
|
1958
2246
|
// src/commands/agent-setup.ts
|
|
1959
|
-
import { copyFileSync, existsSync as existsSync2, mkdirSync } from "fs";
|
|
2247
|
+
import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1960
2248
|
import { homedir } from "os";
|
|
1961
|
-
import { dirname, join } from "path";
|
|
2249
|
+
import { dirname, join as join2 } from "path";
|
|
1962
2250
|
import { fileURLToPath } from "url";
|
|
1963
2251
|
import { spawnSync } from "child_process";
|
|
1964
2252
|
import { confirm } from "@inquirer/prompts";
|
|
1965
|
-
import
|
|
2253
|
+
import chalk17 from "chalk";
|
|
1966
2254
|
function resolveShippedSkillPath(fromDir) {
|
|
1967
|
-
for (const rel of ["..",
|
|
1968
|
-
const candidate =
|
|
2255
|
+
for (const rel of ["..", join2("..", "..")]) {
|
|
2256
|
+
const candidate = join2(fromDir, rel, "skills", "vibeview-agent", "SKILL.md");
|
|
1969
2257
|
if (existsSync2(candidate)) return candidate;
|
|
1970
2258
|
}
|
|
1971
2259
|
return null;
|
|
1972
2260
|
}
|
|
1973
2261
|
function skillInstallDir(project, cwd, home) {
|
|
1974
2262
|
const base = project ? cwd : home;
|
|
1975
|
-
return
|
|
2263
|
+
return join2(base, ".claude", "skills", "vibeview-agent");
|
|
1976
2264
|
}
|
|
1977
2265
|
function installSkill(shippedPath, targetDir) {
|
|
1978
|
-
const target =
|
|
2266
|
+
const target = join2(targetDir, "SKILL.md");
|
|
1979
2267
|
const existed = existsSync2(target);
|
|
1980
|
-
|
|
2268
|
+
mkdirSync2(targetDir, { recursive: true });
|
|
1981
2269
|
copyFileSync(shippedPath, target);
|
|
1982
2270
|
return existed ? "updated" : "installed";
|
|
1983
2271
|
}
|
|
@@ -1990,23 +2278,23 @@ function registerMcp() {
|
|
|
1990
2278
|
encoding: "utf8"
|
|
1991
2279
|
});
|
|
1992
2280
|
if (res.status === 0) {
|
|
1993
|
-
console.log(
|
|
2281
|
+
console.log(chalk17.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
|
|
1994
2282
|
return;
|
|
1995
2283
|
}
|
|
1996
2284
|
const out = `${res.stderr ?? ""}${res.stdout ?? ""}`;
|
|
1997
2285
|
if (out.includes("already exists")) {
|
|
1998
|
-
console.log(
|
|
2286
|
+
console.log(chalk17.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
|
|
1999
2287
|
return;
|
|
2000
2288
|
}
|
|
2001
|
-
console.error(
|
|
2002
|
-
console.error(
|
|
2289
|
+
console.error(chalk17.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
|
|
2290
|
+
console.error(chalk17.yellow(" register it manually: claude mcp add vibeview -- vibeview mcp"));
|
|
2003
2291
|
}
|
|
2004
2292
|
async function agentSetup(opts) {
|
|
2005
2293
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
2006
2294
|
const shipped = resolveShippedSkillPath(moduleDir);
|
|
2007
2295
|
if (!shipped) {
|
|
2008
2296
|
console.error(
|
|
2009
|
-
|
|
2297
|
+
chalk17.red(
|
|
2010
2298
|
"error: could not find the packaged skill (skills/vibeview-agent/SKILL.md) \u2014 is this a complete install of the vibeview package?"
|
|
2011
2299
|
)
|
|
2012
2300
|
);
|
|
@@ -2015,9 +2303,9 @@ async function agentSetup(opts) {
|
|
|
2015
2303
|
}
|
|
2016
2304
|
const targetDir = skillInstallDir(opts.project ?? false, process.cwd(), homedir());
|
|
2017
2305
|
const result = installSkill(shipped, targetDir);
|
|
2018
|
-
console.log(
|
|
2306
|
+
console.log(chalk17.green(`\u2713 vibeview-agent skill ${result}: ${join2(targetDir, "SKILL.md")}`));
|
|
2019
2307
|
console.log(
|
|
2020
|
-
|
|
2308
|
+
chalk17.dim(
|
|
2021
2309
|
opts.project ? " Claude Code discovers it automatically for anyone working in this project." : " Claude Code discovers it automatically in every project on this machine."
|
|
2022
2310
|
)
|
|
2023
2311
|
);
|
|
@@ -2025,7 +2313,7 @@ async function agentSetup(opts) {
|
|
|
2025
2313
|
if (!claudeCliAvailable()) {
|
|
2026
2314
|
if (opts.mcp === true) {
|
|
2027
2315
|
console.error(
|
|
2028
|
-
|
|
2316
|
+
chalk17.yellow(
|
|
2029
2317
|
"\u26A0 claude CLI not found \u2014 install Claude Code, then run: claude mcp add vibeview -- vibeview mcp"
|
|
2030
2318
|
)
|
|
2031
2319
|
);
|
|
@@ -2039,7 +2327,7 @@ async function agentSetup(opts) {
|
|
|
2039
2327
|
}
|
|
2040
2328
|
console.log(
|
|
2041
2329
|
`
|
|
2042
|
-
Suggested line for your project's ${
|
|
2330
|
+
Suggested line for your project's ${chalk17.bold("CLAUDE.md")} (or ${chalk17.bold(
|
|
2043
2331
|
"AGENTS.md"
|
|
2044
2332
|
)} for other agents):
|
|
2045
2333
|
`
|
|
@@ -2047,7 +2335,7 @@ Suggested line for your project's ${chalk16.bold("CLAUDE.md")} (or ${chalk16.bol
|
|
|
2047
2335
|
console.log(` ${CLAUDE_MD_SUGGESTION}
|
|
2048
2336
|
`);
|
|
2049
2337
|
console.log(
|
|
2050
|
-
|
|
2338
|
+
chalk17.dim(
|
|
2051
2339
|
"Other agents (Cursor, Copilot, Codex, ...): point them at the skill file above, or\nadd the MCP server per client \u2014 see https://vibeview.io/docs/agent-control"
|
|
2052
2340
|
)
|
|
2053
2341
|
);
|
|
@@ -2239,14 +2527,35 @@ credentials.command("add").description(
|
|
|
2239
2527
|
"--profile <path>",
|
|
2240
2528
|
"ios/tvos: the .mobileprovision file (required; tvOS profiles are distinct from iOS ones)"
|
|
2241
2529
|
).option("--app <id>", "Scope to a single app (default: org-wide)").option("--name <name>", "Credential name (default: the file basename without extension)").action((opts) => credentialsAdd(opts));
|
|
2530
|
+
credentials.command("generate").description(
|
|
2531
|
+
"Generate an Android keystore server-side and store it as a signing credential. Saves the keystore file locally and prints its passwords ONCE \u2014 they can never be retrieved again."
|
|
2532
|
+
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option(
|
|
2533
|
+
"--platform <platform>",
|
|
2534
|
+
"android (iOS/tvOS certificates require an Apple Developer account)"
|
|
2535
|
+
).option("--name <name>", "Credential name").option("--app <id>", "Scope to a single app (default: org-wide)").option("--alias <alias>", "Key alias (default: server-chosen)").option("--out <dir>", "Directory to save the keystore file into (default: current directory)").option(
|
|
2536
|
+
"--force",
|
|
2537
|
+
"Replace an existing credential of the same name \u2014 irreversible; breaks Play Store updates signed with the old key",
|
|
2538
|
+
false
|
|
2539
|
+
).option(
|
|
2540
|
+
"--json",
|
|
2541
|
+
"Print the raw generation response (including the one-time secrets) as JSON",
|
|
2542
|
+
false
|
|
2543
|
+
).action((opts) => credentialsGenerate(opts));
|
|
2242
2544
|
credentials.command("list").description("List signing credentials (never prints key material)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide credentials only)").option("--json", "Print the raw credential list as a JSON array", false).action((opts) => credentialsList(opts));
|
|
2243
2545
|
credentials.command("rm <name>").description("Remove a signing credential (replace by rm then add)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide)").option("--json", "Print the result as one JSON line", false).action((name, opts) => credentialsRm(name, opts));
|
|
2546
|
+
var webhooks = program.command("webhooks").description("Manage build-completion webhook endpoints");
|
|
2547
|
+
webhooks.command("add").description(
|
|
2548
|
+
"Add a webhook endpoint (HTTPS only). Prints the signing secret ONCE \u2014 it can never be retrieved again."
|
|
2549
|
+
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--url <url>", "The HTTPS URL to POST build events to").option("--secret <secret>", "Your own signing secret (default: server-generated)").option("--json", "Print the raw creation response (including the secret) as JSON", false).action((opts) => webhooksAdd(opts));
|
|
2550
|
+
webhooks.command("list").description("List webhook endpoints with their last delivery status (never prints secrets)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the raw webhook list as a JSON array", false).action((opts) => webhooksList(opts));
|
|
2551
|
+
webhooks.command("rm <id>").description("Remove a webhook endpoint (rotate the secret by rm then add)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the result as one JSON line", false).action((id, opts) => webhooksRm(id, opts));
|
|
2552
|
+
webhooks.command("test <id>").description("Send a ping delivery and wait up to 60s for the result").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the final delivery result as one JSON line", false).action((id, opts) => webhooksTest(id, opts));
|
|
2244
2553
|
program.command("dev-status").description("Show the status of a `vibeview dev --detach` session in this project").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print as one JSON line", false).action((opts) => devStatus(opts));
|
|
2245
2554
|
program.command("dev-stop").description("Stop a `vibeview dev --detach` session in this project").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print as one JSON line", false).action((opts) => devStop(opts));
|
|
2246
2555
|
program.command("mcp").description(
|
|
2247
2556
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop as tools"
|
|
2248
2557
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
2249
|
-
const { runMcpServer } = await import("./server-
|
|
2558
|
+
const { runMcpServer } = await import("./server-AZLB25BY.js");
|
|
2250
2559
|
await runMcpServer(opts);
|
|
2251
2560
|
});
|
|
2252
2561
|
program.command("agent-setup").description(
|
|
@@ -11,11 +11,11 @@ import {
|
|
|
11
11
|
readDevState,
|
|
12
12
|
renderEnvelope,
|
|
13
13
|
startDevSession
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-3IMJ7FNN.js";
|
|
15
15
|
import {
|
|
16
16
|
ApiClient,
|
|
17
17
|
resolveAuth
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-SYFMOPJW.js";
|
|
19
19
|
|
|
20
20
|
// src/mcp/server.ts
|
|
21
21
|
import { writeFileSync } from "fs";
|
|
@@ -239,7 +239,7 @@ async function handleUploadApp(args, deps) {
|
|
|
239
239
|
}
|
|
240
240
|
const name = typeof args.name === "string" && args.name ? args.name : void 0;
|
|
241
241
|
const upload = deps.uploadApp ?? (async (f, n) => {
|
|
242
|
-
const { uploadAppBuild } = await import("./upload-app-
|
|
242
|
+
const { uploadAppBuild } = await import("./upload-app-XRB7CKA3.js");
|
|
243
243
|
const { result: result2 } = await uploadAppBuild(f, { ...deps.auth, name: n });
|
|
244
244
|
return result2;
|
|
245
245
|
});
|