vibeview 0.52.2 → 0.54.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/README.md CHANGED
@@ -91,6 +91,7 @@ Build-time secrets work the same way:
91
91
 
92
92
  ```bash
93
93
  vibeview env set NPM_TOKEN "..." # secret by default, injected into builds
94
+ vibeview env set API_URL "https://staging.example.com" --plain --profile preview # only preview builds
94
95
  vibeview webhooks add --url https://ci.example.com/hooks/vibeview
95
96
  ```
96
97
 
@@ -13,7 +13,7 @@ import {
13
13
  resolveArtifact,
14
14
  resolveAuth,
15
15
  uploadAppBuild
16
- } from "./chunk-LRPZFDKL.js";
16
+ } from "./chunk-R4GTQPZH.js";
17
17
 
18
18
  // src/lib/formatters/not-checked.ts
19
19
  function notCheckedSteps(steps) {
@@ -5566,6 +5566,15 @@ var AGENT_COMMANDS = [
5566
5566
  }
5567
5567
  ]
5568
5568
  },
5569
+ {
5570
+ // shake: {} — the same control as the dashboard's Shake button. One shake
5571
+ // per call. Phones, tablets and foldables; refused on TV devices.
5572
+ verb: "shake",
5573
+ tool: "shake",
5574
+ summary: "Shake the device once (phones, tablets and foldables)",
5575
+ mutating: true,
5576
+ params: []
5577
+ },
5569
5578
  {
5570
5579
  // set_location: { latitude: -90..90, longitude: -180..180 } — both
5571
5580
  // required (agent_control.py validate_tool_args refuses anything else).
@@ -6074,6 +6083,7 @@ export {
6074
6083
  stepsNotCheckedLabel,
6075
6084
  notCheckedSuffix,
6076
6085
  outcomeOf,
6086
+ PROFILE_NAME_RE,
6077
6087
  developmentProfile,
6078
6088
  DEV_PLATFORMS,
6079
6089
  PLATFORM_NEEDS_METRO,
@@ -560,7 +560,8 @@ var ApiClient = class {
560
560
  return this.requestRaw("GET", `/build-env/${qs}`);
561
561
  }
562
562
  /** POST /api/v1/build-env/ (JSON body) for a plain/sensitive/secret value.
563
- * Requires ADMIN role. Upserts by (org, app, name). */
563
+ * Requires ADMIN role. Upserts by (org, app, profile, name); `profile`
564
+ * omitted = every build profile. */
564
565
  async setBuildEnvVar(body) {
565
566
  return this.requestRaw("POST", "/build-env/", body);
566
567
  }
@@ -573,6 +574,7 @@ var ApiClient = class {
573
574
  formData.append("name", body.name);
574
575
  formData.append("visibility", body.visibility);
575
576
  if (body.app) formData.append("app", body.app);
577
+ if (body.profile) formData.append("profile", body.profile);
576
578
  const blob = new Blob([new Uint8Array(body.content)]);
577
579
  formData.append("file", blob, body.filename);
578
580
  return this.requestFormData("POST", "/build-env/", formData);
@@ -585,11 +587,15 @@ var ApiClient = class {
585
587
  async deleteBuildEnvVar(id) {
586
588
  await this.requestRaw("DELETE", `/build-env/${id}`);
587
589
  }
588
- /** GET /api/v1/build-env/pull[?app=]. Requires DEVELOPER role. The only
589
- * endpoint that returns values — already excludes secret/file variables
590
- * server-side. */
591
- async pullBuildEnvVars(app) {
592
- const qs = app ? `?app=${encodeURIComponent(app)}` : "";
590
+ /** GET /api/v1/build-env/pull[?app=][&profile=]. Requires DEVELOPER role.
591
+ * The only endpoint that returns values — already excludes secret/file
592
+ * variables server-side. With `profile`, the values a build of that
593
+ * profile gets (precedence resolved server-side). */
594
+ async pullBuildEnvVars(app, profile) {
595
+ const params = new URLSearchParams();
596
+ if (app) params.set("app", app);
597
+ if (profile) params.set("profile", profile);
598
+ const qs = params.toString() ? `?${params.toString()}` : "";
593
599
  return this.requestRaw("GET", `/build-env/pull${qs}`);
594
600
  }
595
601
  /** GET /api/v1/signing-credentials/[?app=]. Requires VIEWER role. Never
@@ -670,8 +676,26 @@ var ApiClient = class {
670
676
  async deletePlayKey(id) {
671
677
  await this.requestRaw("DELETE", `/signing-credentials/play-keys/${id}`);
672
678
  }
679
+ /** GET /api/v1/signing-credentials/amazon-keys. VIEWER role. */
680
+ async listAmazonKeys() {
681
+ return this.requestRaw("GET", "/signing-credentials/amazon-keys");
682
+ }
683
+ /** POST /api/v1/signing-credentials/amazon-keys (JSON). ADMIN role. The
684
+ * backend mints an Amazon token first, so bad credentials fail here. */
685
+ async addAmazonKey(body) {
686
+ return this.requestRaw("POST", "/signing-credentials/amazon-keys", {
687
+ ...body.name !== void 0 ? { name: body.name } : {},
688
+ client_id: body.clientId,
689
+ client_secret: body.clientSecret
690
+ });
691
+ }
692
+ /** DELETE /api/v1/signing-credentials/amazon-keys/{id}. ADMIN role. */
693
+ async deleteAmazonKey(id) {
694
+ await this.requestRaw("DELETE", `/signing-credentials/amazon-keys/${id}`);
695
+ }
673
696
  /** POST /api/v1/submissions — submit a succeeded production build's
674
- * artifact to TestFlight (ios/tvos) or a Play track (android/androidtv).
697
+ * artifact to TestFlight (ios/tvos), a Play track (android/androidtv), or
698
+ * Amazon Appstore review (android/androidtv with store "amazon").
675
699
  * `platform` is always sent: the backend derives the real platform from
676
700
  * the build row and 400s on a mismatch, so a pasted wrong --build id
677
701
  * fails loudly instead of silently submitting to the other store family.
@@ -688,16 +712,22 @@ var ApiClient = class {
688
712
  ...body.ascKey !== void 0 ? { asc_key: body.ascKey } : {},
689
713
  ...body.releaseStatus !== void 0 ? { release_status: body.releaseStatus } : {},
690
714
  ...body.rollout !== void 0 ? { rollout: body.rollout } : {},
691
- ...body.changesNotSentForReview !== void 0 ? { changes_not_sent_for_review: body.changesNotSentForReview } : {}
715
+ ...body.changesNotSentForReview !== void 0 ? { changes_not_sent_for_review: body.changesNotSentForReview } : {},
716
+ ...body.store !== void 0 ? { store: body.store } : {},
717
+ ...body.amazonAppId !== void 0 ? { amazon_app_id: body.amazonAppId } : {},
718
+ ...body.releaseNotes !== void 0 ? { release_notes: body.releaseNotes } : {},
719
+ ...body.discardOpenEdit !== void 0 ? { discard_open_edit: body.discardOpenEdit } : {}
692
720
  });
693
721
  }
694
722
  /** GET /api/v1/submissions/preflight — is the store ready to receive
695
- * this app? Answers by identifier (bundle id / package name), so it can
696
- * run before any build exists. Always resolves — a "not ready" is an
723
+ * this app? Answers by identifier (bundle id / package name, or an
724
+ * Amazon App ID with store "amazon"), so it can run before any build
725
+ * exists. Always resolves — a "not ready" is an
697
726
  * answer carried in `status`/`message`, never a thrown error. */
698
727
  async checkStorePreflight(q) {
699
728
  const params = new URLSearchParams({ platform: q.platform, identifier: q.identifier });
700
729
  if (q.ascKey !== void 0) params.set("asc_key", q.ascKey);
730
+ if (q.store !== void 0) params.set("store", q.store);
701
731
  return this.requestRaw("GET", `/submissions/preflight?${params.toString()}`);
702
732
  }
703
733
  /** GET /api/v1/submissions?build=<id> — that build's submissions, newest
package/dist/cli.js CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  BUILD_TERMINAL_STATUSES,
5
5
  FEEDBACK_KINDS,
6
6
  FEEDBACK_RULES,
7
+ PROFILE_NAME_RE,
7
8
  buildCommand,
8
9
  clearDevState,
9
10
  createRokuPush,
@@ -35,7 +36,7 @@ import {
35
36
  runAgentVerb,
36
37
  runStepsNotChecked,
37
38
  stepsNotCheckedLabel
38
- } from "./chunk-S4VPSFPP.js";
39
+ } from "./chunk-77OGQKZQ.js";
39
40
  import {
40
41
  ApiClient,
41
42
  ApiStatusError,
@@ -58,7 +59,7 @@ import {
58
59
  runCommandParseExitCode,
59
60
  saveConfig,
60
61
  uploadApp
61
- } from "./chunk-LRPZFDKL.js";
62
+ } from "./chunk-R4GTQPZH.js";
62
63
 
63
64
  // src/cli.ts
64
65
  import { createRequire } from "module";
@@ -2376,6 +2377,14 @@ var SUBMIT_PLATFORMS = ["ios", "tvos", "android", "androidtv"];
2376
2377
  function isSubmitPlatform(p) {
2377
2378
  return SUBMIT_PLATFORMS.includes(p);
2378
2379
  }
2380
+ var SUBMIT_STORES = {
2381
+ ios: ["app_store"],
2382
+ tvos: ["app_store"],
2383
+ android: ["play", "amazon"],
2384
+ androidtv: ["play", "amazon"]
2385
+ };
2386
+ var AMAZON_APP_ID_RE = /^amzn1\.devportal\.mobileapp\.[0-9a-f]{32}$/;
2387
+ var AMAZON_APK_PROFILE_HINT = 'Amazon needs a build profile with "distribution": "production" whose command builds a signed .apk (e.g. ./gradlew assembleRelease) \u2014 see https://vibeview.io/docs/store-submission#build-an-apk.';
2379
2388
  var PLAY_TRACKS = ["internal", "alpha", "beta", "production"];
2380
2389
  var RELEASE_STATUSES = ["completed", "draft", "inProgress"];
2381
2390
  function submitConfigFor(cwd, platform) {
@@ -2431,9 +2440,25 @@ async function submitCommand(opts, deps) {
2431
2440
  }
2432
2441
  const platform = opts.platform;
2433
2442
  const androidFamily = platform === "android" || platform === "androidtv";
2443
+ if (opts.store !== void 0) {
2444
+ if (!androidFamily) {
2445
+ throw new UsageError(
2446
+ `--store only applies to android/androidtv (Google Play or Amazon Appstore) \u2014 ${platform} builds go to TestFlight`
2447
+ );
2448
+ }
2449
+ if (!SUBMIT_STORES[platform].includes(opts.store)) {
2450
+ throw new UsageError(
2451
+ `--store must be one of ${SUBMIT_STORES[platform].join(", ")} \u2014 got "${opts.store}"`
2452
+ );
2453
+ }
2454
+ }
2455
+ const amazon = opts.store === "amazon";
2456
+ const play = androidFamily && !amazon;
2457
+ const apple = !androidFamily;
2458
+ const target = amazon ? "--store amazon" : platform;
2434
2459
  if (opts.check && (opts.build || opts.latest)) {
2435
2460
  throw new UsageError(
2436
- "--check looks the app up by identifier, not by build \u2014 drop --build/--latest (pass --bundle-id for ios/tvos or --package for android/androidtv)"
2461
+ "--check looks the app up by identifier, not by build \u2014 drop --build/--latest " + (amazon ? "(pass --amazon-app-id)" : "(pass --bundle-id for ios/tvos or --package for android/androidtv)")
2437
2462
  );
2438
2463
  }
2439
2464
  if (!opts.check && opts.bundleId !== void 0) {
@@ -2449,38 +2474,63 @@ async function submitCommand(opts, deps) {
2449
2474
  "pass --build <id> (see `vibeview builds list`) or --latest (the newest succeeded production build for this platform)"
2450
2475
  );
2451
2476
  }
2452
- if (!androidFamily && opts.track !== void 0) {
2453
- throw new UsageError(`--track only applies to android/androidtv (Play) \u2014 not ${platform}`);
2477
+ if (!play && opts.track !== void 0) {
2478
+ throw new UsageError(`--track only applies to android/androidtv (Play) \u2014 not ${target}`);
2479
+ }
2480
+ if (!play && opts.package !== void 0) {
2481
+ throw new UsageError(`--package only applies to android/androidtv (Play) \u2014 not ${target}`);
2454
2482
  }
2455
- if (!androidFamily && opts.package !== void 0) {
2456
- throw new UsageError(`--package only applies to android/androidtv (Play) \u2014 not ${platform}`);
2483
+ if (!apple && opts.ascKey !== void 0) {
2484
+ throw new UsageError(`--asc-key only applies to ios/tvos (TestFlight) \u2014 not ${target}`);
2457
2485
  }
2458
- if (androidFamily && opts.ascKey !== void 0) {
2459
- throw new UsageError(`--asc-key only applies to ios/tvos (TestFlight) \u2014 not ${platform}`);
2486
+ if (!apple && opts.bundleId !== void 0) {
2487
+ throw new UsageError(
2488
+ amazon ? "--bundle-id only applies to ios/tvos \u2014 for --store amazon pass --amazon-app-id <id>" : `--bundle-id only applies to ios/tvos \u2014 for ${platform} pass --package <name>`
2489
+ );
2460
2490
  }
2461
- if (androidFamily && opts.bundleId !== void 0) {
2491
+ if (!play && opts.releaseStatus !== void 0) {
2462
2492
  throw new UsageError(
2463
- `--bundle-id only applies to ios/tvos \u2014 for ${platform} pass --package <name>`
2493
+ `--release-status only applies to android/androidtv (Play) \u2014 not ${target}`
2464
2494
  );
2465
2495
  }
2466
- if (!androidFamily && opts.releaseStatus !== void 0) {
2496
+ if (!play && opts.rollout !== void 0) {
2497
+ throw new UsageError(`--rollout only applies to android/androidtv (Play) \u2014 not ${target}`);
2498
+ }
2499
+ if (!amazon) {
2500
+ for (const [flag, value] of [
2501
+ ["--amazon-app-id", opts.amazonAppId],
2502
+ ["--release-notes", opts.releaseNotes],
2503
+ ["--discard-open-edit", opts.discardOpenEdit || void 0]
2504
+ ]) {
2505
+ if (value !== void 0) {
2506
+ throw new UsageError(
2507
+ `${flag} only applies with --store amazon (Amazon Appstore, android/androidtv)`
2508
+ );
2509
+ }
2510
+ }
2511
+ }
2512
+ const amazonAppId = opts.amazonAppId?.trim() || void 0;
2513
+ if (amazonAppId !== void 0 && !AMAZON_APP_ID_RE.test(amazonAppId)) {
2467
2514
  throw new UsageError(
2468
- `--release-status only applies to android/androidtv (Play) \u2014 not ${platform}`
2515
+ `--amazon-app-id must be an Amazon App ID (amzn1.devportal.mobileapp. followed by 32 hex characters) \u2014 got "${amazonAppId}"`
2469
2516
  );
2470
2517
  }
2471
- if (!androidFamily && opts.rollout !== void 0) {
2472
- throw new UsageError(`--rollout only applies to android/androidtv (Play) \u2014 not ${platform}`);
2518
+ const releaseNotes = opts.releaseNotes?.trim() || void 0;
2519
+ if (amazon && !opts.check && releaseNotes === void 0) {
2520
+ throw new UsageError(
2521
+ '--release-notes "<text>" is required with --store amazon \u2014 they are written to every language of your Amazon listing'
2522
+ );
2473
2523
  }
2474
- const cfgDefaults = submitConfigFor(process.cwd(), platform);
2475
- const track = opts.track ?? (androidFamily ? cfgDefaults.track : void 0);
2476
- const releaseStatus = opts.releaseStatus ?? (androidFamily ? cfgDefaults.releaseStatus : void 0);
2524
+ const cfgDefaults = amazon ? {} : submitConfigFor(process.cwd(), platform);
2525
+ const track = opts.track ?? (play ? cfgDefaults.track : void 0);
2526
+ const releaseStatus = opts.releaseStatus ?? (play ? cfgDefaults.releaseStatus : void 0);
2477
2527
  const statusUnchanged = opts.releaseStatus === void 0 || opts.releaseStatus === cfgDefaults.releaseStatus;
2478
- const rollout = opts.rollout !== void 0 ? validateRolloutFraction(Number(opts.rollout), "--rollout", opts.rollout) : androidFamily && statusUnchanged && cfgDefaults.rollout !== void 0 ? validateRolloutFraction(
2528
+ const rollout = opts.rollout !== void 0 ? validateRolloutFraction(Number(opts.rollout), "--rollout", opts.rollout) : play && statusUnchanged && cfgDefaults.rollout !== void 0 ? validateRolloutFraction(
2479
2529
  cfgDefaults.rollout,
2480
2530
  `vibeview.json platforms.${platform}.submit.rollout`
2481
2531
  ) : void 0;
2482
- const changesNotSentForReview = androidFamily ? cfgDefaults.changesNotSentForReview : void 0;
2483
- const ascKey2 = opts.ascKey ?? (!androidFamily ? cfgDefaults.ascKey : void 0);
2532
+ const changesNotSentForReview = play ? cfgDefaults.changesNotSentForReview : void 0;
2533
+ const ascKey2 = opts.ascKey ?? (apple ? cfgDefaults.ascKey : void 0);
2484
2534
  if (track !== void 0 && !PLAY_TRACKS.includes(track)) {
2485
2535
  const source = opts.track !== void 0 ? "--track" : `vibeview.json platforms.${platform}.submit.track`;
2486
2536
  throw new UsageError(`${source} must be one of ${PLAY_TRACKS.join(", ")} \u2014 got "${track}"`);
@@ -2505,11 +2555,11 @@ async function submitCommand(opts, deps) {
2505
2555
  const client = new ApiClient(baseUrl, token);
2506
2556
  const d = deps ?? defaultDeps2(client);
2507
2557
  if (opts.check) {
2508
- await checkStore(platform, androidFamily, ascKey2, opts, d);
2558
+ await checkStore(platform, { androidFamily, amazon, amazonAppId }, ascKey2, opts, d);
2509
2559
  return;
2510
2560
  }
2511
2561
  try {
2512
- const buildId = opts.build ?? await resolveLatestBuild(platform, d);
2562
+ const buildId = opts.build ?? await resolveLatestBuild(platform, amazon, d);
2513
2563
  const sub = await d.createSubmission({
2514
2564
  build: buildId,
2515
2565
  platform,
@@ -2518,7 +2568,11 @@ async function submitCommand(opts, deps) {
2518
2568
  ...ascKey2 !== void 0 ? { ascKey: ascKey2 } : {},
2519
2569
  ...releaseStatus !== void 0 ? { releaseStatus } : {},
2520
2570
  ...rollout !== void 0 ? { rollout } : {},
2521
- ...changesNotSentForReview !== void 0 ? { changesNotSentForReview } : {}
2571
+ ...changesNotSentForReview !== void 0 ? { changesNotSentForReview } : {},
2572
+ ...amazon ? { store: "amazon" } : {},
2573
+ ...amazonAppId !== void 0 ? { amazonAppId } : {},
2574
+ ...releaseNotes !== void 0 ? { releaseNotes } : {},
2575
+ ...amazon && opts.discardOpenEdit ? { discardOpenEdit: true } : {}
2522
2576
  });
2523
2577
  emit(opts.json, {
2524
2578
  event: "submission_created",
@@ -2546,13 +2600,14 @@ async function submitCommand(opts, deps) {
2546
2600
  process.exitCode = errorExitCode(err);
2547
2601
  }
2548
2602
  }
2549
- async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
2550
- const flagged = androidFamily ? opts.package : opts.bundleId;
2551
- const usage = androidFamily ? `--check needs the package name to look up: --package <name> (e.g. --package io.example.app)` : `--check needs the bundle identifier to look up: --bundle-id <id> (e.g. --bundle-id io.example.app)`;
2603
+ async function checkStore(platform, family, ascKey2, opts, deps) {
2604
+ const { androidFamily, amazon } = family;
2605
+ const flagged = amazon ? family.amazonAppId : androidFamily ? opts.package : opts.bundleId;
2606
+ const usage = amazon ? "--check needs the Amazon App ID to look up: --amazon-app-id <id> (in the Amazon Developer Console open the app, then Upload Your App File \u2192 Additional information \u2192 App ID)" : androidFamily ? `--check needs the package name to look up: --package <name> (e.g. --package io.example.app)` : `--check needs the bundle identifier to look up: --bundle-id <id> (e.g. --bundle-id io.example.app)`;
2552
2607
  let identifier = flagged;
2553
2608
  if (!identifier) {
2554
2609
  try {
2555
- identifier = await linkedIdentifier(platform, opts, deps);
2610
+ identifier = await linkedIdentifier(platform, amazon, opts, deps);
2556
2611
  } catch (err) {
2557
2612
  printError2(opts, err);
2558
2613
  process.exitCode = errorExitCode(err);
@@ -2564,7 +2619,8 @@ async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
2564
2619
  const result = await deps.checkStorePreflight({
2565
2620
  platform,
2566
2621
  identifier,
2567
- ...ascKey2 !== void 0 ? { ascKey: ascKey2 } : {}
2622
+ ...ascKey2 !== void 0 ? { ascKey: ascKey2 } : {},
2623
+ ...amazon ? { store: "amazon" } : {}
2568
2624
  });
2569
2625
  emit(opts.json, { event: "preflight", ...result });
2570
2626
  if (!opts.json) {
@@ -2573,7 +2629,17 @@ async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
2573
2629
  if (result.ready) {
2574
2630
  console.error(
2575
2631
  chalk17.dim(
2576
- ` next: vibeview build --cloud --production --platform ${platform}, then vibeview submit --platform ${platform} --latest`
2632
+ amazon ? (
2633
+ // Amazon takes an .apk — under the documented setup that is its
2634
+ // own profile, not --production (an .aab). The profile must
2635
+ // say "distribution": "production" (a prompt-created profile
2636
+ // has none, so it builds for the simulator and --latest never
2637
+ // finds it).
2638
+ // The suggested command never carries --discard-open-edit:
2639
+ // deleting the user's edit is their call, on its own line.
2640
+ ` next: vibeview build --cloud --platform ${platform} --profile <your .apk profile, e.g. amazon>, then vibeview submit --platform ${platform} --store amazon --latest --release-notes "<what's new in this version>"` + (flagged ? ` --amazon-app-id ${flagged}` : "") + `
2641
+ ${AMAZON_APK_PROFILE_HINT}` + (result.open_edit ? "\n An edit is open in the Amazon Developer Console \u2014 finish or delete it there, or add --discard-open-edit to delete it (and the changes in it)." : "")
2642
+ ) : ` next: vibeview build --cloud --production --platform ${platform}, then vibeview submit --platform ${platform} --latest`
2577
2643
  )
2578
2644
  );
2579
2645
  }
@@ -2584,7 +2650,7 @@ async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
2584
2650
  process.exitCode = errorExitCode(err);
2585
2651
  }
2586
2652
  }
2587
- async function linkedIdentifier(platform, opts, deps) {
2653
+ async function linkedIdentifier(platform, amazon, opts, deps) {
2588
2654
  if (!deps.getApp) return void 0;
2589
2655
  let appId;
2590
2656
  try {
@@ -2599,18 +2665,19 @@ async function linkedIdentifier(platform, opts, deps) {
2599
2665
  } catch (err) {
2600
2666
  if (err instanceof NotFoundError) {
2601
2667
  throw new Error(
2602
- `the app linked in vibeview.json for ${platform} (${appId}) no longer exists \u2014 ` + (platform === "android" || platform === "androidtv" ? "pass --package <name>" : "pass --bundle-id <id>")
2668
+ `the app linked in vibeview.json for ${platform} (${appId}) no longer exists \u2014 ` + (amazon ? "pass --amazon-app-id <id>" : platform === "android" || platform === "androidtv" ? "pass --package <name>" : "pass --bundle-id <id>")
2603
2669
  );
2604
2670
  }
2605
2671
  throw err;
2606
2672
  }
2607
- if (!app.package_name) return void 0;
2673
+ const identifier = amazon ? app.amazon_app_id ?? void 0 : app.package_name;
2674
+ if (!identifier) return void 0;
2608
2675
  if (!opts.json) {
2609
- console.error(chalk17.dim(`Checking ${app.package_name} (${app.name}, linked in vibeview.json)`));
2676
+ console.error(chalk17.dim(`Checking ${identifier} (${app.name}, linked in vibeview.json)`));
2610
2677
  }
2611
- return app.package_name;
2678
+ return identifier;
2612
2679
  }
2613
- async function resolveLatestBuild(platform, deps) {
2680
+ async function resolveLatestBuild(platform, apkOnly, deps) {
2614
2681
  const { builds: builds2 } = await deps.listCloudBuilds({
2615
2682
  platform,
2616
2683
  status: "succeeded",
@@ -2618,8 +2685,13 @@ async function resolveLatestBuild(platform, deps) {
2618
2685
  limit: 200
2619
2686
  });
2620
2687
  const match = builds2.find(
2621
- (b) => b.platform === platform && b.distribution === "production" && b.status === "succeeded" && b.has_artifact
2688
+ (b) => b.platform === platform && b.distribution === "production" && b.status === "succeeded" && b.has_artifact && (!apkOnly || (b.artifact_filename ?? "").toLowerCase().endsWith(".apk"))
2622
2689
  );
2690
+ if (!match && apkOnly) {
2691
+ throw new Error(
2692
+ `no submittable ${platform} .apk build found \u2014 Amazon's API accepts only .apk files, so --latest needs a succeeded production .apk build whose artifact is still stored. ${AMAZON_APK_PROFILE_HINT} Or pass --build <id> (see \`vibeview builds list\`).`
2693
+ );
2694
+ }
2623
2695
  if (!match) {
2624
2696
  throw new Error(
2625
2697
  `no submittable ${platform} build found \u2014 --latest needs a succeeded production build whose artifact is still stored. Run \`vibeview build --cloud --production --platform ${platform}\` first, or pass --build <id> (see \`vibeview builds list\`).`
@@ -2882,6 +2954,17 @@ function isGitIgnored(path, cwd = process.cwd()) {
2882
2954
  }
2883
2955
 
2884
2956
  // src/commands/env.ts
2957
+ function checkProfileOption(profile) {
2958
+ if (profile === void 0) return;
2959
+ if (!PROFILE_NAME_RE.test(profile)) {
2960
+ throw new Error(
2961
+ `--profile must be a build profile name (lowercase letters, digits and dashes, starting with a letter), got "${profile}"`
2962
+ );
2963
+ }
2964
+ }
2965
+ function profileLabel(profile) {
2966
+ return profile ? `profile "${profile}"` : "every profile";
2967
+ }
2885
2968
  function clientFor(opts) {
2886
2969
  const { token, baseUrl } = resolveAuth(opts);
2887
2970
  return new ApiClient(baseUrl, token);
@@ -2900,10 +2983,20 @@ function defaultDeps5(client) {
2900
2983
  listVars: (app) => client.listBuildEnvVars(app),
2901
2984
  setVar: (body) => client.setBuildEnvVar(body),
2902
2985
  setFileVar: (body) => client.setBuildEnvFileVar(body),
2903
- rmVar: async ({ name, app }) => {
2986
+ rmVar: async ({ name, app, profile }) => {
2904
2987
  const { variables } = await client.listBuildEnvVars(app);
2905
2988
  const wantApp = app ?? null;
2906
- const match = variables.find((v) => v.name === name && (v.app_public_id ?? null) === wantApp);
2989
+ const wantProfile = profile ?? null;
2990
+ const inScope = variables.filter(
2991
+ (v) => v.name === name && (v.app_public_id ?? null) === wantApp
2992
+ );
2993
+ const match = inScope.find((v) => (v.profile ?? null) === wantProfile);
2994
+ if (!match && inScope.length > 0) {
2995
+ const others = inScope.map((v) => v.profile ? `--profile ${v.profile}` : "no --profile (every profile)").join(", ");
2996
+ throw new Error(
2997
+ `No variable named "${name}" for ${profileLabel(wantProfile)}${app ? ` in app "${app}"` : ""}. It exists for: ${others}. Re-run with the matching --profile to remove one.`
2998
+ );
2999
+ }
2907
3000
  if (!match) {
2908
3001
  const otherScope = variables.find((v) => v.name === name);
2909
3002
  if (otherScope) {
@@ -2915,7 +3008,7 @@ function defaultDeps5(client) {
2915
3008
  }
2916
3009
  await client.deleteBuildEnvVar(match.id);
2917
3010
  },
2918
- pullVars: (app) => client.pullBuildEnvVars(app),
3011
+ pullVars: (app, profile) => client.pullBuildEnvVars(app, profile),
2919
3012
  // `hidden` is always true in practice (env set only prompts for secret
2920
3013
  // values), but the parameter exists so a future caller/test can't
2921
3014
  // accidentally get echoed input by omitting it.
@@ -2928,9 +3021,11 @@ function defaultDeps5(client) {
2928
3021
  };
2929
3022
  }
2930
3023
  async function envList(opts, deps) {
3024
+ checkProfileOption(opts.profile);
2931
3025
  const d = deps ?? defaultDeps5(clientFor(opts));
2932
3026
  try {
2933
- const { variables } = await d.listVars(opts.app);
3027
+ const listed = (await d.listVars(opts.app)).variables;
3028
+ const variables = opts.profile ? listed.filter((v) => (v.profile ?? null) === null || v.profile === opts.profile) : listed;
2934
3029
  if (opts.json) {
2935
3030
  console.log(JSON.stringify(variables, null, 2));
2936
3031
  } else {
@@ -2946,11 +3041,12 @@ function formatEnvVarsHuman(variables) {
2946
3041
  if (variables.length === 0) {
2947
3042
  return "No build variables found.\n";
2948
3043
  }
2949
- const HEADERS3 = ["NAME", "VISIBILITY", "SCOPE", "HINT"];
3044
+ const HEADERS3 = ["NAME", "VISIBILITY", "SCOPE", "PROFILE", "HINT"];
2950
3045
  const rows = variables.map((v) => [
2951
3046
  v.name,
2952
3047
  v.is_file ? `${v.visibility} (file)` : v.visibility,
2953
3048
  v.app_public_id ?? "org-wide",
3049
+ v.profile ?? "all",
2954
3050
  v.value_hint ?? ""
2955
3051
  ]);
2956
3052
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
@@ -2961,6 +3057,7 @@ function formatEnvVarsHuman(variables) {
2961
3057
  return lines.join("\n") + "\n";
2962
3058
  }
2963
3059
  async function envSet(name, value, opts, deps) {
3060
+ checkProfileOption(opts.profile);
2964
3061
  const d = deps ?? defaultDeps5(clientFor(opts));
2965
3062
  let resolvedValue = value;
2966
3063
  if (resolvedValue === void 0 && !opts.file) {
@@ -2975,7 +3072,14 @@ async function envSet(name, value, opts, deps) {
2975
3072
  if (opts.file) {
2976
3073
  const content = await d.readFile(opts.file);
2977
3074
  const filename = basename2(opts.file);
2978
- const result2 = await d.setFileVar({ name, content, filename, visibility, app: opts.app });
3075
+ const result2 = await d.setFileVar({
3076
+ name,
3077
+ content,
3078
+ filename,
3079
+ visibility,
3080
+ app: opts.app,
3081
+ profile: opts.profile
3082
+ });
2979
3083
  printSetResult(opts, result2);
2980
3084
  process.exitCode = 0;
2981
3085
  return;
@@ -2983,7 +3087,13 @@ async function envSet(name, value, opts, deps) {
2983
3087
  if (resolvedValue === void 0) {
2984
3088
  resolvedValue = await d.prompt({ message: `Value for ${name}`, hidden: true });
2985
3089
  }
2986
- const result = await d.setVar({ name, value: resolvedValue, visibility, app: opts.app });
3090
+ const result = await d.setVar({
3091
+ name,
3092
+ value: resolvedValue,
3093
+ visibility,
3094
+ app: opts.app,
3095
+ profile: opts.profile
3096
+ });
2987
3097
  printSetResult(opts, result);
2988
3098
  process.exitCode = 0;
2989
3099
  } catch (err) {
@@ -3006,17 +3116,20 @@ function printSetResult(opts, result) {
3006
3116
  if (opts.json) {
3007
3117
  console.log(JSON.stringify(result));
3008
3118
  } else {
3009
- console.log(chalk20.green(`\u2713 set ${result.name} (${result.visibility})`));
3119
+ const forProfile = opts.profile ? `, profile ${opts.profile}` : "";
3120
+ console.log(chalk20.green(`\u2713 set ${result.name} (${result.visibility}${forProfile})`));
3010
3121
  }
3011
3122
  }
3012
3123
  async function envRm(name, opts, deps) {
3124
+ checkProfileOption(opts.profile);
3013
3125
  const d = deps ?? defaultDeps5(clientFor(opts));
3014
3126
  try {
3015
- await d.rmVar({ name, app: opts.app });
3127
+ await d.rmVar({ name, app: opts.app, profile: opts.profile });
3016
3128
  if (opts.json) {
3017
- console.log(JSON.stringify({ event: "deleted", name }));
3129
+ console.log(JSON.stringify({ event: "deleted", name, profile: opts.profile ?? null }));
3018
3130
  } else {
3019
- console.log(chalk20.green(`\u2713 removed ${name}`));
3131
+ const forProfile = opts.profile ? ` (profile ${opts.profile})` : "";
3132
+ console.log(chalk20.green(`\u2713 removed ${name}${forProfile}`));
3020
3133
  }
3021
3134
  process.exitCode = 0;
3022
3135
  } catch (err) {
@@ -3025,10 +3138,11 @@ async function envRm(name, opts, deps) {
3025
3138
  }
3026
3139
  }
3027
3140
  async function envPull(opts, deps) {
3141
+ checkProfileOption(opts.profile);
3028
3142
  const d = deps ?? defaultDeps5(clientFor(opts));
3029
3143
  const outPath = opts.out ?? ".env";
3030
3144
  try {
3031
- const { variables } = await d.pullVars(opts.app);
3145
+ const { variables } = await d.pullVars(opts.app, opts.profile);
3032
3146
  const ignored = await d.isIgnored(outPath);
3033
3147
  if (!ignored) {
3034
3148
  d.warn(
@@ -3160,7 +3274,24 @@ function defaultDeps6(client) {
3160
3274
  }
3161
3275
  await client.deletePlayKey(match.id);
3162
3276
  },
3163
- prompt: ({ message, hidden }) => hidden ? password2({ message }) : input({ message }),
3277
+ addAmazonKey: (body) => client.addAmazonKey(body),
3278
+ listAmazonKeys: () => client.listAmazonKeys(),
3279
+ deleteAmazonKey: async (name) => {
3280
+ const { keys } = await client.listAmazonKeys();
3281
+ const match = keys.find((k) => k.name === name);
3282
+ if (!match) {
3283
+ throw new Error(`No Amazon Appstore key named "${name}" found.`);
3284
+ }
3285
+ await client.deleteAmazonKey(match.id);
3286
+ },
3287
+ readStdin: async () => {
3288
+ const chunks = [];
3289
+ for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
3290
+ return Buffer.concat(chunks).toString("utf8");
3291
+ },
3292
+ // Prompts render on stderr: inquirer defaults to stdout, which would put
3293
+ // "? Client secret" and its echo ahead of a --json line.
3294
+ prompt: ({ message, hidden }) => hidden ? password2({ message }, { output: process.stderr }) : input({ message }, { output: process.stderr }),
3164
3295
  isTTY: Boolean(process.stdin.isTTY),
3165
3296
  readFile: (path) => readFileAsync2(path),
3166
3297
  fileExists: (path) => access(path).then(
@@ -3593,6 +3724,88 @@ async function playKeyRm(name, opts, deps) {
3593
3724
  process.exitCode = errorExitCode(err);
3594
3725
  }
3595
3726
  }
3727
+ async function amazonKeyAdd(opts, deps) {
3728
+ const d = deps ?? defaultDeps6(clientFor2(opts));
3729
+ if (!opts.clientId?.trim()) {
3730
+ throw new UsageError(
3731
+ "--client-id is required (the security profile client ID from the Amazon Developer Console \u2192 API Access)"
3732
+ );
3733
+ }
3734
+ const raw = d.isTTY ? await d.prompt({ message: "Client secret", hidden: true }) : await d.readStdin();
3735
+ const clientSecret = raw.trim();
3736
+ if (!clientSecret) {
3737
+ throw new UsageError(
3738
+ "a client secret is required: run interactively to be prompted, or pipe it on stdin (secrets are never accepted as flags)"
3739
+ );
3740
+ }
3741
+ try {
3742
+ const result = await d.addAmazonKey({
3743
+ ...opts.name !== void 0 ? { name: opts.name } : {},
3744
+ clientId: opts.clientId.trim(),
3745
+ clientSecret
3746
+ });
3747
+ if (opts.json) {
3748
+ console.log(JSON.stringify(result));
3749
+ } else {
3750
+ console.log(chalk21.green(`\u2713 added Amazon Appstore key ${result.name}`));
3751
+ console.log("");
3752
+ console.log(` Client ID: ${chalk21.bold(result.client_id)}`);
3753
+ console.log("");
3754
+ console.log(
3755
+ chalk21.yellow(
3756
+ " The client secret is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
3757
+ )
3758
+ );
3759
+ }
3760
+ process.exitCode = 0;
3761
+ } catch (err) {
3762
+ printError5(opts, err);
3763
+ process.exitCode = errorExitCode(err);
3764
+ }
3765
+ }
3766
+ async function amazonKeyList(opts, deps) {
3767
+ const d = deps ?? defaultDeps6(clientFor2(opts));
3768
+ try {
3769
+ const { keys } = await d.listAmazonKeys();
3770
+ if (opts.json) {
3771
+ console.log(JSON.stringify(keys, null, 2));
3772
+ } else {
3773
+ process.stdout.write(formatAmazonKeysHuman(keys));
3774
+ }
3775
+ process.exitCode = 0;
3776
+ } catch (err) {
3777
+ printError5(opts, err);
3778
+ process.exitCode = errorExitCode(err);
3779
+ }
3780
+ }
3781
+ function formatAmazonKeysHuman(keys) {
3782
+ if (keys.length === 0) {
3783
+ return "No Amazon Appstore keys found.\n";
3784
+ }
3785
+ const HEADERS3 = ["NAME", "CLIENT ID", "CREATED"];
3786
+ const rows = keys.map((k) => [k.name, k.client_id, formatUtcTimestamp(k.created_at)]);
3787
+ const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
3788
+ const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
3789
+ const join5 = (cells) => cells.join(" ").trimEnd();
3790
+ const lines = [chalk21.bold(join5(padCells(HEADERS3)))];
3791
+ for (const row of rows) lines.push(join5(padCells(row)));
3792
+ return lines.join("\n") + "\n";
3793
+ }
3794
+ async function amazonKeyRm(name, opts, deps) {
3795
+ const d = deps ?? defaultDeps6(clientFor2(opts));
3796
+ try {
3797
+ await d.deleteAmazonKey(name);
3798
+ if (opts.json) {
3799
+ console.log(JSON.stringify({ event: "deleted", name }));
3800
+ } else {
3801
+ console.log(chalk21.green(`\u2713 removed Amazon Appstore key ${name}`));
3802
+ }
3803
+ process.exitCode = 0;
3804
+ } catch (err) {
3805
+ printError5(opts, err);
3806
+ process.exitCode = errorExitCode(err);
3807
+ }
3808
+ }
3596
3809
  async function credentialsList(opts, deps) {
3597
3810
  const d = deps ?? defaultDeps6(clientFor2(opts));
3598
3811
  try {
@@ -4390,7 +4603,7 @@ program.command("build").description("Run your configured build command and uplo
4390
4603
  "--cloud only: the Node.js major to build with (e.g. 24), overriding vibeview.json build.node and the project .nvmrc for this run"
4391
4604
  ).option("--json", "Print the result as one JSON line (NDJSON events with --cloud)", false).action((opts) => buildCommand(opts));
4392
4605
  program.command("submit").description(
4393
- "Submit a succeeded production cloud build's artifact to TestFlight (ios/tvos) or a Google Play track (android/androidtv) \u2014 binary upload only; store listing and review stay in the store consoles"
4606
+ "Submit a succeeded production cloud build's artifact to TestFlight (ios/tvos), a Google Play track (android/androidtv), or Amazon Appstore review (android/androidtv with --store amazon). TestFlight and Google Play: binary upload only \u2014 store listing and review stay in the store consoles. --store amazon also writes the release notes and sends the version for Amazon review"
4394
4607
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--platform <platform>", "ios, tvos, android, or androidtv").option("--build <id>", "Cloud build id to submit (see `vibeview builds list`)").option(
4395
4608
  "--latest",
4396
4609
  "Submit the newest succeeded production build for --platform (pass exactly one of --build/--latest)",
@@ -4410,9 +4623,21 @@ program.command("submit").description(
4410
4623
  ).option(
4411
4624
  "--rollout <fraction>",
4412
4625
  "android/androidtv only: staged-rollout fraction, 0 < f < 1 (requires --release-status inProgress)"
4626
+ ).option(
4627
+ "--store <store>",
4628
+ "android/androidtv only: play (Google Play, default) or amazon (Amazon Appstore \u2014 .apk builds only; reaches Fire TV and Fire tablets)"
4629
+ ).option(
4630
+ "--amazon-app-id <id>",
4631
+ "--store amazon only: the Amazon App ID (Developer Console \u2192 your app \u2192 Upload Your App File \u2192 Additional information); saved on the app, so later submits can omit it"
4632
+ ).option(
4633
+ "--release-notes <text>",
4634
+ "--store amazon only, required: release notes written to every language of the Amazon listing"
4635
+ ).option(
4636
+ "--discard-open-edit",
4637
+ "--store amazon only: discard an edit already open in the Amazon Developer Console"
4413
4638
  ).option(
4414
4639
  "--check",
4415
- "Only check store readiness: does the store know this app with your stored key? Takes --platform plus --bundle-id (ios/tvos) or --package (android/androidtv), defaulting to the app linked in vibeview.json; no build needed. Exit 0 when ready",
4640
+ "Only check store readiness: does the store know this app with your stored key? Takes --platform plus --bundle-id (ios/tvos), --package (android/androidtv) or --amazon-app-id (with --store amazon), defaulting to the app linked in vibeview.json; no build needed. Exit 0 when ready",
4416
4641
  false
4417
4642
  ).option("--bundle-id <id>", "ios/tvos, with --check: the bundle identifier to look up").option("--no-wait", "Print the submission id and exit without waiting for completion").option("--json", "Emit line-delimited JSON events on stdout instead of human output", false).action((opts) => submitCommand(opts));
4418
4643
  var builds = program.command("builds").description("Manage cloud builds (see `vibeview build --cloud`)");
@@ -4432,17 +4657,29 @@ versions.command("set <number>").description(
4432
4657
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "App public id (see `vibeview list-apps`)").option("--platform <platform>", "ios, android, tvos, or androidtv").option("--json", "Print the updated version object as one JSON line", false).action((number, opts) => versionsSet(number, opts));
4433
4658
  program.command("whoami").description("Show which account and role this CLI is authenticated as").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the identity as one JSON line", false).action((opts) => whoAmI(opts));
4434
4659
  var env = program.command("env").description("Manage cloud build environment variables and secrets");
4435
- env.command("list").description("List build variables and secrets (never prints a value)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide variables only)").option("--json", "Print the raw variable list as a JSON array", false).action((opts) => envList(opts));
4660
+ env.command("list").description("List build variables and secrets (never prints a value)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide variables only)").option(
4661
+ "--profile <name>",
4662
+ "Only variables a build of this vibeview.json build profile can use (its own + every-profile ones)"
4663
+ ).option("--json", "Print the raw variable list as a JSON array", false).action((opts) => envList(opts));
4436
4664
  env.command("set <name> [value]").description(
4437
4665
  "Set a build variable or secret. Defaults to --secret (never retrievable via the API) unless --plain or --sensitive is passed explicitly."
4438
- ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide)").option("--secret", "Store as secret (default): never returned by the API, including `env pull`").option(
4666
+ ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide)").option(
4667
+ "--profile <name>",
4668
+ "Limit to one vibeview.json build profile (default: every profile); its value beats the every-profile one"
4669
+ ).option("--secret", "Store as secret (default): never returned by the API, including `env pull`").option(
4439
4670
  "--sensitive",
4440
4671
  "Store as sensitive: value_hint only in `env list`, full value via `env pull`"
4441
4672
  ).option("--plain", "Store as plain: value returned by `env pull`, unmasked").option("--file <path>", "Upload a file as the value (e.g. a keystore) instead of a text value").option("--json", "Print the resulting variable as one JSON line", false).action((name, value, opts) => envSet(name, value, opts));
4442
- env.command("rm <name>").description("Remove a build variable or secret").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) => envRm(name, opts));
4673
+ env.command("rm <name>").description("Remove a build variable or secret").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide)").option(
4674
+ "--profile <name>",
4675
+ "Remove the one limited to this build profile (default: the every-profile one)"
4676
+ ).option("--json", "Print the result as one JSON line", false).action((name, opts) => envRm(name, opts));
4443
4677
  env.command("pull").description(
4444
4678
  "Write plain/sensitive variable values to a .env file (secrets and files are never included)"
4445
- ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide variables only)").option("--out <path>", "Output file path (default: .env)").option("--json", "Print the result as one JSON line", false).action((opts) => envPull(opts));
4679
+ ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide variables only)").option(
4680
+ "--profile <name>",
4681
+ "Write the values a build of this vibeview.json build profile gets (default: every-profile values only)"
4682
+ ).option("--out <path>", "Output file path (default: .env)").option("--json", "Print the result as one JSON line", false).action((opts) => envPull(opts));
4446
4683
  var credentials = program.command("credentials").description("Manage app-signing credentials for production cloud builds");
4447
4684
  credentials.command("add").description(
4448
4685
  "Add a signing credential. Prompts for passwords with hidden input \u2014 they are never accepted as flags (a flag lands in shell history and in `ps`)."
@@ -4492,6 +4729,14 @@ playKey.command("add").description(
4492
4729
  ).option("--name <name>", "Key name (default: server-chosen)").option("--json", "Print the raw response as one JSON line", false).action((opts) => playKeyAdd(opts));
4493
4730
  playKey.command("list").description("List stored Google Play keys (metadata only \u2014 never the JSON key)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the raw key list as a JSON array", false).action((opts) => playKeyList(opts));
4494
4731
  playKey.command("rm <name>").description("Remove a stored Google Play key").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the result as one JSON line", false).action((name, opts) => playKeyRm(name, opts));
4732
+ var amazonKey = credentials.command("amazon-key").description(
4733
+ "Manage Amazon Appstore keys (a security profile attached to the App Submission API) \u2014 they power `vibeview submit --store amazon`"
4734
+ );
4735
+ amazonKey.command("add").description(
4736
+ "Store an Amazon security profile client ID and secret. The secret is prompted for (or read from stdin when piped), stored encrypted and never shown again; adding the same name again rotates the key."
4737
+ ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--client-id <id>", "The security profile client ID").option("--name <name>", "Key name (default: amazon)").option("--json", "Print the raw response as one JSON line", false).action((opts) => amazonKeyAdd(opts));
4738
+ amazonKey.command("list").description("List stored Amazon Appstore keys (metadata only \u2014 never the secret)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the raw key list as a JSON array", false).action((opts) => amazonKeyList(opts));
4739
+ amazonKey.command("rm <name>").description("Remove a stored Amazon Appstore key").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the result as one JSON line", false).action((name, opts) => amazonKeyRm(name, opts));
4495
4740
  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));
4496
4741
  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));
4497
4742
  var webhooks = program.command("webhooks").description("Manage build-completion webhook endpoints");
@@ -4520,7 +4765,7 @@ program.command("dev-reload").description(
4520
4765
  program.command("mcp").description(
4521
4766
  "Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
4522
4767
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
4523
- const { runMcpServer } = await import("./server-5VWZMZRE.js");
4768
+ const { runMcpServer } = await import("./server-BGOXC6GL.js");
4524
4769
  await runMcpServer({ ...opts, version: pkg.version });
4525
4770
  });
4526
4771
  program.command("agent-setup").description(
@@ -35,12 +35,12 @@ import {
35
35
  renderEnvelope,
36
36
  resolveMetro,
37
37
  startDevSession
38
- } from "./chunk-S4VPSFPP.js";
38
+ } from "./chunk-77OGQKZQ.js";
39
39
  import {
40
40
  ApiClient,
41
41
  NotFoundError,
42
42
  resolveAuth
43
- } from "./chunk-LRPZFDKL.js";
43
+ } from "./chunk-R4GTQPZH.js";
44
44
 
45
45
  // src/mcp/server.ts
46
46
  import { writeFileSync } from "fs";
@@ -999,7 +999,7 @@ async function handleUploadApp(args, deps) {
999
999
  }
1000
1000
  const name = typeof args.name === "string" && args.name ? args.name : void 0;
1001
1001
  const upload = deps.uploadApp ?? (async (f, n) => {
1002
- const { uploadAppBuild } = await import("./upload-app-OV3QKL7O.js");
1002
+ const { uploadAppBuild } = await import("./upload-app-CYGKCGJE.js");
1003
1003
  const { result: result2 } = await uploadAppBuild(f, { ...deps.auth, name: n });
1004
1004
  return result2;
1005
1005
  });
@@ -3,7 +3,7 @@ import {
3
3
  describeBuild,
4
4
  uploadApp,
5
5
  uploadAppBuild
6
- } from "./chunk-LRPZFDKL.js";
6
+ } from "./chunk-R4GTQPZH.js";
7
7
  export {
8
8
  describeBuild,
9
9
  uploadApp,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeview",
3
- "version": "0.52.2",
3
+ "version": "0.54.0",
4
4
  "description": "VibeView CLI -- run your app on cloud iOS simulators, Android emulators, Apple TV, Android TV and Roku devices: a React Native dev loop, device control for you or your coding agent (CLI or MCP), and tests from CI",
5
5
  "mcpName": "io.github.vibeview/vibeview",
6
6
  "type": "module",
@@ -311,6 +311,7 @@ human-readable text.
311
311
  | `relaunch-app` | — | Close the app and start it again (cold start, data kept) — for "does it persist after a restart" checks. The app log keeps streaming. Not on embed sessions. |
312
312
  | `set-posture` | `<closed\|partial\|open>` or `--angle <0-180>` | Fold or unfold a foldable device (form factor `foldable`: the iPhone Duo, Android foldables) to a preset or an exact hinge angle — exactly one. Reports posture, angle and lit screen. Errors on a device without a hinge. |
313
313
  | `rotate` | `[--degrees <90\|180\|270>]` | Rotate the device and report the new orientation. Phones/tablets and Android foldables toggle portrait/landscape (90 only); the iPhone Duo turns a quarter clockwise by default. Not on TV. |
314
+ | `shake` | — | Shake the device once, like the dashboard's Shake button, to open an app's shake-triggered menus (a bug-report screen, the React Native developer menu in a debug build). Phones, tablets and foldables; not on TV or embed sessions. |
314
315
  | `set-language` | `<tag\|default>` | Restart the app in another language (`de`, `pt-BR`, …) or the device's (`default`). Only the app changes. iPhone, iPad, Apple TV, Android 13+; not Roku or embed sessions. |
315
316
  | `set-status-bar` | `<clean\|default>` | Show the clean screenshot status bar (9:41, full battery and signal) or the device's own. iOS phones and tablets only. |
316
317
  | `set-location` | `--lat <n> --lon <n>` | Set the device's simulated GPS position (decimal degrees; west and south are negative) for location-based flows. Phones, tablets and foldables; not on TV. |