@mushi-mushi/cli 0.23.1 → 0.25.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
@@ -14,7 +14,7 @@ import {
14
14
  resolveConsoleUrl,
15
15
  resolveConsoleUrlSync,
16
16
  saveConfig
17
- } from "./chunk-NZ64LBLU.js";
17
+ } from "./chunk-2WHQONHK.js";
18
18
  import {
19
19
  FRAMEWORKS,
20
20
  detectFramework,
@@ -25,7 +25,7 @@ import {
25
25
  } from "./chunk-AIZRI665.js";
26
26
  import {
27
27
  MUSHI_CLI_VERSION
28
- } from "./chunk-I4UK3WTN.js";
28
+ } from "./chunk-SS42XQ57.js";
29
29
 
30
30
  // src/init.ts
31
31
  import * as p from "@clack/prompts";
@@ -60,7 +60,9 @@ async function startDeviceAuth(endpoint, clientId) {
60
60
  });
61
61
  const json = await res.json().catch(() => null);
62
62
  if (!res.ok || !json?.ok || !json.data) {
63
- throw new Error(json?.error?.message ?? `Could not start browser sign-in (HTTP ${res.status}).`);
63
+ throw new Error(
64
+ json?.error?.message ?? `Could not start browser sign-in (HTTP ${res.status}).`
65
+ );
64
66
  }
65
67
  return json.data;
66
68
  }
@@ -101,10 +103,15 @@ async function pollDeviceToken(endpoint, deviceCode) {
101
103
  };
102
104
  }
103
105
  } catch (err) {
104
- return { status: "error", message: err instanceof Error ? err.message : String(err), retryable: true };
106
+ return {
107
+ status: "error",
108
+ message: err instanceof Error ? err.message : String(err),
109
+ retryable: true
110
+ };
105
111
  }
106
112
  }
107
113
  var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
114
+ var deviceAuthRetryDelayMs = 1e3;
108
115
  async function waitForCliToken(endpoint, session, opts = {}) {
109
116
  const sleep = opts.sleep ?? defaultSleep;
110
117
  const now = opts.now ?? Date.now;
@@ -155,19 +162,51 @@ function cliAuthHeaders(cliToken) {
155
162
  Authorization: `Bearer ${cliToken}`
156
163
  };
157
164
  }
