vibeview 0.53.0 → 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.
@@ -13,7 +13,7 @@ import {
13
13
  resolveArtifact,
14
14
  resolveAuth,
15
15
  uploadAppBuild
16
- } from "./chunk-YCFQKMG7.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).
@@ -676,8 +676,26 @@ var ApiClient = class {
676
676
  async deletePlayKey(id) {
677
677
  await this.requestRaw("DELETE", `/signing-credentials/play-keys/${id}`);
678
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
+ }
679
696
  /** POST /api/v1/submissions — submit a succeeded production build's
680
- * 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").
681
699
  * `platform` is always sent: the backend derives the real platform from
682
700
  * the build row and 400s on a mismatch, so a pasted wrong --build id
683
701
  * fails loudly instead of silently submitting to the other store family.
@@ -694,16 +712,22 @@ var ApiClient = class {
694
712
  ...body.ascKey !== void 0 ? { asc_key: body.ascKey } : {},
695
713
  ...body.releaseStatus !== void 0 ? { release_status: body.releaseStatus } : {},
696
714
  ...body.rollout !== void 0 ? { rollout: body.rollout } : {},
697
- ...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 } : {}
698
720
  });
699
721
  }
700
722
  /** GET /api/v1/submissions/preflight — is the store ready to receive
701
- * this app? Answers by identifier (bundle id / package name), so it can
702
- * 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
703
726
  * answer carried in `status`/`message`, never a thrown error. */
704
727
  async checkStorePreflight(q) {
705
728
  const params = new URLSearchParams({ platform: q.platform, identifier: q.identifier });
706
729
  if (q.ascKey !== void 0) params.set("asc_key", q.ascKey);
730
+ if (q.store !== void 0) params.set("store", q.store);
707
731
  return this.requestRaw("GET", `/submissions/preflight?${params.toString()}`);
708
732
  }
709
733
  /** GET /api/v1/submissions?build=<id> — that build's submissions, newest
package/dist/cli.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  runAgentVerb,
37
37
  runStepsNotChecked,
38
38
  stepsNotCheckedLabel
39
- } from "./chunk-O6MXCYWR.js";
39
+ } from "./chunk-77OGQKZQ.js";
40
40
  import {
41
41
  ApiClient,
42
42
  ApiStatusError,
@@ -59,7 +59,7 @@ import {
59
59
  runCommandParseExitCode,
60
60
  saveConfig,
61
61
  uploadApp
62
- } from "./chunk-YCFQKMG7.js";
62
+ } from "./chunk-R4GTQPZH.js";
63
63
 
64
64
  // src/cli.ts
65
65
  import { createRequire } from "module";
@@ -2377,6 +2377,14 @@ var SUBMIT_PLATFORMS = ["ios", "tvos", "android", "androidtv"];
2377
2377
  function isSubmitPlatform(p) {
2378
2378
  return SUBMIT_PLATFORMS.includes(p);
2379
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.';
2380
2388
  var PLAY_TRACKS = ["internal", "alpha", "beta", "production"];
2381
2389
  var RELEASE_STATUSES = ["completed", "draft", "inProgress"];
2382
2390
  function submitConfigFor(cwd, platform) {
@@ -2432,9 +2440,25 @@ async function submitCommand(opts, deps) {
2432
2440
  }
2433
2441
  const platform = opts.platform;
2434
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;
2435
2459
  if (opts.check && (opts.build || opts.latest)) {
2436
2460
  throw new UsageError(
2437
- "--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)")
2438
2462
  );
2439
2463
  }
2440
2464
  if (!opts.check && opts.bundleId !== void 0) {
@@ -2450,38 +2474,63 @@ async function submitCommand(opts, deps) {
2450
2474
  "pass --build <id> (see `vibeview builds list`) or --latest (the newest succeeded production build for this platform)"
2451
2475
  );
2452
2476
  }
2453
- if (!androidFamily && opts.track !== void 0) {
2454
- 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}`);
2455
2479
  }
2456
- if (!androidFamily && opts.package !== void 0) {
2457
- throw new UsageError(`--package only applies to android/androidtv (Play) \u2014 not ${platform}`);
2480
+ if (!play && opts.package !== void 0) {
2481
+ throw new UsageError(`--package only applies to android/androidtv (Play) \u2014 not ${target}`);
2458
2482
  }
2459
- if (androidFamily && opts.ascKey !== void 0) {
2460
- throw new UsageError(`--asc-key only applies to ios/tvos (TestFlight) \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}`);
2461
2485
  }
2462
- if (androidFamily && opts.bundleId !== void 0) {
2486
+ if (!apple && opts.bundleId !== void 0) {
2463
2487
  throw new UsageError(
2464
- `--bundle-id only applies to ios/tvos \u2014 for ${platform} pass --package <name>`
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>`
2465
2489
  );
2466
2490
  }
2467
- if (!androidFamily && opts.releaseStatus !== void 0) {
2491
+ if (!play && opts.releaseStatus !== void 0) {
2468
2492
  throw new UsageError(
2469
- `--release-status only applies to android/androidtv (Play) \u2014 not ${platform}`
2493
+ `--release-status only applies to android/androidtv (Play) \u2014 not ${target}`
2470
2494
  );
2471
2495
  }
2472
- if (!androidFamily && opts.rollout !== void 0) {
2473
- throw new UsageError(`--rollout only applies to android/androidtv (Play) \u2014 not ${platform}`);
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
+ }
2474
2511
  }
2475
- const cfgDefaults = submitConfigFor(process.cwd(), platform);
2476
- const track = opts.track ?? (androidFamily ? cfgDefaults.track : void 0);
2477
- const releaseStatus = opts.releaseStatus ?? (androidFamily ? cfgDefaults.releaseStatus : void 0);
2512
+ const amazonAppId = opts.amazonAppId?.trim() || void 0;
2513
+ if (amazonAppId !== void 0 && !AMAZON_APP_ID_RE.test(amazonAppId)) {
2514
+ throw new UsageError(
2515
+ `--amazon-app-id must be an Amazon App ID (amzn1.devportal.mobileapp. followed by 32 hex characters) \u2014 got "${amazonAppId}"`
2516
+ );
2517
+ }
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
+ );
2523
+ }
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);
2478
2527
  const statusUnchanged = opts.releaseStatus === void 0 || opts.releaseStatus === cfgDefaults.releaseStatus;
2479
- 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(
2480
2529
  cfgDefaults.rollout,
2481
2530
  `vibeview.json platforms.${platform}.submit.rollout`
2482
2531
  ) : void 0;
2483
- const changesNotSentForReview = androidFamily ? cfgDefaults.changesNotSentForReview : void 0;
2484
- 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);
2485
2534
  if (track !== void 0 && !PLAY_TRACKS.includes(track)) {
2486
2535
  const source = opts.track !== void 0 ? "--track" : `vibeview.json platforms.${platform}.submit.track`;
2487
2536
  throw new UsageError(`${source} must be one of ${PLAY_TRACKS.join(", ")} \u2014 got "${track}"`);
@@ -2506,11 +2555,11 @@ async function submitCommand(opts, deps) {
2506
2555
  const client = new ApiClient(baseUrl, token);
2507
2556
  const d = deps ?? defaultDeps2(client);
2508
2557
  if (opts.check) {
2509
- await checkStore(platform, androidFamily, ascKey2, opts, d);
2558
+ await checkStore(platform, { androidFamily, amazon, amazonAppId }, ascKey2, opts, d);
2510
2559
  return;
2511
2560
  }
2512
2561
  try {
2513
- const buildId = opts.build ?? await resolveLatestBuild(platform, d);
2562
+ const buildId = opts.build ?? await resolveLatestBuild(platform, amazon, d);
2514
2563
  const sub = await d.createSubmission({
2515
2564
  build: buildId,
2516
2565
  platform,
@@ -2519,7 +2568,11 @@ async function submitCommand(opts, deps) {
2519
2568
  ...ascKey2 !== void 0 ? { ascKey: ascKey2 } : {},
2520
2569
  ...releaseStatus !== void 0 ? { releaseStatus } : {},
2521
2570
  ...rollout !== void 0 ? { rollout } : {},
2522
- ...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 } : {}
2523
2576
  });
2524
2577
  emit(opts.json, {
2525
2578
  event: "submission_created",
@@ -2547,13 +2600,14 @@ async function submitCommand(opts, deps) {
2547
2600
  process.exitCode = errorExitCode(err);
2548
2601
  }
2549
2602
  }
2550
- async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
2551
- const flagged = androidFamily ? opts.package : opts.bundleId;
2552
- 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)`;
2553
2607
  let identifier = flagged;
2554
2608
  if (!identifier) {
2555
2609
  try {
2556
- identifier = await linkedIdentifier(platform, opts, deps);
2610
+ identifier = await linkedIdentifier(platform, amazon, opts, deps);
2557
2611
  } catch (err) {
2558
2612
  printError2(opts, err);
2559
2613
  process.exitCode = errorExitCode(err);
@@ -2565,7 +2619,8 @@ async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
2565
2619
  const result = await deps.checkStorePreflight({
2566
2620
  platform,
2567
2621
  identifier,
2568
- ...ascKey2 !== void 0 ? { ascKey: ascKey2 } : {}
2622
+ ...ascKey2 !== void 0 ? { ascKey: ascKey2 } : {},
2623
+ ...amazon ? { store: "amazon" } : {}
2569
2624
  });
2570
2625
  emit(opts.json, { event: "preflight", ...result });
2571
2626
  if (!opts.json) {
@@ -2574,7 +2629,17 @@ async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
2574
2629
  if (result.ready) {
2575
2630
  console.error(
2576
2631
  chalk17.dim(
2577
- ` 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`
2578
2643
  )
2579
2644
  );
2580
2645
  }
@@ -2585,7 +2650,7 @@ async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
2585
2650
  process.exitCode = errorExitCode(err);
2586
2651
  }
2587
2652
  }
2588
- async function linkedIdentifier(platform, opts, deps) {
2653
+ async function linkedIdentifier(platform, amazon, opts, deps) {
2589
2654
  if (!deps.getApp) return void 0;
2590
2655
  let appId;
2591
2656
  try {
@@ -2600,18 +2665,19 @@ async function linkedIdentifier(platform, opts, deps) {
2600
2665
  } catch (err) {
2601
2666
  if (err instanceof NotFoundError) {
2602
2667
  throw new Error(
2603
- `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>")
2604
2669
  );
2605
2670
  }
2606
2671
  throw err;
2607
2672
  }
2608
- 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;
2609
2675
  if (!opts.json) {
2610
- 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)`));
2611
2677
  }
2612
- return app.package_name;
2678
+ return identifier;
2613
2679
  }
2614
- async function resolveLatestBuild(platform, deps) {
2680
+ async function resolveLatestBuild(platform, apkOnly, deps) {
2615
2681
  const { builds: builds2 } = await deps.listCloudBuilds({
2616
2682
  platform,
2617
2683
  status: "succeeded",
@@ -2619,8 +2685,13 @@ async function resolveLatestBuild(platform, deps) {
2619
2685
  limit: 200
2620
2686
  });
2621
2687
  const match = builds2.find(
2622
- (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"))
2623
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
+ }
2624
2695
  if (!match) {
2625
2696
  throw new Error(
2626
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\`).`
@@ -3203,7 +3274,24 @@ function defaultDeps6(client) {
3203
3274
  }
3204
3275
  await client.deletePlayKey(match.id);
3205
3276
  },
3206
- 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 }),
3207
3295
  isTTY: Boolean(process.stdin.isTTY),
3208
3296
  readFile: (path) => readFileAsync2(path),
3209
3297
  fileExists: (path) => access(path).then(
@@ -3636,6 +3724,88 @@ async function playKeyRm(name, opts, deps) {
3636
3724
  process.exitCode = errorExitCode(err);
3637
3725
  }
3638
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
+ }
3639
3809
  async function credentialsList(opts, deps) {
3640
3810
  const d = deps ?? defaultDeps6(clientFor2(opts));
3641
3811
  try {
@@ -4433,7 +4603,7 @@ program.command("build").description("Run your configured build command and uplo
4433
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"
4434
4604
  ).option("--json", "Print the result as one JSON line (NDJSON events with --cloud)", false).action((opts) => buildCommand(opts));
4435
4605
  program.command("submit").description(
4436
- "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"
4437
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(
4438
4608
  "--latest",
4439
4609
  "Submit the newest succeeded production build for --platform (pass exactly one of --build/--latest)",
@@ -4453,9 +4623,21 @@ program.command("submit").description(
4453
4623
  ).option(
4454
4624
  "--rollout <fraction>",
4455
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"
4456
4638
  ).option(
4457
4639
  "--check",
4458
- "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",
4459
4641
  false
4460
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));
4461
4643
  var builds = program.command("builds").description("Manage cloud builds (see `vibeview build --cloud`)");
@@ -4547,6 +4729,14 @@ playKey.command("add").description(
4547
4729
  ).option("--name <name>", "Key name (default: server-chosen)").option("--json", "Print the raw response as one JSON line", false).action((opts) => playKeyAdd(opts));
4548
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));
4549
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));
4550
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));
4551
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));
4552
4742
  var webhooks = program.command("webhooks").description("Manage build-completion webhook endpoints");
@@ -4575,7 +4765,7 @@ program.command("dev-reload").description(
4575
4765
  program.command("mcp").description(
4576
4766
  "Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
4577
4767
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
4578
- const { runMcpServer } = await import("./server-JLT3PQTH.js");
4768
+ const { runMcpServer } = await import("./server-BGOXC6GL.js");
4579
4769
  await runMcpServer({ ...opts, version: pkg.version });
4580
4770
  });
4581
4771
  program.command("agent-setup").description(
@@ -35,12 +35,12 @@ import {
35
35
  renderEnvelope,
36
36
  resolveMetro,
37
37
  startDevSession
38
- } from "./chunk-O6MXCYWR.js";
38
+ } from "./chunk-77OGQKZQ.js";
39
39
  import {
40
40
  ApiClient,
41
41
  NotFoundError,
42
42
  resolveAuth
43
- } from "./chunk-YCFQKMG7.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-KQCXAZST.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-YCFQKMG7.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.53.0",
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. |