preflightlaunch 0.2.0 → 0.2.2

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/index.js CHANGED
@@ -2,14 +2,15 @@
2
2
  import { createRequire } from "node:module";
3
3
  const require = createRequire(import.meta.url);
4
4
  import {
5
+ DEFAULT_API_URL,
5
6
  apiRequest,
6
7
  brand,
7
- brandSplash,
8
+ brandDim,
9
+ clearAuth,
10
+ confirm,
8
11
  createSpinner,
9
12
  critical,
10
13
  error,
11
- findProjectInDir,
12
- findXcodeProjects,
13
14
  getConfig,
14
15
  handleUnknownCommand,
15
16
  hasRunBefore,
@@ -22,21 +23,23 @@ import {
22
23
  logout,
23
24
  markAsRun,
24
25
  ok,
25
- outro,
26
+ renderHeader,
26
27
  renderReport,
27
28
  renderReportJson,
29
+ resumeSubmitCommand,
28
30
  scanProject,
29
31
  select,
32
+ setAscConnected,
30
33
  setLastScannedPath,
31
- showTagline,
32
34
  source_default,
33
35
  spinner,
34
36
  submitCommand,
35
37
  subtext,
36
38
  success,
39
+ text,
37
40
  tip,
38
41
  warning
39
- } from "./chunk-X5CBMYPG.js";
42
+ } from "./chunk-26P7VL2P.js";
40
43
  import {
41
44
  __commonJS,
42
45
  __require,
@@ -3095,9 +3098,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3095
3098
  helpWidth: context.helpWidth,
3096
3099
  outputHasColors: context.hasColors
3097
3100
  });
3098
- const text = helper.formatHelp(this, helper);
3099
- if (context.hasColors) return text;
3100
- return this._outputConfiguration.stripColor(text);
3101
+ const text2 = helper.formatHelp(this, helper);
3102
+ if (context.hasColors) return text2;
3103
+ return this._outputConfiguration.stripColor(text2);
3101
3104
  }
3102
3105
  /**
3103
3106
  * @typedef HelpContext
@@ -3251,7 +3254,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3251
3254
  * @param {(string | Function)} text - string to add, or a function returning a string
3252
3255
  * @return {Command} `this` command for chaining
3253
3256
  */
