@mushi-mushi/cli 0.22.1 → 0.23.1

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,13 +1,20 @@
1
- import {
2
- MUSHI_CLI_VERSION
3
- } from "./chunk-YK4LF7AM.js";
4
1
  import {
5
2
  TEST_REPORT_FETCH_TIMEOUT_MS,
3
+ apiCall,
4
+ apiKeyHint,
5
+ cliSetupDeepLink,
6
+ consoleUrl,
7
+ ensureClientId,
6
8
  loadConfig,
7
9
  normalizeEndpoint,
10
+ openInBrowser,
11
+ projectIdHint,
12
+ reportsUrl,
8
13
  resolveCloudEndpoint,
14
+ resolveConsoleUrl,
15
+ resolveConsoleUrlSync,
9
16
  saveConfig
10
- } from "./chunk-XEXYGDQV.js";
17
+ } from "./chunk-NZ64LBLU.js";
11
18
  import {
12
19
  FRAMEWORKS,
13
20
  detectFramework,
@@ -15,231 +22,21 @@ import {
15
22
  envVarsToWrite,
16
23
  installCommand,
17
24
  readPackageJson
18
- } from "./chunk-JAUKBLPK.js";
25
+ } from "./chunk-AIZRI665.js";
26
+ import {
27
+ MUSHI_CLI_VERSION
28
+ } from "./chunk-I4UK3WTN.js";
19
29
 
20
30
  // src/init.ts
21
31
  import * as p from "@clack/prompts";
22
32
  import { spawn } from "child_process";
23
33
  import { randomUUID } from "crypto";