165
+ var DeviceAuthRequestError = class extends Error {
166
+ step;
167
+ status;
168
+ retryable;
169
+ constructor(step, message, status) {
170
+ super(message);
171
+ this.name = "DeviceAuthRequestError";
172
+ this.step = step;
173
+ this.status = status;
174
+ this.retryable = status === void 0 || status >= 500 || status === 408;
175
+ }
176
+ };
177
+ async function withOneRetry(run) {
178
+ try {
179
+ return await run();
180
+ } catch (err) {
181
+ if (err instanceof DeviceAuthRequestError && err.retryable) {
182
+ if (deviceAuthRetryDelayMs > 0) {
183
+ await defaultSleep(deviceAuthRetryDelayMs);
184
+ }
185
+ return run();
186
+ }
187
+ throw err;
188
+ }
189
+ }
158
190
  async function listProjects(endpoint, cliToken) {
159
191
  const base = trimTrailingSlash(endpoint);
160
- try {
161
- const res = await deviceFetch(`${base}/v1/cli/projects`, { headers: cliAuthHeaders(cliToken) });
192
+ return withOneRetry(async () => {
193
+ let res;
194
+ try {
195
+ res = await deviceFetch(`${base}/v1/cli/projects`, { headers: cliAuthHeaders(cliToken) });
196
+ } catch (err) {
197
+ throw new DeviceAuthRequestError(
198
+ "list_projects",
199
+ `Could not reach ${base}/v1/cli/projects: ${err instanceof Error ? err.message : String(err)}`
200
+ );
201
+ }
162
202
  const json = await res.json().catch(() => null);
163
203
  if (res.ok && json?.ok) return json.data?.projects ?? [];
164
- } catch (err) {
165
- console.error(
166
- "[mushi] Failed to list projects:",
167
- err instanceof Error ? err.message : err
204
+ throw new DeviceAuthRequestError(
205
+ "list_projects",
206
+ json?.error?.message ?? `Could not list projects (HTTP ${res.status}).`,
207
+ res.status
168
208
  );
169
- }
170
- return [];
209
+ });
171
210
  }
172
211
  async function createProject(endpoint, cliToken, name) {
173
212
  const base = trimTrailingSlash(endpoint);
@@ -185,17 +224,26 @@ async function createProject(endpoint, cliToken, name) {
185
224
  }
186
225
  async function mintProjectKey(endpoint, cliToken, projectId) {
187
226
  const base = trimTrailingSlash(endpoint);
227
+ let res;
188
228
  try {
189
- const res = await deviceFetch(`${base}/v1/cli/projects/${projectId}/keys`, {
229
+ res = await deviceFetch(`${base}/v1/cli/projects/${projectId}/keys`, {
190
230
  method: "POST",
191
231
  headers: cliAuthHeaders(cliToken),
192
232
  body: JSON.stringify({})
193
233
  });
194
- const json = await res.json().catch(() => null);
195
- if (res.ok && json?.ok) return json.data?.key ?? null;
196
- } catch {
234
+ } catch (err) {
235
+ throw new DeviceAuthRequestError(
236
+ "mint_key",
237
+ `Could not reach ${base}/v1/cli/projects/\u2026/keys: ${err instanceof Error ? err.message : String(err)}`
238
+ );
197
239
  }
198
- return null;
240
+ const json = await res.json().catch(() => null);
241
+ if (res.ok && json?.ok && json.data?.key) return json.data.key;
242
+ throw new DeviceAuthRequestError(
243
+ "mint_key",
244
+ json?.error?.message ?? `Could not mint an SDK key (HTTP ${res.status}).`,
245
+ res.status
246
+ );
199
247
  }
200
248
 
201
249
  // src/freshness.ts
@@ -419,8 +467,16 @@ async function runInit(options = {}) {
419
467
  } else {
420
468
  p.log.info(`Skipped install. Run \`${installCommand(pm, packagesToInstall)}\` yourself.`);
421
469
  }
422
- await writeEnvFile(cwd, credentials.apiKey, credentials.projectId, framework, endpoint, Boolean(options.yes));
470
+ await writeEnvFile(
471
+ cwd,
472
+ credentials.apiKey,
473
+ credentials.projectId,
474
+ framework,
475
+ endpoint,
476
+ Boolean(options.yes)
477
+ );
423
478
  persistCliConfig(credentials.apiKey, credentials.projectId, endpoint);
479
+ emitWizardFunnelEvent(credentials, endpoint, "wizard_env_written", { framework: framework.id });
424
480
  const enableRewards = await maybeEnableRewards(options);
425
481
  printNextSteps(framework, consoleBase, enableRewards);
426
482
  await maybeSendTestReport(credentials, { ...options, endpoint, consoleBase });
@@ -492,7 +548,9 @@ async function acquireCredentials(options, consoleBase, endpoint) {
492
548
  if (options.yes) {
493
549
  const creds = await runBrowserSignIn(options, endpoint, consoleBase);
494
550
  if (creds) return creds;
495
- p.log.warn("Browser sign-in didn't complete \u2014 switching to manual entry.");
551
+ p.log.warn(
552
+ "Browser sign-in didn't complete \u2014 switching to manual entry. Run `npx mushi-mushi doctor --auth` to diagnose the sign-in path."
553
+ );
496
554
  } else {
497
555
  const method = await p.select({
498
556
  message: "Connect this app to Mushi",
@@ -517,7 +575,9 @@ async function acquireCredentials(options, consoleBase, endpoint) {
517
575
  if (method === "browser") {
518
576
  const creds = await runBrowserSignIn(options, endpoint, consoleBase);
519
577
  if (creds) return creds;
520
- p.log.warn("Browser sign-in didn't complete \u2014 switching to manual entry.");
578
+ p.log.warn(
579
+ "Browser sign-in didn't complete \u2014 switching to manual entry. Run `npx mushi-mushi doctor --auth` to diagnose the sign-in path."
580
+ );
521
581
  }
522
582
  }
523
583
  return collectCredentialsManually(options, consoleBase, endpoint);
@@ -558,7 +618,9 @@ async function runBrowserSignIn(options, endpoint, consoleBase) {
558
618
  fetchSpin.start("Loading your projects\u2026");
559
619
  try {
560
620
  projects = await listProjects(endpoint, cliToken);
561
- fetchSpin.stop(projects.length > 0 ? `Found ${projects.length} project(s).` : "No projects yet.");
621
+ fetchSpin.stop(
622
+ projects.length > 0 ? `Found ${projects.length} project(s).` : "No projects yet."
623
+ );
562
624
  } catch (err) {
563
625
  fetchSpin.stop("Could not load projects \u2014 you can still create a new one.");
564
626
  p.log.warn(err instanceof Error ? err.message : String(err));
@@ -606,19 +668,14 @@ async function runBrowserSignIn(options, endpoint, consoleBase) {
606
668
  const keySpin = p.spinner();
607
669
  keySpin.start("Minting SDK key\u2026");
608
670
  try {
609
- const minted = await mintProjectKey(endpoint, cliToken, projectId);
610
- apiKey = minted ?? void 0;
611
- if (!apiKey) {
612
- keySpin.stop("Could not mint an API key.");
613
- p.log.warn(
614
- `Open the console Verify tab to generate a key manually: ${consoleUrl(consoleBase, "/onboarding?tab=verify")}`
615
- );
616
- return null;
617
- }
671
+ apiKey = await mintProjectKey(endpoint, cliToken, projectId);
618
672
  keySpin.stop("SDK key ready.");
619
673
  } catch (err) {
620
674
  keySpin.stop("Could not mint an API key.");
621
675
  p.log.warn(err instanceof Error ? err.message : String(err));
676
+ p.log.warn(
677
+ `You're signed in, but key minting failed. Generate one manually in the console Verify tab: ${consoleUrl(consoleBase, "/onboarding?tab=verify")}`
678
+ );
622
679
  return null;
623
680
  }
624
681
  }
@@ -678,10 +735,11 @@ async function verifyCredentials(credentials, options, consoleBase) {
678
735
  const endpoint = resolveCloudEndpoint(options.endpoint);
679
736
  const spinner2 = p.spinner();
680
737
  spinner2.start("Verifying credentials\u2026");
681
- const result = await apiCall(
682
- "/v1/sync/whoami",
683
- { apiKey: credentials.apiKey, projectId: credentials.projectId, endpoint }
684
- );
738
+ const result = await apiCall("/v1/sync/whoami", {
739
+ apiKey: credentials.apiKey,
740
+ projectId: credentials.projectId,
741
+ endpoint
742
+ });
685
743
  if (!result.ok) {
686
744
  spinner2.stop("Credentials could not be verified.");
687
745
  p.log.error(result.error?.message ?? "Authentication failed.");
@@ -836,6 +894,20 @@ function persistCliConfig(apiKey, projectId, endpoint) {
836
894
  const existing = loadConfig();
837
895
  saveConfig({ ...existing, apiKey, projectId, endpoint });
838
896
  }
897
+ function emitWizardFunnelEvent(credentials, endpoint, event, metadata = {}) {
898
+ if (process.env.MUSHI_NO_TELEMETRY) return;
899
+ void fetch(`${endpoint.replace(/\/$/, "")}/v1/cli/funnel`, {
900
+ method: "POST",
901
+ headers: {
902
+ "Content-Type": "application/json",
903
+ "X-Mushi-Api-Key": credentials.apiKey,
904
+ "X-Mushi-Project": credentials.projectId
905
+ },
906
+ body: JSON.stringify({ event, source: "cli", metadata }),
907
+ signal: AbortSignal.timeout(4e3)
908
+ }).catch(() => {
909
+ });
910
+ }
839
911
  function printNextSteps(framework, consoleBase, enableRewards = false) {
840
912
  p.note(framework.snippet(), "Add this to your app:");
841
913
  if (enableRewards) {
@@ -869,7 +941,7 @@ async function maybeOfferConnect(credentials, options, _consoleBase) {
869
941
  if (p.isCancel(answer) || !answer) return;
870
942
  const endpoint = resolveCloudEndpoint(options.endpoint);
871
943
  try {
872
- const { runConnect } = await import("./connect-IWFKKXGZ.js");
944
+ const { runConnect } = await import("./connect-XFWRSEMW.js");
873
945
  await runConnect({
874
946
  apiKey: credentials.apiKey,
875
947
  projectId: credentials.projectId,
@@ -880,12 +952,8 @@ async function maybeOfferConnect(credentials, options, _consoleBase) {
880
952
  wait: true
881
953
  });
882
954
  } catch (err) {
883
- p.log.warn(
884
- err instanceof Error ? err.message : String(err)
885
- );
886
- p.log.info(
887
- "You can run manually: mushi connect --write-env --wire-ide --wait"
888
- );
955
+ p.log.warn(err instanceof Error ? err.message : String(err));
956
+ p.log.info("You can run manually: mushi connect --write-env --wire-ide --wait");
889
957
  }
890
958
  }
891
959
  async function maybeEnableRewards(options) {
package/dist/version.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MUSHI_CLI_VERSION
3
- } from "./chunk-I4UK3WTN.js";
3
+ } from "./chunk-SS42XQ57.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.23.1",
3
+ "version": "0.25.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": {
@@ -11,11 +11,11 @@
11
11
  "commander": "^15.0.0"
12
12
  },
13
13
  "devDependencies": {
14
- "@types/node": "^22.19.21",
15
- "eslint": "^10.4.1",
14
+ "@types/node": "^22.20.0",
15
+ "eslint": "^10.6.0",
16
16
  "tsup": "^8.5.1",
17
17
  "typescript": "^6.0.3",
18
- "vitest": "^4.1.8",
18
+ "vitest": "^4.1.9",
19
19
  "@mushi-mushi/eslint-config": "0.0.0"
20
20
  },
21
21
  "author": "Kenji Sakuramoto (https://bsky.app/profile/mushimushi.dev)",
@@ -1,6 +0,0 @@
1
- // src/version.ts
2
- var MUSHI_CLI_VERSION = true ? "0.23.1" : "0.0.0-dev";
3
-
4
- export {
5
- MUSHI_CLI_VERSION
6
- };