3254
- addHelpText(position, text) {
3257
+ addHelpText(position, text2) {
3255
3258
  const allowedValues = ["beforeAll", "before", "after", "afterAll"];
3256
3259
  if (!allowedValues.includes(position)) {
3257
3260
  throw new Error(`Unexpected value for position to addHelpText.
@@ -3260,10 +3263,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
3260
3263
  const helpEvent = `${position}Help`;
3261
3264
  this.on(helpEvent, (context) => {
3262
3265
  let helpStr;
3263
- if (typeof text === "function") {
3264
- helpStr = text({ error: context.error, command: context.command });
3266
+ if (typeof text2 === "function") {
3267
+ helpStr = text2({ error: context.error, command: context.command });
3265
3268
  } else {
3266
- helpStr = text;
3269
+ helpStr = text2;
3267
3270
  }
3268
3271
  if (helpStr) {
3269
3272
  context.write(`${helpStr}
@@ -3774,11 +3777,11 @@ var require_utils = __commonJS({
3774
3777
  }
3775
3778
  return output;
3776
3779
  }
3777
- function hyperlink(url, text) {
3780
+ function hyperlink(url, text2) {
3778
3781
  const OSC = "\x1B]";
3779
3782
  const BEL = "\x07";
3780
3783
  const SEP = ";";
3781
- return [OSC, "8", SEP, SEP, url || text, BEL, text, OSC, "8", SEP, SEP, BEL].join("");
3784
+ return [OSC, "8", SEP, SEP, url || text2, BEL, text2, OSC, "8", SEP, SEP, BEL].join("");
3782
3785
  }
3783
3786
  module.exports = {
3784
3787
  strlen,
@@ -3979,10 +3982,10 @@ var require_trap = __commonJS({
3979
3982
  "../../node_modules/@colors/colors/lib/custom/trap.js"(exports, module) {
3980
3983
  "use strict";
3981
3984
  init_esm_shims();
3982
- module["exports"] = function runTheTrap(text, options) {
3985
+ module["exports"] = function runTheTrap(text2, options) {
3983
3986
  var result = "";
3984
- text = text || "Run the trap, drop the bass";
3985
- text = text.split("");
3987
+ text2 = text2 || "Run the trap, drop the bass";
3988
+ text2 = text2.split("");
3986
3989
  var trap = {
3987
3990
  a: ["@", "\u0104", "\u023A", "\u0245", "\u0394", "\u039B", "\u0414"],
3988
3991
  b: ["\xDF", "\u0181", "\u0243", "\u026E", "\u03B2", "\u0E3F"],
@@ -4030,7 +4033,7 @@ var require_trap = __commonJS({
4030
4033
  y: ["\xA5", "\u04B0", "\u04CB"],
4031
4034
  z: ["\u01B5", "\u0240"]
4032
4035
  };
4033
- text.forEach(function(c) {
4036
+ text2.forEach(function(c) {
4034
4037
  c = c.toLowerCase();
4035
4038
  var chars = trap[c] || [" "];
4036
4039
  var rand = Math.floor(Math.random() * chars.length);
@@ -4050,8 +4053,8 @@ var require_zalgo = __commonJS({
4050
4053
  "../../node_modules/@colors/colors/lib/custom/zalgo.js"(exports, module) {
4051
4054
  "use strict";
4052
4055
  init_esm_shims();
4053
- module["exports"] = function zalgo(text, options) {
4054
- text = text || " he is here ";
4056
+ module["exports"] = function zalgo(text2, options) {
4057
+ text2 = text2 || " he is here ";
4055
4058
  var soul = {
4056
4059
  "up": [
4057
4060
  "\u030D",
@@ -4184,7 +4187,7 @@ var require_zalgo = __commonJS({
4184
4187
  });
4185
4188
  return bool;
4186
4189
  }
4187
- function heComes(text2, options2) {
4190
+ function heComes(text3, options2) {
4188
4191
  var result = "";
4189
4192
  var counts;
4190
4193
  var l;
@@ -4193,12 +4196,12 @@ var require_zalgo = __commonJS({
4193
4196
  options2["mid"] = typeof options2["mid"] !== "undefined" ? options2["mid"] : true;
4194
4197
  options2["down"] = typeof options2["down"] !== "undefined" ? options2["down"] : true;
4195
4198
  options2["size"] = typeof options2["size"] !== "undefined" ? options2["size"] : "maxi";
4196
- text2 = text2.split("");
4197
- for (l in text2) {
4199
+ text3 = text3.split("");
4200
+ for (l in text3) {
4198
4201
  if (isChar(l)) {
4199
4202
  continue;
4200
4203
  }
4201
- result = result + text2[l];
4204
+ result = result + text3[l];
4202
4205
  counts = { "up": 0, "down": 0, "mid": 0 };
4203
4206
  switch (options2.size) {
4204
4207
  case "mini":
@@ -4229,7 +4232,7 @@ var require_zalgo = __commonJS({
4229
4232
  }
4230
4233
  return result;
4231
4234
  }
4232
- return heComes(text, options);
4235
+ return heComes(text2, options);
4233
4236
  };
4234
4237
  }
4235
4238
  });
@@ -5227,7 +5230,7 @@ async function loginCommand() {
5227
5230
  const spinner2 = createSpinner("Opening browser for login...");
5228
5231
  spinner2.start();
5229
5232
  try {
5230
- const result = await loginWithBrowser();
5233
+ const result = await loginWithBrowser("login");
5231
5234
  if (result) {
5232
5235
  spinner2.stop();
5233
5236
  success(`Logged in as ${source_default.bold(result.email)}`);
@@ -5267,7 +5270,7 @@ async function whoamiCommand() {
5267
5270
  }
5268
5271
  console.log();
5269
5272
  console.log(source_default.bold(" Account"));
5270
- console.log(` Email: ${source_default.cyan(data.user.email)}`);
5273
+ console.log(` Email: ${brandDim(data.user.email)}`);
5271
5274
  console.log(` ID: ${source_default.dim(data.user.id)}`);
5272
5275
  if (data.user.credits != null) {
5273
5276
  console.log(` Credits: ${source_default.green(data.user.credits)}`);
@@ -5318,10 +5321,11 @@ async function scanCommand(path) {
5318
5321
  const resolvedPath = await interactiveProjectSelect();
5319
5322
  if (!resolvedPath) return;
5320
5323
  path = resolvedPath;
5324
+ } else {
5325
+ intro("Scanning project");
5321
5326
  }
5322
5327
  const dir = resolve(path);
5323
5328
  setLastScannedPath(dir);
5324
- intro("Scanning project");
5325
5329
  const s = spinner();
5326
5330
  s.start("Looking for App Store files...");
5327
5331
  const detected = scanProject(dir);
@@ -5380,12 +5384,12 @@ async function scanCommand(path) {
5380
5384
  const next = await select({
5381
5385
  message: "What next?",
5382
5386
  options: [
5383
- { value: "submit", label: "Submit for full analysis", hint: "1 credit" },
5387
+ { value: "submit", label: "Submit for full analysis", hint: "100 credits" },
5384
5388
  { value: "done", label: "Done for now" }
5385
5389
  ]
5386
5390
  });
5387
5391
  if (next === "submit") {
5388
- const { submitCommand: submitCommand2 } = await import("./submit-WANAECAV.js");
5392
+ const { submitCommand: submitCommand2 } = await import("./submit-HEQTSQL5.js");
5389
5393
  await submitCommand2(dir, {});
5390
5394
  } else {
5391
5395
  tip(`Run ${brand("preflight submit")} anytime to get AI-powered fix instructions.`);
@@ -5399,6 +5403,11 @@ async function statusCommand(id, options) {
5399
5403
  error("Not logged in. Run `preflight login` first.");
5400
5404
  process.exit(1);
5401
5405
  }
5406
+ if (!id) {
5407
+ error("Please provide a submission ID. Run `preflight history` to find one.");
5408
+ process.exitCode = 1;
5409
+ return;
5410
+ }
5402
5411
  if (options.watch) {
5403
5412
  return watchStatus(id);
5404
5413
  }
@@ -5480,8 +5489,13 @@ async function reportCommand(id, options) {
5480
5489
  error("Not logged in. Run `preflight login` first.");
5481
5490
  process.exit(1);
5482
5491
  }
5492
+ if (!id) {
5493
+ error("Please provide a submission or report ID. Run `preflight history` to find one.");
5494
+ process.exitCode = 1;
5495
+ return;
5496
+ }
5483
5497
  if (options.open) {
5484
- await open_default(`https://preflight.dev/report/${id}`);
5498
+ await open_default(`${DEFAULT_API_URL}/report/${id}`);
5485
5499
  console.log(source_default.dim(" Opened report in browser"));
5486
5500
  return;
5487
5501
  }
@@ -5499,7 +5513,7 @@ async function reportCommand(id, options) {
5499
5513
  renderReportJson(data.report, data.items);
5500
5514
  } else {
5501
5515
  renderReport(data.report, data.items);
5502
- console.log(source_default.dim(` Full report: https://preflight.dev/report/${id}`));
5516
+ console.log(source_default.dim(` Full report: ${DEFAULT_API_URL}/report/${id}`));
5503
5517
  console.log();
5504
5518
  }
5505
5519
  } catch (err) {
@@ -5564,64 +5578,190 @@ async function historyCommand(options) {
5564
5578
  process.exit(1);
5565
5579
  }
5566
5580
  }
5581
+ async function interactiveHistory() {
5582
+ const s = spinner();
5583
+ s.start("Loading your reviews...");
5584
+ try {
5585
+ const res = await apiRequest("/api/submissions");
5586
+ const data = await res.json();
5587
+ s.stop("Reviews loaded");
5588
+ if (!res.ok) {
5589
+ log.error(data.message || "Failed to load reviews");
5590
+ return;
5591
+ }
5592
+ if (!data.data || data.data.length === 0) {
5593
+ log.info("No reviews yet. Start your first review from the main menu.");
5594
+ console.log();
5595
+ await confirm("Back to menu?", true);
5596
+ return;
5597
+ }
5598
+ const submissions = data.data;
5599
+ while (true) {
5600
+ const options = submissions.map((sub2) => {
5601
+ const date = new Date(sub2.created_at).toLocaleDateString("en-US", {
5602
+ month: "short",
5603
+ day: "numeric"
5604
+ });
5605
+ const statusLabel = sub2.status === "complete" ? "Ready" : sub2.status === "failed" ? "Failed" : sub2.status === "analyzing" ? "Analyzing..." : sub2.status === "draft" ? "Draft" : sub2.status;
5606
+ const hint = sub2.status === "complete" ? "View report" : sub2.status === "draft" ? "Resume draft" : "";
5607
+ return {
5608
+ value: sub2.id,
5609
+ label: `${sub2.app_name || "Unknown"} - ${statusLabel} (${date})`,
5610
+ hint
5611
+ };
5612
+ });
5613
+ options.push({
5614
+ value: "__back__",
5615
+ label: "Back to menu",
5616
+ hint: ""
5617
+ });
5618
+ const selected = await select({
5619
+ message: "Your Reviews",
5620
+ options
5621
+ });
5622
+ if (selected === null || selected === "__back__") return;
5623
+ const sub = submissions.find((s2) => s2.id === selected);
5624
+ if (!sub) return;
5625
+ if (sub.status === "draft") {
5626
+ const draftAction = await select({
5627
+ message: `Draft: ${sub.app_name || "Unknown"}`,
5628
+ options: [
5629
+ { value: "resume", label: "Resume Draft", hint: "Continue where you left off" },
5630
+ { value: "back", label: "Back to list" }
5631
+ ]
5632
+ });
5633
+ if (draftAction === null || draftAction === "back") continue;
5634
+ const draftSpinner = spinner();
5635
+ draftSpinner.start("Loading draft...");
5636
+ try {
5637
+ const draftRes = await apiRequest(`/api/submissions/${sub.id}`);
5638
+ const draftData = await draftRes.json();
5639
+ draftSpinner.stop("Draft loaded");
5640
+ if (draftRes.ok && draftData.data) {
5641
+ await resumeSubmitCommand(draftData.data);
5642
+ return;
5643
+ } else {
5644
+ log.error("Could not load this draft.");
5645
+ }
5646
+ } catch {
5647
+ draftSpinner.stop("Failed to load draft");
5648
+ }
5649
+ continue;
5650
+ }
5651
+ if (sub.status !== "complete" || !sub.report_id) {
5652
+ if (sub.status === "analyzing") {
5653
+ log.info("This review is still being analyzed. Check back in a few minutes.");
5654
+ } else if (sub.status === "failed") {
5655
+ log.warning("This review failed. Try submitting again.");
5656
+ } else {
5657
+ log.info(`Status: ${sub.status}`);
5658
+ }
5659
+ console.log();
5660
+ continue;
5661
+ }
5662
+ const reportSpinner = spinner();
5663
+ reportSpinner.start("Loading report...");
5664
+ try {
5665
+ const reportRes = await apiRequest(`/api/reports/${sub.report_id}`);
5666
+ const reportData = await reportRes.json();
5667
+ reportSpinner.stop("Report loaded");
5668
+ if (reportRes.ok) {
5669
+ renderReport(reportData.report, reportData.items);
5670
+ console.log(subtext(` Full report: https://preflightlaunch.com/report/${sub.report_id}`));
5671
+ console.log();
5672
+ } else {
5673
+ log.error("Could not load this report.");
5674
+ }
5675
+ } catch {
5676
+ reportSpinner.stop("Failed to load report");
5677
+ }
5678
+ }
5679
+ } catch (err) {
5680
+ s.stop("Failed to load reviews");
5681
+ log.error(`Error: ${err instanceof Error ? err.message : "Unknown error"}`);
5682
+ }
5683
+ }
5567
5684
 
5568
5685
  // src/commands/setup.ts
5569
5686
  init_esm_shims();
5570
5687
 
5571
5688
  // src/commands/onboarding.ts
5572
5689
  init_esm_shims();
5573
- import { homedir } from "os";
5574
- async function runOnboarding() {
5575
- brandSplash();
5576
- intro("Welcome to Preflight!");
5577
- log.message("Let's get you set up. This takes about 30 seconds.");
5578
- if (!isLoggedIn()) {
5579
- const authChoice = await select({
5580
- message: "Step 1 of 2: Set up your account",
5581
- options: [
5582
- { value: "signup", label: "Open browser to sign up", hint: "Create a free account" },
5583
- { value: "login", label: "I already have an account", hint: "Log in" },
5584
- { value: "skip", label: "Skip for now", hint: "Scan works without login" }
5585
- ]
5586
- });
5587
- if (authChoice === null) return;
5588
- if (authChoice === "signup" || authChoice === "login") {
5589
- const s = spinner();
5590
- s.start("Opening browser...");
5591
- const result = await loginWithBrowser();
5592
- s.stop(result ? `Logged in as ${result.email}` : "Login skipped");
5690
+ async function showWelcomeScreen() {
5691
+ renderHeader();
5692
+ console.log(" Preflight scans your app for common issues that");
5693
+ console.log(" cause App Store rejections -- before you submit.");
5694
+ console.log();
5695
+ console.log(subtext(" How it works:"));
5696
+ console.log(subtext(" 1. Point us to your Xcode project"));
5697
+ console.log(subtext(" 2. We scan for 100+ rejection risks"));
5698
+ console.log(subtext(" 3. Get a detailed report with fixes"));
5699
+ console.log();
5700
+ const result = await select({
5701
+ message: "Ready?",
5702
+ options: [
5703
+ { value: "start", label: "Get Started" }
5704
+ ]
5705
+ });
5706
+ if (result === null) return false;
5707
+ markAsRun();
5708
+ return true;
5709
+ }
5710
+ async function showAuthScreen() {
5711
+ renderHeader();
5712
+ const authChoice = await select({
5713
+ message: "How would you like to get started?",
5714
+ options: [
5715
+ { value: "signup", label: "Create a free account", hint: "Sign up with email, GitHub, or Google" },
5716
+ { value: "login", label: "I already have an account", hint: "Log in with email, GitHub, or Google" }
5717
+ ]
5718
+ });
5719
+ if (authChoice === null) return false;
5720
+ if (authChoice === "signup") {
5721
+ const s2 = spinner();
5722
+ s2.start("Opening signup page in browser...");
5723
+ try {
5724
+ await loginWithBrowser("signup");
5725
+ } catch {
5726
+ s2.stop("Could not open browser");
5727
+ }
5728
+ s2.stop("Signup page opened");
5729
+ log.info("Create your account in the browser, then come back here.");
5730
+ console.log();
5731
+ const ready = await confirm("Done signing up? Ready to log in?");
5732
+ if (ready === null || !ready) {
5733
+ log.info(subtext("Run `preflight` anytime to come back."));
5734
+ return false;
5593
5735
  }
5594
- } else {
5595
- log.success("Already logged in. Skipping account setup.");
5596
5736
  }
5597
- const projects = findXcodeProjects();
5598
- const cwdProject = findProjectInDir(process.cwd());
5599
- if (projects.length > 0 || cwdProject) {
5600
- const allProjects = cwdProject ? [cwdProject, ...projects.filter((p) => p.path !== cwdProject.path)] : projects;
5601
- const choices = allProjects.slice(0, 5).map((proj) => ({
5602
- value: proj.path,
5603
- label: `${proj.name} (${proj.type === "xcworkspace" ? ".xcworkspace" : ".xcodeproj"})`,
5604
- hint: proj.path.replace(homedir(), "~")
5605
- }));
5606
- choices.push({
5607
- value: "__skip__",
5608
- label: "Skip - I'll scan later",
5609
- hint: ""
5610
- });
5611
- const projectChoice = await select({
5612
- message: "Step 2 of 2: Choose your Xcode project",
5613
- options: choices
5614
- });
5615
- if (projectChoice === null) return;
5616
- if (projectChoice !== "__skip__") {
5617
- setLastScannedPath(projectChoice);
5618
- log.success(`Project saved! Run ${brand("preflight scan")} to scan it.`);
5737
+ const s = spinner();
5738
+ s.start("Opening login page... Waiting for you to finish in browser.");
5739
+ try {
5740
+ const result = await loginWithBrowser("login");
5741
+ if (result) {
5742
+ s.stop(`Logged in as ${result.email}`);
5743
+ return true;
5744
+ } else {
5745
+ s.stop("Login timed out or was cancelled");
5746
+ log.warning("Run `preflight` anytime to try again.");
5747
+ return false;
5619
5748
  }
5749
+ } catch {
5750
+ s.stop("Could not open browser");
5751
+ log.warning("Run `preflight login` to try from the command line.");
5752
+ return false;
5753
+ }
5754
+ }
5755
+ async function runOnboarding() {
5756
+ if (!isLoggedIn()) {
5757
+ const welcomed = await showWelcomeScreen();
5758
+ if (!welcomed) return;
5759
+ const authenticated = await showAuthScreen();
5760
+ if (!authenticated) return;
5620
5761
  } else {
5621
- log.info("No Xcode projects found on your Mac.\nRun " + brand("preflight scan <path>") + " when you're ready.");
5762
+ markAsRun();
5763
+ log.success("Already logged in.");
5622
5764
  }
5623
- markAsRun();
5624
- outro("You're all set! Run " + brand("preflight") + " to get started.");
5625
5765
  }
5626
5766
 
5627
5767
  // src/commands/setup.ts
@@ -5629,9 +5769,185 @@ async function setupCommand() {
5629
5769
  await runOnboarding();
5630
5770
  }
5631
5771
 
5772
+ // src/commands/asc.ts
5773
+ init_esm_shims();
5774
+ import { readFileSync, existsSync } from "fs";
5775
+ import { resolve as resolve2 } from "path";
5776
+ async function ascConnectCommand() {
5777
+ log.step("Connect to App Store Connect");
5778
+ console.log(subtext(" You'll need your API key from App Store Connect > Users and Access > Integrations."));
5779
+ console.log();
5780
+ const keyId = await text({
5781
+ message: "Key ID",
5782
+ placeholder: "e.g. ABC123DEF4",
5783
+ validate: (val) => {
5784
+ if (!val?.trim()) return "Key ID is required";
5785
+ }
5786
+ });
5787
+ if (keyId === null) return;
5788
+ const issuerId = await text({
5789
+ message: "Issuer ID",
5790
+ placeholder: "e.g. 12345678-1234-1234-1234-123456789012",
5791
+ validate: (val) => {
5792
+ if (!val?.trim()) return "Issuer ID is required";
5793
+ }
5794
+ });
5795
+ if (issuerId === null) return;
5796
+ const p8Path = await text({
5797
+ message: "Path to .p8 private key file",
5798
+ placeholder: "~/Downloads/AuthKey_ABC123DEF4.p8",
5799
+ validate: (val) => {
5800
+ if (!val?.trim()) return "File path is required";
5801
+ const resolved = resolve2(val.replace(/^~/, process.env.HOME || ""));
5802
+ if (!existsSync(resolved)) return `File not found: ${resolved}`;
5803
+ if (!resolved.endsWith(".p8")) return "File must be a .p8 key file";
5804
+ }
5805
+ });
5806
+ if (p8Path === null) return;
5807
+ const resolvedP8 = resolve2(p8Path.replace(/^~/, process.env.HOME || ""));
5808
+ let privateKey;
5809
+ try {
5810
+ privateKey = readFileSync(resolvedP8, "utf-8");
5811
+ } catch {
5812
+ log.error("Could not read .p8 file.");
5813
+ return;
5814
+ }
5815
+ const s = spinner();
5816
+ s.start("Connecting to App Store Connect...");
5817
+ try {
5818
+ const res = await apiRequest("/api/asc/connect", {
5819
+ method: "POST",
5820
+ body: JSON.stringify({
5821
+ keyId: keyId.trim(),
5822
+ issuerId: issuerId.trim(),
5823
+ privateKey
5824
+ })
5825
+ });
5826
+ const data = await res.json();
5827
+ if (!res.ok) {
5828
+ s.stop("Connection failed");
5829
+ log.error(data.message || "Failed to connect to App Store Connect");
5830
+ return;
5831
+ }
5832
+ s.stop("Connected to App Store Connect!");
5833
+ if (data.apps && data.apps.length > 0) {
5834
+ console.log();
5835
+ log.success(`Team: ${data.teamName || "Your team"}`);
5836
+ console.log();
5837
+ const appOptions = data.apps.map((app) => ({
5838
+ value: app.id,
5839
+ label: app.name,
5840
+ hint: app.bundleId || ""
5841
+ }));
5842
+ const selectedApp = await select({
5843
+ message: "Select your app",
5844
+ options: appOptions
5845
+ });
5846
+ if (selectedApp !== null) {
5847
+ const autofillSpinner = spinner();
5848
+ autofillSpinner.start("Saving app selection...");
5849
+ const autofillRes = await apiRequest("/api/asc/autofill", {
5850
+ method: "POST",
5851
+ body: JSON.stringify({ appId: selectedApp })
5852
+ });
5853
+ if (autofillRes.ok) {
5854
+ const autofillData = await autofillRes.json();
5855
+ autofillSpinner.stop("App selected");
5856
+ log.success(`Connected: ${autofillData.app_name || "App"} is ready for autofill.`);
5857
+ } else {
5858
+ autofillSpinner.stop("App selection saved");
5859
+ }
5860
+ }
5861
+ } else {
5862
+ log.success("Connected! Your App Store Connect data is now available for autofill.");
5863
+ }
5864
+ setAscConnected(true);
5865
+ console.log();
5866
+ } catch (err) {
5867
+ s.stop("Connection failed");
5868
+ log.error(`Error: ${err instanceof Error ? err.message : "Unknown error"}`);
5869
+ }
5870
+ }
5871
+ async function ascStatusCommand() {
5872
+ const s = createSpinner("Checking App Store Connect status...");
5873
+ s.start();
5874
+ try {
5875
+ const res = await apiRequest("/api/asc/connect");
5876
+ const data = await res.json();
5877
+ s.stop();
5878
+ if (!res.ok) {
5879
+ log.error(data.message || "Could not check status");
5880
+ return;
5881
+ }
5882
+ console.log();
5883
+ console.log(brand(" App Store Connect"));
5884
+ console.log();
5885
+ if (data.connected) {
5886
+ console.log(` Status: ${brandDim("Connected")}`);
5887
+ if (data.appName) console.log(` App: ${data.appName}`);
5888
+ if (data.keyId) console.log(` Key ID: ${data.keyId.slice(0, 3)}${"*".repeat(Math.max(0, data.keyId.length - 3))}`);
5889
+ } else {
5890
+ console.log(` Status: ${subtext("Not connected")}`);
5891
+ console.log();
5892
+ console.log(subtext(` Run ${brand("preflight asc connect")} to set up.`));
5893
+ }
5894
+ console.log();
5895
+ } catch (err) {
5896
+ s.stop();
5897
+ log.error(`Error: ${err instanceof Error ? err.message : "Unknown error"}`);
5898
+ }
5899
+ }
5900
+ async function ascDisconnectCommand() {
5901
+ const confirmed = await confirm("Disconnect from App Store Connect?", false);
5902
+ if (confirmed === null || !confirmed) return;
5903
+ const s = createSpinner("Disconnecting...");
5904
+ s.start();
5905
+ try {
5906
+ const res = await apiRequest("/api/asc/connect", {
5907
+ method: "DELETE"
5908
+ });
5909
+ s.stop();
5910
+ if (res.ok) {
5911
+ setAscConnected(false);
5912
+ log.success("Disconnected from App Store Connect.");
5913
+ } else {
5914
+ const data = await res.json();
5915
+ log.error(data.message || "Could not disconnect");
5916
+ }
5917
+ } catch (err) {
5918
+ s.stop();
5919
+ log.error(`Error: ${err instanceof Error ? err.message : "Unknown error"}`);
5920
+ }
5921
+ }
5922
+ async function ascInteractiveMenu() {
5923
+ while (true) {
5924
+ const choice = await select({
5925
+ message: "App Store Connect",
5926
+ options: [
5927
+ { value: "connect", label: "Connect", hint: "Set up API key" },
5928
+ { value: "status", label: "View Status", hint: "Check connection" },
5929
+ { value: "disconnect", label: "Disconnect", hint: "Remove API key" },
5930
+ { value: "back", label: "Back to menu" }
5931
+ ]
5932
+ });
5933
+ if (choice === null || choice === "back") return;
5934
+ switch (choice) {
5935
+ case "connect":
5936
+ await ascConnectCommand();
5937
+ break;
5938
+ case "status":
5939
+ await ascStatusCommand();
5940
+ break;
5941
+ case "disconnect":
5942
+ await ascDisconnectCommand();
5943
+ break;
5944
+ }
5945
+ }
5946
+ }
5947
+
5632
5948
  // src/index.ts
5633
5949
  var program2 = new Command();
5634
- program2.name("preflight").description("Preflight - App Store Review Scanner").version("0.2.0");
5950
+ program2.name("preflight").description("Preflight - App Store Review Scanner").version("0.2.2");
5635
5951
  program2.command("login").description("Log in to Preflight (opens browser)").action(loginCommand);
5636
5952
  program2.command("logout").description("Log out and clear stored credentials").action(logoutCommand);
5637
5953
  program2.command("whoami").description("Show current user and credit balance").action(whoamiCommand);
@@ -5642,53 +5958,86 @@ program2.command("report [id]").description("View full analysis report").option(
5642
5958
  program2.command("history").description("List past submissions").option("--json", "Output as JSON").action(historyCommand);
5643
5959
  program2.command("credits").description("Show credit balance").action(creditsCommand);
5644
5960
  program2.command("setup").description("Run guided setup (can be re-run anytime)").action(setupCommand);
5961
+ var ascCmd = program2.command("asc").description("App Store Connect integration");
5962
+ ascCmd.command("connect").description("Connect your App Store Connect account").action(ascConnectCommand);
5963
+ ascCmd.command("status").description("Check App Store Connect connection status").action(ascStatusCommand);
5964
+ ascCmd.command("disconnect").description("Disconnect from App Store Connect").action(ascDisconnectCommand);
5645
5965
  program2.on("command:*", (operands) => {
5646
5966
  handleUnknownCommand(operands[0]);
5647
5967
  process.exitCode = 1;
5648
5968
  });
5969
+ async function fetchCredits() {
5970
+ try {
5971
+ const res = await apiRequest("/api/credits");
5972
+ if (!res.ok) return void 0;
5973
+ const data = await res.json();
5974
+ return data.credits ?? void 0;
5975
+ } catch {
5976
+ return void 0;
5977
+ }
5978
+ }
5979
+ async function openUrl(url) {
5980
+ try {
5981
+ const open = (await import("./open-A77P4RC4.js")).default;
5982
+ await open(url);
5983
+ } catch {
5984
+ console.log(subtext(` Visit: ${url}`));
5985
+ }
5986
+ }
5649
5987
  async function interactiveMenu() {
5650
5988
  if (!hasRunBefore()) {
5651
- await runOnboarding();
5652
- return;
5989
+ const welcomed = await showWelcomeScreen();
5990
+ if (!welcomed) return;
5653
5991
  }
5654
- intro();
5655
- showTagline();
5656
- const loggedIn = isLoggedIn();
5657
- const options = loggedIn ? [
5658
- { value: "scan", label: "Scan my app", hint: "Free preview" },
5659
- { value: "submit", label: "Submit for full AI analysis", hint: "Uses 1 credit" },
5660
- { value: "history", label: "View my reports", hint: "Past submissions" },
5661
- { value: "account", label: "Check account & credits", hint: "" },
5662
- { value: "help", label: "Help - show all commands", hint: "" }
5663
- ] : [
5664
- { value: "scan", label: "Scan my app", hint: "Free, no login needed" },
5665
- { value: "login", label: "Log in to your account", hint: "Opens browser" },
5666
- { value: "help", label: "Help - show all commands", hint: "" }
5667
- ];
5668
- const choice = await select({
5669
- message: "What would you like to do?",
5670
- options
5671
- });
5672
- if (choice === null) return;
5673
- switch (choice) {
5674
- case "scan":
5675
- await scanCommand();
5676
- break;
5677
- case "submit":
5678
- await submitCommand();
5679
- break;
5680
- case "login":
5681
- await loginCommand();
5682
- break;
5683
- case "history":
5684
- await historyCommand({});
5685
- break;
5686
- case "account":
5687
- await whoamiCommand();
5688
- break;
5689
- case "help":
5690
- program2.outputHelp();
5691
- break;
5992
+ if (!isLoggedIn()) {
5993
+ const authenticated = await showAuthScreen();
5994
+ if (!authenticated) return;
5995
+ }
5996
+ let cachedCredits;
5997
+ cachedCredits = await fetchCredits();
5998
+ while (true) {
5999
+ const { email } = getConfig();
6000
+ renderHeader(email, cachedCredits);
6001
+ const choice = await select({
6002
+ message: "What would you like to do?",
6003
+ options: [
6004
+ { value: "review", label: "New Review", hint: "Scan your app for App Store issues" },
6005
+ { value: "history", label: "View Reviews", hint: "See your past review reports" },
6006
+ { value: "buy", label: "Buy Credits", hint: "Get more credits at preflightlaunch.com" },
6007
+ { value: "asc", label: "App Store Connect", hint: "Connect your ASC account for autofill" },
6008
+ { value: "logout", label: "Log Out" }
6009
+ ]
6010
+ });
6011
+ if (choice === null) {
6012
+ console.log();
6013
+ console.log(subtext(" See you next time!"));
6014
+ console.log();
6015
+ return;
6016
+ }
6017
+ switch (choice) {
6018
+ case "review":
6019
+ await submitCommand(void 0, {}, true);
6020
+ cachedCredits = await fetchCredits();
6021
+ break;
6022
+ case "history":
6023
+ await interactiveHistory();
6024
+ break;
6025
+ case "buy":
6026
+ await openUrl("https://preflightlaunch.com/pricing");
6027
+ log.info("Opened pricing page in browser.");
6028
+ await new Promise((r) => setTimeout(r, 2e3));
6029
+ cachedCredits = await fetchCredits();
6030
+ break;
6031
+ case "asc":
6032
+ await ascInteractiveMenu();
6033
+ break;
6034
+ case "logout":
6035
+ clearAuth();
6036
+ const authenticated = await showAuthScreen();
6037
+ if (!authenticated) return;
6038
+ cachedCredits = await fetchCredits();
6039
+ break;
6040
+ }
5692
6041
  }
5693
6042
  }
5694
6043
  if (process.argv.length <= 2) {