24
- import { appendFileSync, existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync } from "fs";
25
- import { join as join3 } from "path";
26
-
27
- // src/console-url.ts
28
- import { existsSync, readFileSync } from "fs";
29
- import { dirname, join } from "path";
30
- var HOSTED_CONSOLE_BASE = "https://kensaur.us/mushi-mushi/admin";
31
- var LOCAL_ADMIN_URL = "http://localhost:6464";
32
- var LOCAL_PROBE_MS = 800;
33
- function normalizeConsoleBase(url) {
34
- const trimmed = url.trim();
35
- let end = trimmed.length;
36
- while (end > 0 && trimmed.charCodeAt(end - 1) === 47) end--;
37
- return trimmed.slice(0, end);
38
- }
39
- function consoleUrl(base, route) {
40
- const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
41
- return `${normalizeConsoleBase(base)}${normalizedRoute}`;
42
- }
43
- function resolveConsoleUrlFromEnv() {
44
- const raw = process.env["MUSHI_CONSOLE_URL"]?.trim();
45
- return raw ? normalizeConsoleBase(raw) : void 0;
46
- }
47
- function isInsideMushiMonorepo(cwd) {
48
- let dir = cwd;
49
- for (let depth = 0; depth < 14; depth += 1) {
50
- const pkgPath = join(dir, "package.json");
51
- if (existsSync(pkgPath)) {
52
- try {
53
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
54
- if (pkg.name === "mushi-mushi") return true;
55
- } catch {
56
- }
57
- }
58
- const parent = dirname(dir);
59
- if (parent === dir) break;
60
- dir = parent;
61
- }
62
- return false;
63
- }
64
- async function probeLocalAdminConsole() {
65
- try {
66
- const controller = new AbortController();
67
- const timer = setTimeout(() => controller.abort(), LOCAL_PROBE_MS);
68
- const res = await fetch(LOCAL_ADMIN_URL, {
69
- signal: controller.signal,
70
- method: "HEAD"
71
- });
72
- clearTimeout(timer);
73
- if (res.ok || res.status === 404 || res.status === 401) {
74
- return LOCAL_ADMIN_URL;
75
- }
76
- } catch {
77
- }
78
- return null;
79
- }
80
- function resolveConsoleUrlSync(cwd = process.cwd()) {
81
- const fromEnv = resolveConsoleUrlFromEnv();
82
- if (fromEnv) return fromEnv;
83
- const config = loadConfig();
84
- if (config.consoleUrl) return normalizeConsoleBase(config.consoleUrl);
85
- if (isInsideMushiMonorepo(cwd)) return LOCAL_ADMIN_URL;
86
- return HOSTED_CONSOLE_BASE;
87
- }
88
- async function resolveConsoleUrl(options) {
89
- const fromEnv = resolveConsoleUrlFromEnv();
90
- if (fromEnv) return fromEnv;
91
- const config = loadConfig();
92
- if (config.consoleUrl) return normalizeConsoleBase(config.consoleUrl);
93
- const local = await probeLocalAdminConsole();
94
- if (local) return local;
95
- const cwd = options?.cwd ?? process.cwd();
96
- if (isInsideMushiMonorepo(cwd)) return LOCAL_ADMIN_URL;
97
- return HOSTED_CONSOLE_BASE;
98
- }
99
- function projectIdHint(base) {
100
- return `Where to find it: ${consoleUrl(base, "/projects")} \u2192 select your project \u2192 click the UUID chip to copy (or copy it from the panel right after you create the project).`;
101
- }
102
- function apiKeyHint(base) {
103
- return `Where to find it: ${consoleUrl(base, "/onboarding?tab=verify")} \u2192 Generate API key (SDK ingest needs report:write \u2014 not Settings \u2192 BYOK keys). Treat it like a password \u2014 env file only, never commit.`;
104
- }
105
- function cliSetupDeepLink(base) {
106
- return consoleUrl(base, "/onboarding?tab=steps&setup=cli");
107
- }
108
- function reportsUrl(base, reportId) {
109
- return reportId ? consoleUrl(base, `/reports/${reportId}`) : consoleUrl(base, "/reports");
110
- }
111
- async function openInBrowser(url) {
112
- let parsed;
113
- try {
114
- parsed = new URL(url);
115
- } catch {
116
- return;
117
- }
118
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
119
- return;
120
- }
121
- const safeUrl = parsed.toString();
122
- const { spawn: spawn2 } = await import("child_process");
123
- const [command, args] = process.platform === "win32" ? (
124
- // Open via rundll32's FileProtocolHandler rather than `cmd /c start`.
125
- // `start` is a cmd builtin, so the URL would be re-parsed by cmd.exe and
126
- // characters like `&` / `|` / `^` could still break out — exactly the
127
- // injection surface CodeQL flags. rundll32 never invokes a shell: the URL
128
- // is handed to the default browser as one opaque argument.
129
- ["rundll32", ["url.dll,FileProtocolHandler", safeUrl]]
130
- ) : process.platform === "darwin" ? ["open", [safeUrl]] : ["xdg-open", [safeUrl]];
131
- await new Promise((resolve2) => {
132
- try {
133
- const child = spawn2(command, [...args], { stdio: "ignore", shell: false });
134
- child.on("error", () => resolve2());
135
- child.on("exit", () => resolve2());
136
- child.unref();
137
- } catch {
138
- resolve2();
139
- }
140
- });
141
- }
142
-
143
- // src/signals.ts
144
- var activeController = null;
145
- function getAbortSignal(external) {
146
- if (external) return external.signal;
147
- if (!activeController || activeController.signal.aborted) {
148
- activeController = new AbortController();
149
- }
150
- return activeController.signal;
151
- }
152
-
153
- // src/cli-shared.ts
154
- var API_TIMEOUT_MS = 15e3;
155
- async function apiCall(path, config, options = {}) {
156
- const endpoint = config.endpoint;
157
- if (!endpoint) {
158
- return {
159
- ok: false,
160
- error: {
161
- code: "NO_ENDPOINT",
162
- message: "No API endpoint configured.\n Run: mushi login --api-key <key> --endpoint <url> --project-id <id>\n Or: export MUSHI_API_ENDPOINT=https://<project-ref>.supabase.co/functions/v1/api"
163
- }
164
- };
165
- }
166
- const controller = new AbortController();
167
- const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
168
- const signals = [controller.signal, getAbortSignal()];
169
- const compositeSignal = AbortSignal.any ? AbortSignal.any(signals) : controller.signal;
170
- try {
171
- const res = await fetch(`${endpoint}${path}`, {
172
- ...options,
173
- signal: compositeSignal,
174
- headers: {
175
- "Content-Type": "application/json",
176
- // `apiKeyAuth` reads X-Mushi-Api-Key; the Authorization header is a
177
- // fallback accepted by some older middleware. Both are sent.
178
- "Authorization": `Bearer ${config.apiKey ?? ""}`,
179
- "X-Mushi-Api-Key": config.apiKey ?? "",
180
- "X-Mushi-Project": config.projectId ?? "",
181
- "X-Mushi-Cli-Version": MUSHI_CLI_VERSION,
182
- ...options.headers
183
- }
184
- });
185
- clearTimeout(timer);
186
- let body;
187
- const contentType = res.headers.get("content-type") ?? "";
188
- if (contentType.includes("application/json")) {
189
- try {
190
- body = await res.json();
191
- } catch {
192
- body = null;
193
- }
194
- } else {
195
- const text2 = await res.text();
196
- try {
197
- body = JSON.parse(text2);
198
- } catch {
199
- body = {
200
- ok: false,
201
- error: {
202
- code: `HTTP_${res.status}`,
203
- message: text2.trim().slice(0, 300) || `HTTP ${res.status}`
204
- }
205
- };
206
- }
207
- }
208
- if (!res.ok && typeof body === "object" && body !== null && !("ok" in body)) {
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})`;
213
- return {
214
- ok: false,
215
- httpStatus: res.status,
216
- error: { code: errCode, message: errMsg }
217
- };
218
- }
219
- return body;
220
- } catch (err) {
221
- clearTimeout(timer);
222
- if (err instanceof Error && err.name === "AbortError") {
223
- return {
224
- ok: false,
225
- error: {
226
- code: "TIMEOUT",
227
- message: `Request timed out after ${API_TIMEOUT_MS / 1e3}s. Check your network or endpoint.`
228
- }
229
- };
230
- }
231
- return {
232
- ok: false,
233
- error: {
234
- code: "NETWORK_ERROR",
235
- message: err instanceof Error ? err.message : String(err)
236
- }
237
- };
238
- }
239
- }
34
+ import { appendFileSync, existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
35
+ import { join as join2 } from "path";
240
36
 
241
37
  // src/device-auth.ts
242
38
  var DEVICE_FETCH_TIMEOUT_MS = 15e3;
39
+ var DEFAULT_SLOW_DOWN_MS = 5e3;
243
40
  function trimTrailingSlash(endpoint) {
244
41
  let end = endpoint.length;
245
42
  while (end > 0 && endpoint.charCodeAt(end - 1) === 47) end--;
@@ -254,11 +51,12 @@ async function deviceFetch(url, init = {}) {
254
51
  clearTimeout(timer);
255
52
  }
256
53
  }
257
- async function startDeviceAuth(endpoint) {
54
+ async function startDeviceAuth(endpoint, clientId) {
258
55
  const base = trimTrailingSlash(endpoint);
259
56
  const res = await deviceFetch(`${base}/v1/cli/auth/device/start`, {
260
57
  method: "POST",
261
- headers: { "Content-Type": "application/json" }
58
+ headers: { "Content-Type": "application/json" },
59
+ body: JSON.stringify(clientId ? { client_id: clientId } : {})
262
60
  });
263
61
  const json = await res.json().catch(() => null);
264
62
  if (!res.ok || !json?.ok || !json.data) {
@@ -281,6 +79,12 @@ async function pollDeviceToken(endpoint, deviceCode) {
281
79
  switch (json.error) {
282
80
  case "authorization_pending":
283
81
  return { status: "pending" };
82
+ case "slow_down": {
83
+ const retryAfterHeader = res.headers?.get?.("Retry-After");
84
+ const retryAfterSeconds = retryAfterHeader ? Number(retryAfterHeader) : NaN;
85
+ const retryAfterMs = Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0 ? retryAfterSeconds * 1e3 : DEFAULT_SLOW_DOWN_MS;
86
+ return { status: "slow_down", retryAfterMs };
87
+ }
284
88
  case "access_denied":
285
89
  return { status: "denied" };
286
90
  case "expired_token":
@@ -289,9 +93,11 @@ async function pollDeviceToken(endpoint, deviceCode) {
289
93
  return {
290
94
  status: "error",
291
95
  message: json.error_description ?? json.error ?? `HTTP ${res.status}`,
292
- // 5xx is a server hiccup the next poll may clear; a 4xx is a definitive
293
- // rejection (already claimed / invalid_grant) that won't fix itself.
294
- retryable: res.status >= 500
96
+ // 5xx is a server hiccup the next poll may clear; 408 (gateway
97
+ // timeout) is equally transient. Any other 4xx is a definitive
98
+ // rejection (already claimed / invalid_grant) that won't fix
99
+ // itself. (429/slow_down is handled above as its own outcome.)
100
+ retryable: res.status >= 500 || res.status === 408
295
101
  };
296
102
  }
297
103
  } catch (err) {
@@ -302,7 +108,7 @@ var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
302
108
  async function waitForCliToken(endpoint, session, opts = {}) {
303
109
  const sleep = opts.sleep ?? defaultSleep;
304
110
  const now = opts.now ?? Date.now;
305
- const intervalMs = (session.interval || 5) * 1e3;
111
+ let intervalMs = (session.interval || 5) * 1e3;
306
112
  const deadline = now() + (session.expires_in || 600) * 1e3;
307
113
  const maxConsecutiveErrors = opts.maxConsecutiveErrors ?? 5;
308
114
  let consecutiveErrors = 0;
@@ -320,6 +126,11 @@ async function waitForCliToken(endpoint, session, opts = {}) {
320
126
  consecutiveErrors = 0;
321
127
  opts.onPending?.();
322
128
  continue;
129
+ case "slow_down":
130
+ consecutiveErrors = 0;
131
+ intervalMs = Math.max(intervalMs, outcome.retryAfterMs);
132
+ opts.onSlowDown?.(outcome.retryAfterMs);
133
+ continue;
323
134
  case "denied":
324
135
  throw new Error("Login was denied in the browser.");
325
136
  case "expired":
@@ -350,7 +161,11 @@ async function listProjects(endpoint, cliToken) {
350
161
  const res = await deviceFetch(`${base}/v1/cli/projects`, { headers: cliAuthHeaders(cliToken) });
351
162
  const json = await res.json().catch(() => null);
352
163
  if (res.ok && json?.ok) return json.data?.projects ?? [];
353
- } catch {
164
+ } catch (err) {
165
+ console.error(
166
+ "[mushi] Failed to list projects:",
167
+ err instanceof Error ? err.message : err
168
+ );
354
169
  }
355
170
  return [];
356
171
  }
@@ -443,8 +258,8 @@ function parse(version) {
443
258
  }
444
259
 
445
260
  // src/monorepo.ts
446
- import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
447
- import { dirname as dirname2, join as join2, resolve } from "path";
261
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
262
+ import { dirname, join, resolve } from "path";
448
263
  var WORKSPACE_GLOB_CANDIDATES = ["apps/*", "packages/*", "examples/*"];
449
264
  var FRAMEWORK_DEPS = {
450
265
  next: "Next.js",
@@ -461,9 +276,9 @@ var FRAMEWORK_DEPS = {
461
276
  function detectWorkspaceHint(cwd) {
462
277
  const root = findWorkspaceRoot(cwd);
463
278
  if (!root) return null;
464
- const rootPkg = readPackageJsonSafely(join2(root, "package.json"));
279
+ const rootPkg = readPackageJsonSafely(join(root, "package.json"));
465
280
  if (rootPkg && getFrameworkFromPkg(rootPkg)) return null;
466
- const source = existsSync2(join2(root, "pnpm-workspace.yaml")) ? "pnpm-workspace" : root === cwd ? "package-json" : "parent";
281
+ const source = existsSync(join(root, "pnpm-workspace.yaml")) ? "pnpm-workspace" : root === cwd ? "package-json" : "parent";
467
282
  const apps = collectAppsFromGlobs(root);
468
283
  if (apps.length === 0) return null;
469
284
  return { root, apps, source };
@@ -472,15 +287,15 @@ function findWorkspaceRoot(start) {
472
287
  let dir = resolve(start);
473
288
  for (let i = 0; i < 8; i++) {
474
289
  if (isWorkspaceRoot(dir)) return dir;
475
- const parent = dirname2(dir);
290
+ const parent = dirname(dir);
476
291
  if (parent === dir) break;
477
292
  dir = parent;
478
293
  }
479
294
  return null;
480
295
  }
481
296
  function isWorkspaceRoot(dir) {
482
- if (existsSync2(join2(dir, "pnpm-workspace.yaml"))) return true;
483
- const pkg = readPackageJsonSafely(join2(dir, "package.json"));
297
+ if (existsSync(join(dir, "pnpm-workspace.yaml"))) return true;
298
+ const pkg = readPackageJsonSafely(join(dir, "package.json"));
484
299
  if (!pkg) return false;
485
300
  return Boolean(pkg.workspaces);
486
301
  }
@@ -488,8 +303,8 @@ function collectAppsFromGlobs(root) {
488
303
  const results = [];
489
304
  for (const glob of WORKSPACE_GLOB_CANDIDATES) {
490
305
  const prefix = glob.replace("/*", "");
491
- const parentDir = join2(root, prefix);
492
- if (!existsSync2(parentDir)) continue;
306
+ const parentDir = join(root, prefix);
307
+ if (!existsSync(parentDir)) continue;
493
308
  let entries;
494
309
  try {
495
310
  entries = readdirSync(parentDir);
@@ -497,7 +312,7 @@ function collectAppsFromGlobs(root) {
497
312
  continue;
498
313
  }
499
314
  for (const entry of entries) {
500
- const pkgPath = join2(parentDir, entry, "package.json");
315
+ const pkgPath = join(parentDir, entry, "package.json");
501
316
  if (!isFileSafe(pkgPath)) continue;
502
317
  const pkg = readPackageJsonSafely(pkgPath);
503
318
  if (!pkg) continue;
@@ -515,14 +330,14 @@ function collectAppsFromGlobs(root) {
515
330
  function readPackageJsonSafely(path) {
516
331
  if (!isFileSafe(path)) return null;
517
332
  try {
518
- return JSON.parse(readFileSync2(path, "utf-8"));
333
+ return JSON.parse(readFileSync(path, "utf-8"));
519
334
  } catch {
520
335
  return null;
521
336
  }
522
337
  }
523
338
  function isFileSafe(path) {
524
339
  try {
525
- return existsSync2(path) && statSync(path).isFile();
340
+ return existsSync(path) && statSync(path).isFile();
526
341
  } catch {
527
342
  return false;
528
343
  }
@@ -591,7 +406,10 @@ async function runInit(options = {}) {
591
406
  const detected = detectFramework(cwd, pkg);
592
407
  const framework = await chooseFramework(detected, options);
593
408
  const consoleBase = await resolveConsoleUrl({ cwd });
594
- const endpoint = resolveCloudEndpoint(options.endpoint);
409
+ const endpoint = resolveCloudEndpoint(
410
+ options.endpoint ?? process.env.MUSHI_API_ENDPOINT?.trim() ?? loadConfig().endpoint
411
+ );
412
+ options = { ...options, endpoint };
595
413
  const credentials = await acquireCredentials(options, consoleBase, endpoint);
596
414
  await verifyCredentials(credentials, options, consoleBase);
597
415
  const pm = detectPackageManager(cwd);
@@ -702,14 +520,14 @@ async function acquireCredentials(options, consoleBase, endpoint) {
702
520
  p.log.warn("Browser sign-in didn't complete \u2014 switching to manual entry.");
703
521
  }
704
522
  }
705
- return collectCredentialsManually(options, consoleBase);
523
+ return collectCredentialsManually(options, consoleBase, endpoint);
706
524
  }
707
525
  async function runBrowserSignIn(options, endpoint, consoleBase) {
708
526
  const startSpin = p.spinner();
709
527
  startSpin.start("Starting secure browser sign-in\u2026");
710
528
  let session;
711
529
  try {
712
- session = await startDeviceAuth(endpoint);
530
+ session = await startDeviceAuth(endpoint, ensureClientId());
713
531
  } catch (err) {
714
532
  startSpin.stop("Could not start browser sign-in.");
715
533
  p.log.warn(err instanceof Error ? err.message : String(err));
@@ -807,15 +625,36 @@ async function runBrowserSignIn(options, endpoint, consoleBase) {
807
625
  if (!projectId || !apiKey) return null;
808
626
  return { apiKey, projectId };
809
627
  }
810
- async function collectCredentialsManually(options, consoleBase) {
628
+ async function collectCredentialsManually(options, consoleBase, endpoint) {
811
629
  const existing = loadConfig();
812
- const rawProjectId = options.projectId ?? existing.projectId ?? await promptText({
630
+ let savedProjectId = existing.projectId;
631
+ let savedApiKey = existing.apiKey;
632
+ if (!options.projectId && !options.apiKey && savedProjectId && savedApiKey) {
633
+ p.log.info(
634
+ `Found saved credentials from a previous sign-in (project ${sanitizeSecret(savedProjectId).slice(0, 8)}\u2026).`
635
+ );
636
+ const checkSpin = p.spinner();
637
+ checkSpin.start("Checking saved credentials\u2026");
638
+ const check = await apiCall("/v1/sync/whoami", {
639
+ apiKey: sanitizeSecret(savedApiKey),
640
+ projectId: sanitizeSecret(savedProjectId),
641
+ endpoint
642
+ });
643
+ if (check.ok) {
644
+ checkSpin.stop(`Saved credentials still work (${check.data.project_name}).`);
645
+ } else {
646
+ checkSpin.stop("Saved credentials no longer authenticate \u2014 enter fresh ones below.");
647
+ savedProjectId = void 0;
648
+ savedApiKey = void 0;
649
+ }
650
+ }
651
+ const rawProjectId = options.projectId ?? savedProjectId ?? await promptText({
813
652
  message: "Project ID",
814
653
  placeholder: "e.g. bdafa28d-b153-482f-bd4f-42981f3fd3a4",
815
654
  hint: projectIdHint(consoleBase),
816
655
  validate: (v) => PROJECT_ID_PATTERN.test(v.trim()) ? void 0 : "Expected a UUID \u2014 copy it from the Projects page or the panel right after you create a project."
817
656
  });
818
- const rawApiKey = options.apiKey ?? existing.apiKey ?? await promptText({
657
+ const rawApiKey = options.apiKey ?? savedApiKey ?? await promptText({
819
658
  message: "API key",
820
659
  placeholder: "mushi_xxxxxxxxxxxx",
821
660
  hint: apiKeyHint(consoleBase),
@@ -846,10 +685,22 @@ async function verifyCredentials(credentials, options, consoleBase) {
846
685
  if (!result.ok) {
847
686
  spinner2.stop("Credentials could not be verified.");
848
687
  p.log.error(result.error?.message ?? "Authentication failed.");
849
- p.log.info(`Double-check values in the console:
850
- ${cliSetupDeepLink(consoleBase)}`);
688
+ p.log.warn("Setup did NOT complete: nothing was installed and no env vars were written.");
689
+ p.log.info(
690
+ [
691
+ "To recover:",
692
+ ' \u2022 Re-run `npx mushi-mushi` and choose "Sign in with your browser", or',
693
+ ` \u2022 Double-check the Project ID / API key in the console: ${cliSetupDeepLink(consoleBase)}`
694
+ ].join("\n")
695
+ );
851
696
  throw new Error("Credential verification failed \u2014 fix Project ID / API key and re-run.");
852
697
  }
698
+ if (result.data.project_id && result.data.project_id !== credentials.projectId) {
699
+ p.log.warn(
700
+ `Project ID ${redact(credentials.projectId)} doesn't match this API key's project \u2014 using the key's actual project (${result.data.project_name}) instead.`
701
+ );
702
+ credentials.projectId = result.data.project_id;
703
+ }
853
704
  spinner2.stop(`Connected to ${result.data.project_name}`);
