@mushi-mushi/cli 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/init.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MUSHI_CLI_VERSION
3
- } from "./chunk-6LM2KW4S.js";
3
+ } from "./chunk-5KN6S63C.js";
4
4
  import {
5
5
  TEST_REPORT_FETCH_TIMEOUT_MS,
6
6
  loadConfig,
@@ -15,13 +15,13 @@ import {
15
15
  envVarsToWrite,
16
16
  installCommand,
17
17
  readPackageJson
18
- } from "./chunk-7Q44KWGN.js";
18
+ } from "./chunk-JAUKBLPK.js";
19
19
 
20
20
  // src/init.ts
21
21
  import * as p from "@clack/prompts";
22
22
  import { spawn } from "child_process";
23
23
  import { randomUUID } from "crypto";
24
- import { appendFileSync, existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
24
+ import { appendFileSync, existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync } from "fs";
25
25
  import { join as join3 } from "path";
26
26
 
27
27
  // src/console-url.ts
@@ -207,13 +207,13 @@ async function apiCall(path, config, options = {}) {
207
207
  }
208
208
  if (!res.ok && typeof body === "object" && body !== null && !("ok" in body)) {
209
209
  const b = body;
210
+ const nested = b["error"];
211
+ const errCode = nested?.["code"] ?? b["code"] ?? `HTTP_${res.status}`;
212
+ const errMsg = nested?.["message"] ?? b["message"] ?? `Request failed (${res.status})`;
210
213
  return {
211
214
  ok: false,
212
215
  httpStatus: res.status,
213
- error: {
214
- code: b["code"] ?? `HTTP_${res.status}`,
215
- message: b["message"] ?? `Request failed (${res.status})`
216
- }
216
+ error: { code: errCode, message: errMsg }
217
217
  };
218
218
  }
219
219
  return body;
@@ -306,8 +306,12 @@ async function waitForCliToken(endpoint, session, opts = {}) {
306
306
  const deadline = now() + (session.expires_in || 600) * 1e3;
307
307
  const maxConsecutiveErrors = opts.maxConsecutiveErrors ?? 5;
308
308
  let consecutiveErrors = 0;
309
+ let firstPoll = true;
309
310
  while (now() < deadline) {
310
- await sleep(intervalMs);
311
+ if (!firstPoll) {
312
+ await sleep(intervalMs);
313
+ }
314
+ firstPoll = false;
311
315
  const outcome = await pollDeviceToken(endpoint, session.device_code);
312
316
  switch (outcome.status) {
313
317
  case "approved":
@@ -535,6 +539,33 @@ function getFrameworkFromPkg(pkg) {
535
539
  return void 0;
536
540
  }
537
541
 
542
+ // src/auth-ui.ts
543
+ var LINE = "\u2500".repeat(58);
544
+ function printAuthBanner(userCode, verificationUri) {
545
+ console.log("");
546
+ console.log(` \u250C${LINE}\u2510`);
547
+ console.log(` \u2502 Browser sign-in \u2014 follow these 3 steps \u2502`);
548
+ console.log(` \u251C${LINE}\u2524`);
549
+ console.log(` \u2502 \u2502`);
550
+ console.log(` \u2502 1. A browser tab will open (or visit the URL below) \u2502`);
551
+ console.log(` \u2502 2. Sign in if prompted, then click "Approve" \u2502`);
552
+ console.log(` \u2502 3. Come back here \u2014 setup continues automatically \u2502`);
553
+ console.log(` \u2502 \u2502`);
554
+ console.log(` \u251C${LINE}\u2524`);
555
+ console.log(` \u2502 Verification code (show in browser \u2014 NOT terminal): \u2502`);
556
+ console.log(` \u2502 \u2502`);
557
+ console.log(` \u2502 ${userCode.padEnd(52)}\u2502`);
558
+ console.log(` \u2502 \u2502`);
559
+ console.log(` \u2502 \u26A0 Do NOT paste or type this code in your terminal \u2502`);
560
+ console.log(` \u2514${LINE}\u2518`);
561
+ console.log("");
562
+ console.log(` If the browser didn't open, visit this URL and approve:`);
563
+ console.log(` ${verificationUri}`);
564
+ console.log("");
565
+ console.log(" \u23F3 Waiting for browser approval\u2026 (Ctrl+C to cancel)");
566
+ console.log("");
567
+ }
568
+
538
569
  // src/init.ts
539
570
  var ENV_FILES = [".env.local", ".env"];
540
571
  var PROJECT_ID_PATTERN = /^(?:proj_[A-Za-z0-9_-]{10,}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
@@ -542,7 +573,7 @@ var API_KEY_PATTERN = /^(mushi_|mush_pk_)[A-Za-z0-9_-]{10,}$/;
542
573
  async function runInit(options = {}) {
543
574
  const cwd = options.cwd ?? process.cwd();
544
575
  ensureInteractiveOrBailOut(options);
545
- p.intro("\u{1F41B} Mushi Mushi setup wizard");
576
+ p.intro("Mushi setup");
546
577
  await printFreshnessHint();
547
578
  warnIfWorkspaceRoot(cwd);
548
579
  const pkg = readPackageJson(cwd);
@@ -570,13 +601,13 @@ async function runInit(options = {}) {
570
601
  } else {
571
602
  p.log.info(`Skipped install. Run \`${installCommand(pm, packagesToInstall)}\` yourself.`);
572
603
  }
573
- writeEnvFile(cwd, credentials.apiKey, credentials.projectId, framework);
604
+ await writeEnvFile(cwd, credentials.apiKey, credentials.projectId, framework, endpoint, Boolean(options.yes));
574
605
  persistCliConfig(credentials.apiKey, credentials.projectId, endpoint);
575
606
  const enableRewards = await maybeEnableRewards(options);
576
607
  printNextSteps(framework, consoleBase, enableRewards);
577
608
  await maybeSendTestReport(credentials, { ...options, endpoint, consoleBase });
578
609
  await maybeOfferConnect(credentials, options, consoleBase);
579
- p.outro("Setup complete. Happy bug squashing \u{1F41B}");
610
+ p.outro("Setup complete.");
580
611
  }
581
612
  function ensureInteractiveOrBailOut(options) {
582
613
  const isTTY = Boolean(process.stdin.isTTY && process.stdout.isTTY);
@@ -641,7 +672,7 @@ async function acquireCredentials(options, consoleBase, endpoint) {
641
672
  }
642
673
  }
643
674
  if (options.yes) {
644
- const creds = await runBrowserSignIn(options, endpoint);
675
+ const creds = await runBrowserSignIn(options, endpoint, consoleBase);
645
676
  if (creds) return creds;
646
677
  p.log.warn("Browser sign-in didn't complete \u2014 switching to manual entry.");
647
678
  } else {
@@ -657,7 +688,7 @@ async function acquireCredentials(options, consoleBase, endpoint) {
657
688
  {
658
689
  value: "manual",
659
690
  label: "Paste a Project ID + API key",
660
- hint: "Self-hosted or expert setup"
691
+ hint: "Self-hosted or expert setup \u2014 run npx mushi-mushi (not mushi setup)"
661
692
  }
662
693
  ]
663
694
  });
@@ -666,14 +697,14 @@ async function acquireCredentials(options, consoleBase, endpoint) {
666
697
  process.exit(0);
667
698
  }
668
699
  if (method === "browser") {
669
- const creds = await runBrowserSignIn(options, endpoint);
700
+ const creds = await runBrowserSignIn(options, endpoint, consoleBase);
670
701
  if (creds) return creds;
671
702
  p.log.warn("Browser sign-in didn't complete \u2014 switching to manual entry.");
672
703
  }
673
704
  }
674
705
  return collectCredentialsManually(options, consoleBase);
675
706
  }
676
- async function runBrowserSignIn(options, endpoint) {
707
+ async function runBrowserSignIn(options, endpoint, consoleBase) {
677
708
  const startSpin = p.spinner();
678
709
  startSpin.start("Starting secure browser sign-in\u2026");
679
710
  let session;
@@ -685,13 +716,11 @@ async function runBrowserSignIn(options, endpoint) {
685
716
  return null;
686
717
  }
687
718
  startSpin.stop("Browser sign-in ready.");
688
- p.log.step(`Confirmation code: ${session.user_code}`);
689
- p.log.info(`Opening ${session.verification_uri}`);
690
719
  try {
691
720
  await openInBrowser(session.verification_uri);
692
721
  } catch {
693
722
  }
694
- p.log.message("Approve the request in your browser to continue. (Ctrl+C to cancel)");
723
+ printAuthBanner(session.user_code, session.verification_uri);
695
724
  const waitSpin = p.spinner();
696
725
  waitSpin.start("Waiting for you to approve in the browser\u2026");
697
726
  let cliToken;
@@ -707,9 +736,14 @@ async function runBrowserSignIn(options, endpoint) {
707
736
  let apiKey;
708
737
  if (!projectId) {
709
738
  let projects = [];
739
+ const fetchSpin = p.spinner();
740
+ fetchSpin.start("Loading your projects\u2026");
710
741
  try {
711
742
  projects = await listProjects(endpoint, cliToken);
712
- } catch {
743
+ fetchSpin.stop(projects.length > 0 ? `Found ${projects.length} project(s).` : "No projects yet.");
744
+ } catch (err) {
745
+ fetchSpin.stop("Could not load projects \u2014 you can still create a new one.");
746
+ p.log.warn(err instanceof Error ? err.message : String(err));
713
747
  }
714
748
  const NEW = "__new__";
715
749
  const choice = await p.select({
@@ -751,7 +785,24 @@ async function runBrowserSignIn(options, endpoint) {
751
785
  }
752
786
  }
753
787
  if (projectId && !apiKey) {
754
- apiKey = await mintProjectKey(endpoint, cliToken, projectId) ?? void 0;
788
+ const keySpin = p.spinner();
789
+ keySpin.start("Minting SDK key\u2026");
790
+ try {
791
+ const minted = await mintProjectKey(endpoint, cliToken, projectId);
792
+ apiKey = minted ?? void 0;
793
+ if (!apiKey) {
794
+ keySpin.stop("Could not mint an API key.");
795
+ p.log.warn(
796
+ `Open the console Verify tab to generate a key manually: ${consoleUrl(consoleBase, "/onboarding?tab=verify")}`
797
+ );
798
+ return null;
799
+ }
800
+ keySpin.stop("SDK key ready.");
801
+ } catch (err) {
802
+ keySpin.stop("Could not mint an API key.");
803
+ p.log.warn(err instanceof Error ? err.message : String(err));
804
+ return null;
805
+ }
755
806
  }
756
807
  if (!projectId || !apiKey) return null;
757
808
  return { apiKey, projectId };
@@ -857,13 +908,37 @@ function runCommand(pm, packages, cwd) {
857
908
  });
858
909
  });
859
910
  }
860
- function writeEnvFile(cwd, apiKey, projectId, framework) {
911
+ async function writeEnvFile(cwd, apiKey, projectId, framework, endpoint, overwrite) {
861
912
  const target = ENV_FILES.find((f) => existsSync3(join3(cwd, f))) ?? ENV_FILES[0];
862
913
  const targetPath = join3(cwd, target);
863
- const newVars = envVarsToWrite(apiKey, projectId, framework);
914
+ const newVars = envVarsToWrite(apiKey, projectId, framework, endpoint);
864
915
  const existing = existsSync3(targetPath) ? readFileSync3(targetPath, "utf-8") : "";
865
916
  if (existing.includes("MUSHI_PROJECT_ID")) {
866
- p.log.warn(`Existing MUSHI_* vars found in ${target} \u2014 leaving them untouched.`);
917
+ let shouldOverwrite = overwrite;
918
+ if (!shouldOverwrite) {
919
+ const answer = await p.confirm({
920
+ message: `Existing MUSHI_* vars found in ${target}. Update with new credentials?`,
921
+ initialValue: true
922
+ });
923
+ if (p.isCancel(answer)) {
924
+ p.log.info(`Kept existing env vars in ${target}.`);
925
+ return;
926
+ }
927
+ shouldOverwrite = Boolean(answer);
928
+ }
929
+ if (!shouldOverwrite) {
930
+ p.log.info(`Kept existing env vars in ${target}. Re-run and confirm to overwrite.`);
931
+ return;
932
+ }
933
+ const MUSHI_LINE_RE = /^(NEXT_PUBLIC_|NUXT_PUBLIC_|VITE_|EXPO_PUBLIC_)?MUSHI_[A-Z_]+=.*/gm;
934
+ const stripped = existing.replace(MUSHI_LINE_RE, "").replace(/\n{3,}/g, "\n\n").trimEnd();
935
+ const prefix2 = stripped.length > 0 ? "\n" : "";
936
+ writeFileSync(targetPath, `${stripped}${prefix2}
937
+ # Mushi Mushi
938
+ ${newVars}
939
+ `);
940
+ p.log.success(`Updated MUSHI_* env vars in ${target}`);
941
+ warnIfMissingFromGitignore(cwd, target);
867
942
  return;
868
943
  }
869
944
  const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
@@ -924,12 +999,14 @@ import { MushiRewardsBadge } from '@mushi-mushi/react';
924
999
  p.log.info(`Enable rewards in your project settings at ${consoleUrl(consoleBase, "/rewards")}`);
925
1000
  p.log.info("Users will earn points for bug reports, screen navigation, and app activity.");
926
1001
  }
927
- p.log.message("Verify the install:");
928
- p.log.message(" \u2022 Start your dev server");
929
- p.log.message(" \u2022 Look for the \u{1F41B} button in the bottom-right corner (or shake on mobile)");
930
- p.log.message(` \u2022 Submit a test report \u2014 it should appear at ${reportsUrl(consoleBase)}`);
931
- p.log.info(
932
- "Tip: `mushi connect --write-env --wire-ide --wait` also wires Cursor MCP and waits for SDK heartbeat."
1002
+ p.note(
1003
+ [
1004
+ " [ ] 1. Paste the init snippet above into your app entry file",
1005
+ " [ ] 2. Start your dev server",
1006
+ " [ ] 3. Run: mushi connect --write-env --wire-ide --wait",
1007
+ ` [ ] 4. Open the Verify tab to send a test report: ${consoleUrl(consoleBase, "/onboarding?tab=verify")}`
1008
+ ].join("\n"),
1009
+ "Next steps:"
933
1010
  );
934
1011
  }
935
1012
  async function maybeOfferConnect(credentials, options, _consoleBase) {
@@ -941,7 +1018,7 @@ async function maybeOfferConnect(credentials, options, _consoleBase) {
941
1018
  if (p.isCancel(answer) || !answer) return;
942
1019
  const endpoint = resolveCloudEndpoint(options.endpoint);
943
1020
  try {
944
- const { runConnect } = await import("./connect-IC7I76Z2.js");
1021
+ const { runConnect } = await import("./connect-2RM43XHO.js");
945
1022
  await runConnect({
946
1023
  apiKey: credentials.apiKey,
947
1024
  projectId: credentials.projectId,
package/dist/version.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MUSHI_CLI_VERSION
3
- } from "./chunk-6LM2KW4S.js";
3
+ } from "./chunk-5KN6S63C.js";
4
4
  export {
5
5
  MUSHI_CLI_VERSION
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mushi-mushi/cli",
3
- "version": "0.20.0",
3
+ "version": "0.22.0",
4
4
  "license": "MIT",
5
5
  "description": "CLI for Mushi Mushi — `mushi init` wizard installs the right SDK for your framework, plus report triage and pipeline health commands",
6
6
  "bin": {
@@ -18,13 +18,13 @@
18
18
  "vitest": "^4.1.8",
19
19
  "@mushi-mushi/eslint-config": "0.0.0"
20
20
  },
21
- "author": "Kenji Sakuramoto",
21
+ "author": "Kenji Sakuramoto (https://bsky.app/profile/mushimushi.dev)",
22
22
  "repository": {
23
23
  "type": "git",
24
24
  "url": "https://github.com/kensaurus/mushi-mushi.git",
25
25
  "directory": "packages/cli"
26
26
  },
27
- "homepage": "https://github.com/kensaurus/mushi-mushi/tree/main/packages/cli#readme",
27
+ "homepage": "https://kensaur.us/mushi-mushi",
28
28
  "bugs": {
29
29
  "url": "https://github.com/kensaurus/mushi-mushi/issues"
30
30
  },
@@ -1,6 +0,0 @@
1
- // src/version.ts
2
- var MUSHI_CLI_VERSION = true ? "0.20.0" : "0.0.0-dev";
3
-
4
- export {
5
- MUSHI_CLI_VERSION
6
- };