854
705
  }
855
706
  function sanitizeSecret(raw) {
@@ -909,10 +760,10 @@ function runCommand(pm, packages, cwd) {
909
760
  });
910
761
  }
911
762
  async function writeEnvFile(cwd, apiKey, projectId, framework, endpoint, overwrite) {
912
- const target = ENV_FILES.find((f) => existsSync3(join3(cwd, f))) ?? ENV_FILES[0];
913
- const targetPath = join3(cwd, target);
763
+ const target = ENV_FILES.find((f) => existsSync2(join2(cwd, f))) ?? ENV_FILES[0];
764
+ const targetPath = join2(cwd, target);
914
765
  const newVars = envVarsToWrite(apiKey, projectId, framework, endpoint);
915
- const existing = existsSync3(targetPath) ? readFileSync3(targetPath, "utf-8") : "";
766
+ const existing = existsSync2(targetPath) ? readFileSync2(targetPath, "utf-8") : "";
916
767
  if (existing.includes("MUSHI_PROJECT_ID")) {
917
768
  let shouldOverwrite = overwrite;
918
769
  if (!shouldOverwrite) {
@@ -971,12 +822,12 @@ function escapeRegexChar(ch) {
971
822
  return /[-/\\^$+?.()|[\]{}]/.test(ch) ? `\\${ch}` : ch;
972
823
  }
973
824
  function warnIfMissingFromGitignore(cwd, envFile) {
974
- const gitignorePath = join3(cwd, ".gitignore");
975
- if (!existsSync3(gitignorePath)) {
825
+ const gitignorePath = join2(cwd, ".gitignore");
826
+ if (!existsSync2(gitignorePath)) {
976
827
  p.log.warn(`No .gitignore found \u2014 make sure ${envFile} is not committed.`);
977
828
  return;
978
829
  }
979
- const content = readFileSync3(gitignorePath, "utf-8");
830
+ const content = readFileSync2(gitignorePath, "utf-8");
980
831
  if (!isEnvFileCoveredByGitignore(content, envFile)) {
981
832
  p.log.warn(`${envFile} is not in .gitignore \u2014 add it before committing.`);
982
833
  }
@@ -1018,7 +869,7 @@ async function maybeOfferConnect(credentials, options, _consoleBase) {
1018
869
  if (p.isCancel(answer) || !answer) return;
1019
870
  const endpoint = resolveCloudEndpoint(options.endpoint);
1020
871
  try {
1021
- const { runConnect } = await import("./connect-2RM43XHO.js");
872
+ const { runConnect } = await import("./connect-IWFKKXGZ.js");
1022
873
  await runConnect({
1023
874
  apiKey: credentials.apiKey,
1024
875
  projectId: credentials.projectId,
package/dist/version.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MUSHI_CLI_VERSION
3
- } from "./chunk-YK4LF7AM.js";
3
+ } from "./chunk-I4UK3WTN.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.22.1",
3
+ "version": "0.23.1",
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": {
@@ -47,18 +47,19 @@
47
47
  "sideEffects": false,
48
48
  "keywords": [
49
49
  "mushi-mushi",
50
- "bug-reporting",
51
- "user-feedback",
52
- "user-report",
53
- "feedback-widget",
54
- "session-replay",
55
- "screenshot",
56
- "shake-to-report",
57
- "sdk",
50
+ "bug-translation",
51
+ "vibe-coding",
52
+ "ai-debugging",
53
+ "ai-triage",
54
+ "auto-fix",
55
+ "mcp",
56
+ "cursor",
57
+ "claude-code",
58
+ "cli",
58
59
  "wizard",
59
60
  "setup",
60
- "cli",
61
61
  "init",
62
+ "sdk",
62
63
  "next.js",
63
64
  "nextjs",
64
65
  "react",
@@ -72,10 +73,7 @@
72
73
  "capacitor",
73
74
  "sentry-companion",
74
75
  "sentry-alternative",
75
- "error-tracking",
76
- "ai-triage",
77
- "auto-fix",
78
- "llm-ops"
76
+ "ai-agent"
79
77
  ],
80
78
  "exports": {
81
79
  ".": {